揃えられたキーワードをインデントする方法は?


17

おそらくClojureの影響を受け、データ構造としてプロパティリストをより頻繁に使用します。Emacsはほとんどの場合、このようにインデントします。

`(:token ,token
         :token-quality ,quality)  , 

これは私が好むものです

`(:token ,token
  :token-quality ,quality) . 

だから、誰かがすでにこれに取り組んでいるのだろうか?


3
リストアイテムの動作はハードコーディングされているため、ここで示すように関数を置き換える必要があります
-wasamasa

とても良い、ありがとう。しかし、変数が存在する場合、なぜ再定義するのでしょうか?
ポリツァ

@wasamasa私は...単純にその関数の名前を変更し、私はそれに切り替えるかもしれないと思うFuco1/lisp-indent-functionとやる(add-hook 'emacs-lisp-mode-hook (lambda () (setq-local lisp-indent-function #'Fuco1/lisp-indent-function)))
Kaushalモディ

回答:


4

これはlisp-indent-function、emacs-lispモードを変更することで実現できます。

(add-hook 'emacs-lisp-mode-hook
          (lambda () (setq-local lisp-indent-function #'common-lisp-indent-function)))

lisp-mode.elemacsソースから、

 (defcustom lisp-indent-function 'lisp-indent-function
  "A function to be called by `calculate-lisp-indent'.
It indents the arguments of a Lisp function call.  This function
should accept two arguments: the indent-point, and the
`parse-partial-sexp' state at that position.  One option for this
function is `common-lisp-indent-function'."
  :type 'function
  :group 'lisp)

代替案

以下のよう@wasamasaが質問にコメントで述べたように、@ Fuco1(上github.com)しているデフォルトの修正lisp-indent-function(で始まるキーワードのインデントを修正するために:)。

Emacsはlisp-indent-function、ユーザーがLispモードのインデントに使用する関数を選択するための変数を提供しています。

元の関数定義をオーバーライドする代わりに、独自の関数を作成し、上記の変数をその関数名に割り当てることができます。

この例では、

  • Fuco1 modded関数Fuco1/lisp-indent-functionemacsセットアップのようなものとして保存します
  • のインデントにその関数を使用しますemacs-lisp-mode
(add-hook 'emacs-lisp-mode-hook
          (lambda () (setq-local lisp-indent-function #'Fuco1/lisp-indent-function)))

参照

githubに参照されているソースが失われた場合、modded関数は以下に貼り付けられます。

;; https://github.com/Fuco1/.emacs.d/blob/af82072196564fa57726bdbabf97f1d35c43b7f7/site-lisp/redef.el#L20-L94
(defun Fuco1/lisp-indent-function (indent-point state)
  "This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine
if the arguments of a Lisp function call should be indented specially.

INDENT-POINT is the position at which the line being indented begins.
Point is located at the point to indent under (for default indentation);
STATE is the `parse-partial-sexp' state for that position.

If the current line is in a call to a Lisp function that has a non-nil
property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent.  The property value can be:

* `defun', meaning indent `defun'-style
  \(this is also the case if there is no property and the function
  has a name that begins with \"def\", and three or more arguments);

* an integer N, meaning indent the first N arguments specially
  (like ordinary function arguments), and then indent any further
  arguments like a body;

* a function to call that returns the indentation (or nil).
  `lisp-indent-function' calls this function with the same two arguments
  that it itself received.

This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
  (let ((normal-indent (current-column))
        (orig-point (point)))
    (goto-char (1+ (elt state 1)))
    (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
    (cond
     ;; car of form doesn't seem to be a symbol, or is a keyword
     ((and (elt state 2)
           (or (not (looking-at "\\sw\\|\\s_"))
               (looking-at ":")))
      (if (not (> (save-excursion (forward-line 1) (point))
                  calculate-lisp-indent-last-sexp))
          (progn (goto-char calculate-lisp-indent-last-sexp)
                 (beginning-of-line)
                 (parse-partial-sexp (point)
                                     calculate-lisp-indent-last-sexp 0 t)))
      ;; Indent under the list or under the first sexp on the same
      ;; line as calculate-lisp-indent-last-sexp.  Note that first
      ;; thing on that line has to be complete sexp since we are
      ;; inside the innermost containing sexp.
      (backward-prefix-chars)
      (current-column))
     ((and (save-excursion
             (goto-char indent-point)
             (skip-syntax-forward " ")
             (not (looking-at ":")))
           (save-excursion
             (goto-char orig-point)
             (looking-at ":")))
      (save-excursion
        (goto-char (+ 2 (elt state 1)))
        (current-column)))
     (t
      (let ((function (buffer-substring (point)
                                        (progn (forward-sexp 1) (point))))
            method)
        (setq method (or (function-get (intern-soft function)
                                       'lisp-indent-function)
                         (get (intern-soft function) 'lisp-indent-hook)))
        (cond ((or (eq method 'defun)
                   (and (null method)
                        (> (length function) 3)
                        (string-match "\\`def" function)))
               (lisp-indent-defform state indent-point))
              ((integerp method)
               (lisp-indent-specform method state
                                     indent-point normal-indent))
              (method
               (funcall method indent-point state))))))))

完全なインデント関数を別の言語用に変更するのは少し極端ではありませんか?Guile私は#:keywordsが私が期待する方法で整列されていないという同じ問題を抱えていますが、Guileインデント関数をCommon Lisp用に作られた関数に置き換えません。
レカド

1
@rekado同意します。しかし、これは特別なケースのようです。(ヒドラの定義で)揃えられていないキーワードと同じ刺激に直面し、そこにある解決策を探していました。私がしようとしてしまったこの emacswikiからの提案をし、それは私の一部となっていconfigコンemacsの今約一ヶ月のために。私はあまりにもキーワードを揃えるためのきれいな実装を見たいlisp-indent-functionです。
カウシャルモディ

興味深い...これ(setq lisp-backquote-indentation nil)は、元の質問のように、逆引用符で囲まれたリストにも必要です。
ポリツァ

@politza申し訳ありませんが、急いでそれらのバッククォートを、マークダウンのコードブロックとしてテキストをフォーマットする構文として読み間違えました。
カウシャルモディ

2

calculate-lisp-indentの概要

より良い解決策は、関数をオーバーライドすることcalculate-lisp-indentです。簡単に言うcalculate-lisp-indentと、ポイントの行をインデントする列を返す関数です。この関数は、lisp-indent-function各行をインデントする量を通知します。(詳細についてはredditに関する私の投稿も参照してください)。

他の回答との比較

この答えがFuco1の修正された関数を使用するよりも優れている点は、(1)calculate-lisp-indent間違ったインデントが返された後にクリーンアップするのではなく、問題の根本を修正するcalculate-lisp-indentことです。明示的に引用符で囲まれているか、または引用符で囲まれているかに関係なく、'および `)で動作します。また、任意にネストされた引用符と逆引用符でも機能します。

この回答は以上交換している利点lisp-indent-functioncommon-lisp-indent-function機能は、それが他のelispのインデントをめちゃくちゃの副作用を持っていないです。Elispとcommon-lispのインデントは異なります。

使い方

この条件(in calculate-lisp-indent)は、sexpが関数のようにインデントされるかどうかを決定するものです。else節に該当するものは、関数のようにインデントされています。if節に該当するものは、通常(現在の要素の下)でインデントされます。関数としてではなくデータとして引用リストをインデントするには、条件付き述語でリストが引用されている場合のチェックをさらに追加する必要があります。

(if (= (point) calculate-lisp-indent-last-sexp)
    ;; Containing sexp has nothing before this line
    ;; except the first element.  Indent under that element.
    nil
  ;; Skip the first element, find start of second (the first
  ;; argument of the function call) and indent under.
  (progn (forward-sexp 1)
         (parse-partial-sexp (point)
                             calculate-lisp-indent-last-sexp
                             0 t)))

このコードは、インデントされているsexpの開いた括弧をチェックします。複数のsexpでsexpを使用する場合は、それらすべてをチェックします。引用符で囲まれた、または逆引用符で囲まれたsexpsが見つかった場合、tを返します。

(let* ((positions (elt state 9))
       (last (car (last positions)))
       (rest (nreverse (butlast positions)))
       (any-quoted-p nil)
       (point nil))
  (or
   (when-let (char last)
     (or (char-equal char ?')
         (char-equal char ?`)))
   (while (and rest (not any-quoted-p))
     (setq point (pop rest))
     (setq any-quoted-p
           (or
            (when-let (char point)
              (or (char-equal char ?')
                  (char-equal char ?`)))
            (save-excursion
              (goto-char (1+ point))
              (looking-at-p "\\(?:back\\)?quote[\t\n\f\s]+(")))))))

