括弧で迷子にならずにelispを編集する方法


13

linum.elのelispコードを修正しています:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

に変更する(count-lines (point-min) (point-max))ことで、インデントが1つずつずれていたバグを修正できました(+ (count-lines (point-min) (point-max)) 1)。それは簡単でした。

ただし(concat " %" (number-to-string w) "2d ")、行番号のカウントが10未満の場合にif-conditional whereを追加して、最小幅が2になるように変更します。

これは簡単なはずです!1つの条件を追加し、連結をコピー/貼り付けます。ケーキだよね?つまり、何をすべきかはわかってますが、elispに触れることはめったになく、多くの括弧で何かを変更しなければならないときはいつも気が遠くなります。

私が理解していることから、「正しい」スタイルは、インデントに基づいてコードを構造化し、それ自体ではなく行末で括弧をラップすることです。他の「C」スタイルの言語から来て、私はこの方法でコードを読み書きするのに苦労しています。だから私の質問は次のとおりです。私は何が間違っていますか?elispを編集してコードをナビゲートし、そこに座ってすべての括弧を数える必要がないようにするにはどうすればよいですか?

elispで深くなりすぎるものを扱うときは、ドアを閉め、ブラインドを引っ張り、K&Rスタイルの括弧を配置する必要があります。

明らかに私はこれをすべて間違っています。どうすればこのようなelispを恐れることなくタッチできますか?

私の質問は、スタイルに関する質問としてではなく、elispをナビゲートおよび編集する方法です。私はすでにスタイルガイドとして次を使用しています:https : //github.com/bbatsov/emacs-lisp-style-guide

更新:

emacs.stackexchangeで恥ずかしがる前にelispを適切にフォーマットする方法:

elispをマークして実行しますM-x indent-region

問題の解決策:

最小幅が2のlinumの右揃えの実行方法を知りたい場合は、次の解決策があります。

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1)
                           (concat " %" (number-to-string w) "d ")
                         " %2d ")))
    ad-do-it))

1
誰もがCから来るLispを読むのに苦労しています。時間がかかりますが、最終的には非常に自然になります。また、おそらくlinum-formatを "%2d"のような形式の文字列に変更することで問題を解決できます。
ジョードンビオンド

%2d幅が3文字以上になると、右揃えから左揃えに移動するので、私は使用できません。
-Zhro

(私の意見では)貴重な一致する括弧を強調するいくつかのライブラリがありhighlight-parenthesesます。rainbow-delimiters; などここには私の自身の簡易版であるhighlight-parentheses最後の色付けされた括弧を削除せずにスクロールすることを許可: stackoverflow.com/a/23998965/2112489 、それは顧客/スレッドごとに一つの質問将来のです。
法律家

1
問題はElispコードを編集するのではなく、実際に読んでいるように思えます。Lispの構造を理解するのに問題があり、それがあなたの編集を損なうように聞こえます。
PythonNut

1
あなたの防御では、それはかなりひどくインデントされたコードです。そのため、最初の3回は自分で間違って読みました。また、paredit
マラバルバ

回答:


12

paredit、smartparens、lispyなど、役立つ可能性のあるアドオンパッケージがいくつかあります。これらのパッケージにより、Lispコードのナビゲートと操作が簡単になり、s式で考えて、括弧のバランスを心配することができます。

Emacsには、学習する価値があり、Lispコードの構造に慣れるのに役立つ可能性のあるsexpsとリストを処理するための組み込みコマンドがたくさんあります。これらは通常C-M-、次のようなプレフィックスでバインドされます。

  • C-M-f/ C-M-bsexpで前後に移動する
  • C-M-n/ C-M-pリストで前後に移動するには
  • C-M-u/ C-M-d1レベル上/下に移動する
  • C-M-t ポイントの周りの2つのsexpsを交換するには
  • C-M-k sexpを殺す
  • C-M-SPC sexpをマークする
  • C-M-q sexpを再度インデントする

