coq-modeで行番号を表示する方法

WARNING

この記事ChatGPTによって中国語から翻訳されたものでいくつかのりがまれているかもしれません。不正確部分があればご了承ください

行番号Line Number表示、問題になるべきではないようにえますvscodeリポジトリには、行番号表示をどのように簡単オフにするかをねるissueがありますVimユーザーにとってはset :number一言言えば行番号表示されます

そうEmacsはそういう特別エディタなのです。私のようなたまに使ユーザーにとって、行番号表示実際問題になります

問題

使用しているのはEmacsディストリビューションあるいは成熟した設定ファイルであるSpacemacsですSpacemacs公式ドキュメントによれば、行番号表示するには.spacemacs変数設定するだけでよいようです

1(setq-default dotspacemacs-line-numbers t)

このdotspacemacs-line-numberst設定するという意味です

またcoq言語サポートするために、私coqレイヤーロードしましたしかしcoqファイルくと、驚くことにこの時点ではcoqソースコードには行番号表示されません

通常直感ではt設定するとすべての状況行番号表示されるはずですではなぜ行番号表示されないのでしょうか

一時的解決策

この問題解決するのは、原因理解するよりも簡単です

検索してみるとspc t n 1現在バッファ行番号表示できることがわかりましたこれはかに問題解決しますEmacs wikiによればこのショートカット実際にはEmacs 26.1以降導入された行番号コマンドdisplay-line-numbers-modeしています

Emacsコマンドm-xspc spc実行できます。実際にはコマンドEmacs Lisp関数Programming Elispドキュメントによれば、関数定義(interactive)がある場合、それはコマンドとしてすことができます

らかにSpacemacsもこの関数使って行番号設定っていますなぜSpacemacs .v ファイル行番号けなかったのかはわかりませんが、自分くことができます

coq レイヤー実際には coq-mode 使用していますcoq-mode EmacsメジャーモードMajor Modeつでcoq言語サポート提供、多くのショートカットキー定義しています

Emacs Hooks というメカニズムサポートしており、特定モードロードされたときに一連操作実行することができますSpacemacs coq-mode 行番号けないのであればこのメカニズム使って手動 display-line-numbers-mode 実行します