ボーナス

キーワードで始まるリストを引用符で囲まなくてもデータとしてインデントする場合は、これを別のチェックとして条件付き述語に追加します。これは、defhydraのように、便宜のためにリストが引用されていないマクロに有用です。

(when-let (char-after (char-after (1+ containing-sexp)))
  (char-equal char-after ?:))

以下に投稿した完全なコードスニペットは、あなたが言及したケースなどで動作します。試してみてください!


;; Your example
`(:token ,token
  :token-quality ,quality)

;; Other cool examples
(quote (hi im gosu
        the best vayne player))

'(i am the phantom of
  the opera)

'((angel of music
   hide no longer))

(backquote (past the point
            no return
            ... the final chapter))

`(fee fi fo
  fum)

;; should indent it like a function.
(iamafunction arg1
              arg2
              arg3)

これがどのように機能するかについてのより詳細な説明については、redditに関する私の投稿を参照しください。

完全なコードスニペット

完全なコードスニペットを次に示します。

(advice-add #'calculate-lisp-indent :override #'void~calculate-lisp-indent)

(defun void~calculate-lisp-indent (&optional parse-start)
  "Add better indentation for quoted and backquoted lists."
  ;; This line because `calculate-lisp-indent-last-sexp` was defined with `defvar` 
  ;; with it's value ommited, marking it special and only defining it locally. So  
  ;; if you don't have this, you'll get a void variable error.
  (defvar calculate-lisp-indent-last-sexp)
  (save-excursion
    (beginning-of-line)
    (let ((indent-point (point))
          state
          ;; setting this to a number inhibits calling hook
          (desired-indent nil)
          (retry t)
          calculate-lisp-indent-last-sexp containing-sexp)
      (cond ((or (markerp parse-start) (integerp parse-start))
             (goto-char parse-start))
            ((null parse-start) (beginning-of-defun))
            (t (setq state parse-start)))
      (unless state
        ;; Find outermost containing sexp
        (while (< (point) indent-point)
          (setq state (parse-partial-sexp (point) indent-point 0))))
      ;; Find innermost containing sexp
      (while (and retry
                  state
                  (> (elt state 0) 0))
        (setq retry nil)
        (setq calculate-lisp-indent-last-sexp (elt state 2))
        (setq containing-sexp (elt state 1))
        ;; Position following last unclosed open.
        (goto-char (1+ containing-sexp))
        ;; Is there a complete sexp since then?
        (if (and calculate-lisp-indent-last-sexp
                 (> calculate-lisp-indent-last-sexp (point)))
            ;; Yes, but is there a containing sexp after that?
            (let ((peek (parse-partial-sexp calculate-lisp-indent-last-sexp
                                            indent-point 0)))
              (if (setq retry (car (cdr peek))) (setq state peek)))))
      (if retry
          nil
        ;; Innermost containing sexp found
        (goto-char (1+ containing-sexp))
        (if (not calculate-lisp-indent-last-sexp)
            ;; indent-point immediately follows open paren.
            ;; Don't call hook.
            (setq desired-indent (current-column))
          ;; Find the start of first element of containing sexp.
          (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
          (cond ((looking-at "\\s(")
                 ;; First element of containing sexp is a list.
                 ;; Indent under that list.
                 )
                ((> (save-excursion (forward-line 1) (point))
                    calculate-lisp-indent-last-sexp)
                 ;; This is the first line to start within the containing sexp.
                 ;; It's almost certainly a function call.
                 (if (or
                      ;; Containing sexp has nothing before this line
                      ;; except the first element. Indent under that element.
                      (= (point) calculate-lisp-indent-last-sexp)

                      ;; First sexp after `containing-sexp' is a keyword. This
                      ;; condition is more debatable. It's so that I can have
                      ;; unquoted plists in macros. It assumes that you won't
                      ;; make a function whose name is a keyword.
                      ;; (when-let (char-after (char-after (1+ containing-sexp)))
                      ;;   (char-equal char-after ?:))

                      ;; Check for quotes or backquotes around.
                      (let* ((positions (elt state 9))
                             (last (car (last positions)))
                             (rest (reverse (butlast positions)))
                             (any-quoted-p nil)
                             (point nil))
                        (or
                         (when-let (char (char-before last))
                           (or (char-equal char ?')
                               (char-equal char ?`)))
                         (progn
                           (while (and rest (not any-quoted-p))
                             (setq point (pop rest))
                             (setq any-quoted-p
                                   (or
                                    (when-let (char (char-before point))
                                      (or (char-equal char ?')
                                          (char-equal char ?`)))
                                    (save-excursion
                                      (goto-char (1+ point))
                                      (looking-at-p
                                       "\\(?:back\\)?quote[\t\n\f\s]+(")))))
                           any-quoted-p))))
                     ;; Containing sexp has nothing before this line
                     ;; except the first element.  Indent under that element.
                     nil
                   ;; Skip the first element, find start of second (the first
                   ;; argument of the function call) and indent under.
                   (progn (forward-sexp 1)
                          (parse-partial-sexp (point)
                                              calculate-lisp-indent-last-sexp
                                              0 t)))
                 (backward-prefix-chars))
                (t
                 ;; Indent beneath first sexp on same line as
                 ;; `calculate-lisp-indent-last-sexp'.  Again, it's
                 ;; almost certainly a function call.
                 (goto-char calculate-lisp-indent-last-sexp)
                 (beginning-of-line)
                 (parse-partial-sexp (point) calculate-lisp-indent-last-sexp
                                     0 t)
                 (backward-prefix-chars)))))
      ;; Point is at the point to indent under unless we are inside a string.
      ;; Call indentation hook except when overridden by lisp-indent-offset
      ;; or if the desired indentation has already been computed.
      (let ((normal-indent (current-column)))
        (cond ((elt state 3)
               ;; Inside a string, don't change indentation.
               nil)
              ((and (integerp lisp-indent-offset) containing-sexp)
               ;; Indent by constant offset
               (goto-char containing-sexp)
               (+ (current-column) lisp-indent-offset))
              ;; in this case calculate-lisp-indent-last-sexp is not nil
              (calculate-lisp-indent-last-sexp
               (or
                ;; try to align the parameters of a known function
                (and lisp-indent-function
                     (not retry)
                     (funcall lisp-indent-function indent-point state))
                ;; If the function has no special alignment
                ;; or it does not apply to this argument,
                ;; try to align a constant-symbol under the last
                ;; preceding constant symbol, if there is such one of
                ;; the last 2 preceding symbols, in the previous
                ;; uncommented line.
                (and (save-excursion
                       (goto-char indent-point)
                       (skip-chars-forward " \t")
                       (looking-at ":"))
                     ;; The last sexp may not be at the indentation
                     ;; where it begins, so find that one, instead.
                     (save-excursion
                       (goto-char calculate-lisp-indent-last-sexp)
                       ;; Handle prefix characters and whitespace
                       ;; following an open paren.  (Bug#1012)
                       (backward-prefix-chars)
                       (while (not (or (looking-back "^[ \t]*\\|([ \t]+"
                                                     (line-beginning-position))
                                       (and containing-sexp
                                            (>= (1+ containing-sexp) (point)))))
                         (forward-sexp -1)
                         (backward-prefix-chars))
                       (setq calculate-lisp-indent-last-sexp (point)))
                     (> calculate-lisp-indent-last-sexp
                        (save-excursion
                          (goto-char (1+ containing-sexp))
                          (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
                          (point)))
                     (let ((parse-sexp-ignore-comments t)
                           indent)
                       (goto-char calculate-lisp-indent-last-sexp)
                       (or (and (looking-at ":")
                                (setq indent (current-column)))
                           (and (< (line-beginning-position)
                                   (prog2 (backward-sexp) (point)))
                                (looking-at ":")
                                (setq indent (current-column))))
                       indent))
                ;; another symbols or constants not preceded by a constant
                ;; as defined above.
                normal-indent))
              ;; in this case calculate-lisp-indent-last-sexp is nil
              (desired-indent)
              (t
               normal-indent))))))

最終ノート

emacsが引用リストと非引用リストを関数としてインデントするのを止める方法として、この質問がより一般化されることは注目に値する。


ありがとうございました!よく書かれた、明確で役立つ。
GaryO

1

kaushalmodiの答えのはるかにハッカー的な代替案については、Guile Schemeでキーワードの配置を修正するlisp-indent-functionためにMark H. Weaverが行ったものと同様のものを上書きできますscheme-indent-function

http://netris.org/~mhw/scheme-indent-function.elからコードをコピーしました。唯一の変更点は、新しいcond句を追加することです。lisp-indent-functionこの関数をそのまま使用する代わりに、現在のコードを使用することもできます。

(インデント関数がこのような小さな変更を単純化するためにこれ以上フックを公開しないのは残念です。)

(defun scheme-indent-function (indent-point state)
  "Scheme mode function for the value of the variable `lisp-indent-function'.
This behaves like the function `lisp-indent-function', except that:

i) it checks for a non-nil value of the property `scheme-indent-function'
\(or the deprecated `scheme-indent-hook'), rather than `lisp-indent-function'.

ii) if that property specifies a function, it is called with three
arguments (not two), the third argument being the default (i.e., current)
indentation."
  (let ((normal-indent (current-column)))
    (goto-char (1+ (elt state 1)))
    (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
    (if (and (elt state 2)
             (not (looking-at "\\sw\\|\\s_")))
        ;; car of form doesn't seem to be a symbol
        (progn
          (if (not (> (save-excursion (forward-line 1) (point))
                      calculate-lisp-indent-last-sexp))
              (progn (goto-char calculate-lisp-indent-last-sexp)
                     (beginning-of-line)
                     (parse-partial-sexp (point)
                     calculate-lisp-indent-last-sexp 0 t)))
          ;; Indent under the list or under the first sexp on the same
          ;; line as calculate-lisp-indent-last-sexp.  Note that first
          ;; thing on that line has to be complete sexp since we are
          ;; inside the innermost containing sexp.
          (backward-prefix-chars)
          (current-column))
      (let ((function (buffer-substring (point)
                    (progn (forward-sexp 1) (point))))
        method)
    (setq method (or (get (intern-soft function) 'scheme-indent-function)
             (get (intern-soft function) 'scheme-indent-hook)))
    (cond ((or (eq method 'defun)
           (and (null method)
            (> (length function) 3)
            (string-match "\\`def" function)))
           (lisp-indent-defform state indent-point))
              ;; This next cond clause is the only change -mhw
          ((and (null method)
                    (> (length function) 1)
                    ; The '#' in '#:' seems to get lost, not sure why
                    (string-match "\\`:" function))
               (let ((lisp-body-indent 1))
                 (lisp-indent-defform state indent-point)))
          ((integerp method)
           (lisp-indent-specform method state
                     indent-point normal-indent))
          (method
        (funcall method state indent-point normal-indent)))))))

なぜそれを上書きし、変数を使用しないのですlisp-indent-functionか?また、機能がないようですemacs-lisp-indent-function
ポリツァ

さて、lisp-indent-functionデフォルトでインデント機能が含まれています。この変数への割り当ては、emacs-lispモードのデフォルトのインデント関数を上書きすることと同じです。(あなたは正しい、特別なものはありませんemacs-lisp-indent-function、それだけlisp-indent-functionです。)
rekado

しかし、それは「ハッキー」ではありません。
ポリツァ

1

lisp-indent-function私のパッケージel-patchを使用して、将来を見越した方法でオーバーライドできます。

(el-patch-defun lisp-indent-function (indent-point state)
  "This function is the normal value of the variable `lisp-indent-function'.
The function `calculate-lisp-indent' calls this to determine
if the arguments of a Lisp function call should be indented specially.
INDENT-POINT is the position at which the line being indented begins.
Point is located at the point to indent under (for default indentation);
STATE is the `parse-partial-sexp' state for that position.
If the current line is in a call to a Lisp function that has a non-nil
property `lisp-indent-function' (or the deprecated `lisp-indent-hook'),
it specifies how to indent.  The property value can be:
* `defun', meaning indent `defun'-style
  (this is also the case if there is no property and the function
  has a name that begins with \"def\", and three or more arguments);
* an integer N, meaning indent the first N arguments specially
  (like ordinary function arguments), and then indent any further
  arguments like a body;
* a function to call that returns the indentation (or nil).
  `lisp-indent-function' calls this function with the same two arguments
  that it itself received.
This function returns either the indentation to use, or nil if the
Lisp function does not specify a special indentation."
  (el-patch-let (($cond (and (elt state 2)
                             (el-patch-wrap 1 1
                               (or (not (looking-at "\\sw\\|\\s_"))
                                   (looking-at ":")))))
                 ($then (progn
                          (if (not (> (save-excursion (forward-line 1) (point))
                                      calculate-lisp-indent-last-sexp))
                              (progn (goto-char calculate-lisp-indent-last-sexp)
                                     (beginning-of-line)
                                     (parse-partial-sexp (point)
                                                         calculate-lisp-indent-last-sexp 0 t)))
                          ;; Indent under the list or under the first sexp on the same
                          ;; line as calculate-lisp-indent-last-sexp.  Note that first
                          ;; thing on that line has to be complete sexp since we are
                          ;; inside the innermost containing sexp.
                          (backward-prefix-chars)
                          (current-column)))
                 ($else (let ((function (buffer-substring (point)
                                                          (progn (forward-sexp 1) (point))))
                              method)
                          (setq method (or (function-get (intern-soft function)
                                                         'lisp-indent-function)
                                           (get (intern-soft function) 'lisp-indent-hook)))
                          (cond ((or (eq method 'defun)
                                     (and (null method)
                                          (> (length function) 3)
                                          (string-match "\\`def" function)))
                                 (lisp-indent-defform state indent-point))
                                ((integerp method)
                                 (lisp-indent-specform method state
                                                       indent-point normal-indent))
                                (method
                                 (funcall method indent-point state))))))
    (let ((normal-indent (current-column))
          (el-patch-add
            (orig-point (point))))
      (goto-char (1+ (elt state 1)))
      (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
      (el-patch-swap
        (if $cond
            ;; car of form doesn't seem to be a symbol
            $then
          $else)
        (cond
         ;; car of form doesn't seem to be a symbol, or is a keyword
         ($cond $then)
         ((and (save-excursion
                 (goto-char indent-point)
                 (skip-syntax-forward " ")
                 (not (looking-at ":")))
               (save-excursion
                 (goto-char orig-point)
                 (looking-at ":")))
          (save-excursion
            (goto-char (+ 2 (elt state 1)))
            (current-column)))
         (t $else))))))

これで問題が解決します。コンテキストでそれを参照してください

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