インデントを修正すると、提供したサンプルコードがより明確になる可能性があります。最初にポイントを置き(defadvice...、ヒットC-M-qして式全体を再度インデントします。を置き換えるには(concat...、そのsexpの先頭にポイントを置きC-M-o、インデントを保持しながら行を分割することで開始します。次に、を追加し、(if...上記のコマンドを使用してリストの末尾にジャンプし、別の閉じ括弧を追加したり、先頭に戻ってインデントしたりします。

またshow-paren-mode、ポイントがリストの最初または最後にあるときに対応する括弧を強調表示するをオンにすることもできます。一致するかっこではなく、式全体を強調表示することをお勧めしshow-paren-styleます。カスタマイズを試してください。

@Tylerが別の回答のコメントで言及したように、これらのコマンドおよび関連するコマンドの詳細については、マニュアルをご覧ください。


C-M-qプレフィックス引数(C-u C-M-q)を指定して呼び出して、ポイントで式をきれいに印刷できることに注意してください。これにより、すべてが改行されてインデントされ、ネストが非常に明確になります。一般的に、あなたのLispコードをそのように見せたくないのですが、手作業でK&Rを完全に実行することなく、少しのコードを理解することは役に立つかもしれません。(そして、いつでもC-x u元に戻すことができます)。
グルーカス

6

LISPを編集する良い方法は、個々の文字や行ではなく、ASTを操作することです。私は2年前に始めたパッケージlispyでそれをやっています 。実際にこれをうまく行う方法を学ぶにはかなりの練習が必要になると思いますが、基本だけを使用しても既に役立つはずです。

この変更を行う方法を説明できます。

ステップ1

通常、開始位置はトップレベルのsexpの前にポイントを置くことです。ここ|がポイントです。

|(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

コードを適切にインデントする必要があるため、i1回押します。それはうまくインデントします。

ステップ2

あなたはマークしたいと思う"d "操作可能なオブジェクト以降では、地域とlispy地域でマークする必要がマークする必要はありませんリスト、またはシンボルのシーケンスおよび/またはリストのいずれかです。

を押しatてそうします。このaコマンドは、親リスト内の単一のシンボルをマークすることを許可しt、この特定のシンボルのインデックスです。

ステップ3

  1. 現在の領域をラップするには()、を押し(ます。
  2. を押しC-fて1文字前に移動し、を挿入しifます。
  3. (もう一度押して挿入し()ます。
  4. を挿入し> w 10ます。
  5. C-f C-m 文字列を新しい行に移動します。

ステップ4

文字列を複製するには:

  1. ポイントでシンボルまたは文字列をマークしM-mます。
  2. を押しcます。

リージョンを派手な方法で非アクティブ化し、ポイントを文字列の最初に配置する場合は、次のキーを押しidmます。

  1. i 文字列の内部コンテンツを選択します。
  2. d ポイントとマークを交換します。
  3. m 地域を無効にします

またはm C-b C-b C-b、この場合、またはを行うことができますC-g C-b C-b C-b。私が思うにidm、それは関係なく、文字列の長さが同じなので、良いです。C-x C-x C-f C-g 文字列の長さに関係なく動作すると思います。

最後に、挿入2して終了コードを取得します。

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) (if (> w 10)
                                                             "2|d "
                                                           "d "))))
    ad-do-it))

概要

全配列でした:at( C-fif(> w 10C-f C-m M-m cidm2

コードの挿入を除外する場合、AST操作に関連しないキーは2 C-fと1 のみであることに注意してくださいC-m


3
Lispyは面白いですね!OPに:あなたは、同様のpareditとワークフローを開発することができます:emacsrocks.com/e14.htmlをし、展開する地域:github.com/magnars/expand-region.el、または単に内蔵の括弧のマッチングコマンドで遊ん:GNU。 org / software / emacs / manual / html_node / emacs / Parentheses.html
タイラー

あなたが私のために問題を解決するために結び付けたとき、私はあなたが推測していたと確信しています。試みてくれてありがとう。あなたのコードを例として使用して、なんとかやり遂げました。正しい解決策については、私の更新を参照してください。
-Zhro

6

時間が経つにつれて慣れてきますが、もちろん、高速化のためにできることはたくさんあります。

くぼみ

この狂気には方法があります。Lispではこれを行うことができます:

(a-fun (another-fun (magic-macro 1)))

それ1が本当に大きな式であり、独自の行でインデントしたいとします。

(a-fun (another-fun (magic-macro 
  1)))

これは誤解を招きます。1上位3レベルのネストの場合でも、1つのスロットのみがインデントされます!親によってそれを置く方が良い。

(a-fun (another-fun (magic-macro 
                      1)))

独自のコードでこのスキームを使用すると、次のようになります。

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                      (+ (count-lines (point-min) (point-max)) 1))))       
          (linum-format 
            (concat " %" (number-to-string w) "d ")))
     ad-do-it))