1(defun dotspacemacs/user-config ()
2  "Configuration for user code:
3This function is called at the very end of Spacemacs startup, after layer
4configuration.
5Put your configuration code here, except for variables that should be set
6before packages are loaded."
7  (add-hook 'coq-mode-hook (lambda () (display-line-numbers-mode 1)))
8)

ここでの dotspacemacs/user-config Spacemacs設定ファイル一部ここで一部カスタム操作実行することができます

これで問題解決しました。実Spacemacsドキュメントでは :enabled-for-modes というオプション紹介していますこのオプション coq-mode 追加すると、問題直接解決しますしかし、私問題原因完全理解するまでこのオプション存在づきませんでした

1(setq-default dotspacemacs-line-numbers
2  '(:enabled-for-modes coq-mode))

なぜ

最初づいたのは dotspacemacs-line-numbers コメントです

1;; Control line numbers activation.
2;; If set to `t', `relative' or `visual' then line numbers are enabled in all
3;; `prog-mode' and `text-mode' derivatives. If set to `relative', line
4;; numbers are relative. If set to `visual', line numbers are also relative,
5;; but only visual lines are counted. For example, folded lines will not be
6;; counted and wrapped lines are counted as multiple lines.
7;; This variable can also be set to a property list for finer control:
8;; (default nil)

このコメントではこの変数 t 設定するとデフォルト prog-mode text-mode 行番号有効になるとべていますこれらのモード Basic Major Mode ばれています。自然推測coq-mode はその一部ではないということです

coq ファイルm-x describe-mode 実行すると、次のように表示されます

1Coq mode defined in ‘coq-mode.el’:
2Major mode for Coq scripts.

coq-mode.el ソースコードてみると、秘密 182 行目にあります

1(defun coq--parent-mode ()
2  (if coq-use-pg (proof-mode) (prog-mode)))
3
4;;;###autoload
5(define-derived-mode coq-mode coq--parent-mode "Coq"
6  "Major mode for Coq scripts.

この coq--parent-mode らかに coq-mode モードparent modeEmacsモードにも継承ツリーがあり、一つのモードモード継承しますこのツリールート確定していませんがいくつかのケースがあります

  • 自分自身
  • Fundamental Mode
  • Basic Major Mode

環境では coq-use-pg t そのモード proof-mode ですではproof-mode モードでしょうか

StackOverflow ある質問では、関連するコード提供されています

1(defun derived-modes (mode)
2  "Return a list of the ancestor modes that MODE is derived from."
3  (let ((modes   ())
4        (parent  nil))
5    (while (setq parent (get mode 'derived-mode-parent))
6      (push parent modes)
7      (setq mode parent))
8    (setq modes  (nreverse modes))))

この関数使うといくつかの「通常」モード継承パスることができます。例えば、先ほどした proof-mode:

1ELISP> (derived-modes 'proof-mode)
2(prog-mode)

はいproof-mode モード prog-mode ですもしほどのドキュメントえているならprog-mode dotspacemacs-line-numbers t 設定されているときにも行番号表示するはずです

では、一体何間違っているのでしょうかこの関数 'coq-mode 入力してみましょう

1ELISP> (derived-modes 'coq-mode)
2(coq--parent-mode)

出力 (coq--parent-mode) これはモードシンボルであり、評価後モードではありませんしたがって(get 'coq-mode 'derived-parent-mode) という構文上シンボルしか取得できず、実際モード取得できませんdefine-derived-mode ソースコード調べると(get mode 'derived-mode-parent) 取得するのはこの設定するモードであることがわかります

1(put ',child 'derived-mode-parent ',parent)

ではSpacemacsモードをどのように取得しているのでしょうかSpacemacs spacemacs/layers/+spacemacs/spacemacs-defaults/funcs.el spacemacs//linum-enabled-for-current-major-mode 関数、現在モード行番号表示有効になっているかどうかをチェックします

1(defun spacemacs//linum-enabled-for-current-major-mode ()
2  "Return non-nil if line number is enabled for current major-mode."
3  (let* ((disabled-for-modes
4          (spacemacs/mplist-get-values dotspacemacs-line-numbers
5                                       :disabled-for-modes))
6         (user-enabled-for-modes
7          (spacemacs/mplist-get-values dotspacemacs-line-numbers
8                                       :enabled-for-modes))
9         ;; default `enabled-for-modes' to '(prog-mode text-mode), because it is
10         ;; a more sensible default than enabling in all buffers - including
11         ;; Magit buffers, terminal buffers, etc. But don't include prog-mode or
12         ;; text-mode if they're explicitly disabled by user
13         (enabled-for-modes (or user-enabled-for-modes
14                                (seq-difference '(prog-mode text-mode)
15                                                disabled-for-modes
16                                                #'eq)))
17         (enabled-for-parent (or (and (equal enabled-for-modes '(all)) 'all)
18                                 (apply #'derived-mode-p enabled-for-modes)))
19         (disabled-for-parent (apply #'derived-mode-p disabled-for-modes)))
20    (or
21     ;; special case 'all: enable for any mode that isn't specifically disabled
22     (and (eq enabled-for-parent 'all) (not disabled-for-parent))
23     ;; current mode or a parent is in :enabled-for-modes, and there isn't a
24     ;; more specific parent (or the mode itself) in :disabled-for-modes
25     (and enabled-for-parent
26          (or (not disabled-for-parent)
27              ;; handles the case where current major-mode has a parent both in
28              ;; :enabled-for-modes and in :disabled-for-modes. Return non-nil
29              ;; if enabled-for-parent is the more specific parent (IOW derives
30              ;; from disabled-for-parent)
31              (spacemacs/derived-mode-p enabled-for-parent disabled-for-parent)))
32     ;; current mode (or parent) not explicitly disabled
33     (and (null user-enabled-for-modes)
34          enabled-for-parent            ; mode is one of default allowed modes
35          disabled-for-modes
36          (not disabled-for-parent)))))

てのそれは derived-mode-p 関数使って現在バッファ major mode 予設 enabled-for-modes モードであるかどうかをわせていますdervied-mode-p Emacs提供する関数ドキュメントによれば、現在バッファモード modes モードであれば(derived-mode-p modes) nil します

derived-mode-p ソースコードをさらにチェックすると、問題根源がついにらかになります

1(defun provided-mode-derived-p (mode &rest modes)
2  "Non-nil if MODE is derived from one of MODES or their aliases.
3Uses the `derived-mode-parent' property of the symbol to trace backwards.
4If you just want to check `major-mode', use `derived-mode-p'."
5  (while
6      (and
7       (not (memq mode modes))
8       (let* ((parent (get mode 'derived-mode-parent))
9              (parentfn (symbol-function parent)))
10         (setq mode (if (and parentfn (symbolp parentfn)) parentfn parent)))))
11  mode)
12
13(defun derived-mode-p (&rest modes)
14  "Non-nil if the current major mode is derived from one of MODES.
15Uses the `derived-mode-parent' property of the symbol to trace backwards."
16  (apply #'provided-mode-derived-p major-mode modes))

dervied-mode-p もまた (get mode 'derived-mode-parent) 使って特定モードモード取得していますがこれは coq-mode しては coq--parent-mode ここで継承チェーン断絶します

Emacsモード定義最終的には関数定義しますしかしもし 'parent しいモード評価できなければEmacsモードする予設関数期待した結果られません。明らかにcoq-mode coq--parent-mode をこのように定義すべきではありませんではcoq--parent-mode はどのように定義すべきなのでしょうか

最終的解決策

Emacs Lispグローバル変数グローバル関数なりこれはSchemeとはいになりますもし coq--parent-mode 直接変数として定義するとcoq-mode ロードできなくなります

1(defvar coq--parent-mode
2  (if coq-use-pg 'proof-mode 'prog-mode))

幸運なことにEmacs Lispには defalias という関数がありこれを使って coq--parent-mode エイリアスとして定義することができます

1(defalias 'coq--parent-mode
2  (if coq-use-pg 'proof-mode 'prog-mode))

上記修正だけでSpacemacs coq-mode prog-mode から派生していることを自動的検出、行番号表示開始できます。私はすでに PR 提出、主分支マージしました