インデントとネストのレベルの間には強い相関関係があることに注意してください。ネストのレベルが大きい行は、少ない行よりも常にインデントされます。

これだけでも大いに役立ちます。コードの「形状」を簡単に確認できます。他のほとんどの言語では、インデントをブロックと、何らかの形式のネスト(明示的または暗黙的)のブロックに関連付けるようにトレーニングされています。

演習として、コードから不要な括弧を削除しました。Elispの基本的な知識(つまり、関数と値、およびの構造let*)を考えると、コードを読み取り、欠落している括弧を提供できるはずです。

custom-set-variables '(linum-format 'dynamic)
defadvice linum-update-window (around linum-dynamic activate)
  let* w length number-to-string
                  + 
                    count-lines (point-min) (point-max) 
                    1       
         linum-format 
           concat " %" (number-to-string w) "d "
     ad-do-it

1
Emacsでは、タブを押すと、ほとんどの場合、ここで説明するようにインデントが正しく設定されます。手動でスペースを追加してインデントを設定しないでください。
タイラー

newline-and-indentemacs-lisp-modeおよびlisp-interaction-modeに「Cm」をバインドすると、最初の例であるPythonNutが解決されます。そして、TABは残りの時間は仕事をします。
デイバーCubranic

5

私はこれを別の答えとして含めています。

インデントが失敗する場合があります。あなたの頼りは、次のようなものを使用することですrainbow-delimiters

ここに画像の説明を入力してください

これにより、任意の括弧のネストレベルが明示的になり、スキャンが容易になります。

ただし、かなり大きな問題が1つあります。かっこはばかげています。ここには重要なバランスがあります。rainbow-delimiters通常、残りのコードを犠牲にして、括弧に多くの重点を置きます!しかし、虹を下に向けると、次第にスキャンが難しくなります。

このバランスのとれた顔のセットを1つ見つけることができる場合は、必ず使用してください。

そうは言っても、1つの解決策(私に喜びを与えさせるもの)は、2組の顔を持ち、それらをオンザフライで切り替えることです。他の作業をしているとき、顔は薄められて背景にフェードインします。

ここに画像の説明を入力してください

しかし、丸括弧にポイントを置く、丸括弧がパンチされるので、ネストレベルを確認できます。

ここに画像の説明を入力してください

そして、これを行うコードは次のとおりです。

(defvar rainbow-delimiters-switch nil
  "t if rainbow-delimiters are currently punched")
(defvar rainbow-delimiters-face-cookies nil
  "a list of face-remap-add-relative cookies to reset")

(make-variable-buffer-local 'rainbow-delimiters-switch)
(make-variable-buffer-local 'rainbow-delimiters-face-cookies)

(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
(add-hook 'text-mode-hook #'rainbow-delimiters-mode)

(with-eval-after-load 'rainbow-delimiters
  (set-face-foreground 'rainbow-delimiters-depth-1-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-2-face "#9b7b6b")
  (set-face-foreground 'rainbow-delimiters-depth-3-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-4-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-5-face "#839564")
  (set-face-foreground 'rainbow-delimiters-depth-6-face "#6391aa")
  (set-face-foreground 'rainbow-delimiters-depth-7-face "#9d748f")
  (set-face-foreground 'rainbow-delimiters-depth-8-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-9-face "#659896")

  (defun rainbow-delimiters-focus-on ()
    "Punch the rainbow-delimiters"
    (setq rainbow-delimiters-face-cookies
      (list
        (face-remap-add-relative 'rainbow-delimiters-depth-1-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-1-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-2-face
          '((:foreground "#9B471D") rainbow-delimiters-depth-2-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-3-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-3-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-4-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-4-face))
            (face-remap-add-relative 'rainbow-delimiters-depth-5-face
          '((:foreground "#679519") rainbow-delimiters-depth-5-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-6-face
          '((:foreground "#0E73AA") rainbow-delimiters-depth-6-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-7-face
          '((:foreground "#9D2574") rainbow-delimiters-depth-7-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-8-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-8-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-9-face
          '((:foreground "#199893") rainbow-delimiters-depth-9-face)))
      rainbow-delimiters-switch t))

  (defun rainbow-delimiters-focus-off ()
    "Reset the rainbow-delimiters faces"
    (mapc #'face-remap-remove-relative rainbow-delimiters-face-cookies)
    (setq rainbow-delimiters-switch nil))

  (defun rainbow-delimiters-focus-on-maybe ()
    "Punch the rainbow-delimiters if the point is on a paren"
    (when (looking-at "[][(){}]")
      (unless (or rainbow-delimiters-switch (minibufferp))
        (rainbow-delimiters-focus-on))))

  (defun rainbow-delimiters-focus-off-maybe ()
    "Reset the rainbow-delimiters if the point is not on a paren"
    (unless (looking-at "[][(){}]")
      (when rainbow-delimiters-switch
        (rainbow-delimiters-focus-off))))

  (run-with-idle-timer 0.6 t 'rainbow-delimiters-focus-on-maybe)
  (run-with-idle-timer 0.1 t 'rainbow-delimiters-focus-off-maybe))

これはカッコいい!最初にハイライト括弧よりもレインボー区切りを選択した理由は何ですか?
タイラー

@Tylerに特別な理由はありませんが、今でも虹の区切り文字に固執しています(しかし、慣れているからかもしれません)。私はごく最近までハイライト括弧について聞いていませんでした。
PythonNut

4

コードをインデントする場合、本当に適切にインデントする必要があります。Lisp開発者は、適切にインデントされたものがどのように見えるかを期待しています。これは、コードがすべて同じに見えることを意味しません。コードをフォーマットする方法はまだあります。

  • 行はどれくらいの長さですか?
  • 関数/マクロ呼び出しを回線上で分散する方法は?
  • インデントのスペースはいくつ必要ですか?
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

インデントが何らかの形で封じ込めを示していると思います。ただし、コードには含まれません。

デフォルトでは、コードはこのようにインデントされます。他の回答では、Emacsの助けを借りてそれを行う方法を説明しました。

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
    ad-do-it))

letバインドされた変数は同じ垂直列から始まると予想されます。本体letインデントが少なくなることも期待しています。a letをインデントする方法を学びます。これを少し訓練すると、視覚的なパターンになり、認識しやすくなります。

コードの主なビジュアルビルディングブロックでありdefadvicelet*関数呼び出しの束。def名前の中にあるのは、名前、引数リスト、および本文が続く定義であることを示しています。

したがって、私は見たいです

defadvice name arglist
  body

次のlet*ようになります: let*バインディングのリストと本文。

したがって、私は見たい:

let*  list of bindings
  body

より詳細な:

let*   binding1
       binding2
       binding3
  body

関数呼び出しについては、私は見たい:

FUNCTIONNAME ARG1 ARG2 ARG3

または

FUNCTIONNAME ARG1
             ARG2
             ARG3

または

FUNCTIONNAME
  ARG1
  ARG2
  ARG3

どれが使用されるかは、引数の長さに依存します。行を長くしすぎたくないので、各行の引数ごとに構造を増やすことができます。

そのため、これらのパターンを使用してコードを記述する必要があり、読者がコード内でこれらのパターンを簡単に識別できることを確認する必要があります。

括弧はその構造を直接囲む必要があります。

(sin 10)

(sin
  10)

空白の例を避ける:

(  sin 10  )

または

(
   sin
)

または

(sin 10
)

Lispでは、括弧は言語構文関数を持たず、リストのデータ構造(s式)の構文の一部です。したがって、リストを作成し、リストをフォーマットします。リストの内容をフォーマットするには、Lispプログラミング言語に関する知識を使用します。let式は、関数呼び出しとは異なるフォーマットする必要があります。それでも両方ともリストであり、括弧でリストを直接囲む必要があります。行に単一の括弧を付けることが理にかなっているいくつかの例がありますが、まれにしか使用しません。

括弧を使用して、式の境界を見つけやすくします。リストからリストへの移動、リストの編集、リストの切り取りとコピー、リストの置換、リストのインデント/フォーマット、リストの選択などを支援します。


2

インデントの基本的なヘルプについては、TABおよび「CMq」(indent-pp-sexp)を使用してください。「Cm」をバインドするnewline-and-indentと、改行の後にTabキーを押す必要がなくなります。

「CM-left / right / up / down / home / end」を使用してsexpでナビゲートできます。これは非常に便利です。

括弧のバランスを維持することを心配している場合は、smartparensのようなものを使用しますpareditは、最初は拘束ジャケットを着ているように感じることがありますが、もう少し寛容です)。また、sexpレベルでナビゲートおよび編集するための多くのバインディングが付属しています。

最後に、かっこを強調表示するには、オプションをオンにして開始します([オプション]メニュー-> [一致するかっこを強調表示]、またはshow-paren-mode)。 「。


1

他の人が答えとして与えたものに加えて、ここに私の2セントがあります:

  • によって提供される自動インデントemacs-lisp-modeは不可欠です。
  • blink-matching-parennilデフォルトではオン(非)です。そのままにしておきます。
  • を使用してshow-paren-mode、カーソルの前の右括弧に対応する左括弧を強調表示します。
  • 一致する左かっこがどこにあるかを見たい場合は、カーソルの前の右かっこを一時的に削除して再入力します。

私は電気的なペアリングや虹を使用したり、その他の「ヘルプ」を使用したりしません。私にとって、それらはわずらわしいものであり、援助ではありません。

特に、electric-pair-mode私にとっては不便です。しかし、一部の人々はそれを愛しています。そして、私はそれがLisp括弧以外のいくつかの他のペアリングコンテキストで役立つかもしれないと想像します(私はそう思いますが、私はそのようなユースケースについても確信していません)。

あなたに最適なものが見つかります。私が言いたい主なことは次のとおりです。(1)自動インデントは友人であり、ポイントの前の右括弧に一致する左括弧を見つける方法もあります。

特に、自動インデントがあなたのために何をするのかを見ると、あなたは二度と正しい括弧を自分自身で線の上に置きたくないことをすぐに教えられます。他の言語に遍在するこのスタイルのコーディングは、うるさいだけです。

自動インデントのほかにあなたが取得するRETTAB、使用して快適になりますC-M-q。(C-h kin emacs-lisp-modeを使用して、これらのキーの機能を確認します。)


0

すでに虹区切り文字について言及している人がいることに気づきました。これはLispコードを編集するときのお気に入りのモードでもあります。

実際、私は括弧が大好きです。Lispで一番いいのは括弧です。使い方を知っていれば、それを扱うのは楽しいです。

  • あなたが編集括弧は簡単に、例えば、プレスができるよう、悪モードとそのプラグインの邪悪なサラウンドをインストールしci(、すべての内部を削除します()va(「()」で包まれたものを選択し、「()」そのもの。を押し%て、一致した括弧間をジャンプできます

  • flycheckまたはflymakeを使用すると、一致しない括弧にリアルタイムで下線が引かれます


0

ここでの難しさは、特定のコードの前後にコードを追加したいということですsexp。この状況では、sexpは(concat " %" (number-to-string w) "d ")です。if文をその (if (> w 1)前に挿入し、else文を " %2d ")その後に挿入したい。

困難の主な部分はこれを隔離することですsexp。個人的には、以下のコマンドを使用して隔離しますsexp

(defun isolate-sexp () (interactive)
       (save-excursion (forward-sexp) (if (not (eolp-almost))
               (split-line) nil)))
(defun eolp-almost () (interactive)
(save-excursion (while (equal (char-after) ? ) (forward-char 1))
        (if (eolp) t nil )      ))

カーソルをの先頭(concat " %" (number-to-string w) "d ")、つまり最初に配置してから(、上記を適用しますisolate-sexp。あなたが得る

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")
                                  ))
            ad-do-it))

次に、この前後に適切なコードを追加しますsexp、すなわち

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1) (concat " %" (number-to-string w) "d ") " %2d ")
                                  ))
            ad-do-it))

そして出来上がり。この方法で取得されたコードはおそらく美しくありませんが、失われる可能性は小さくなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.