Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15926] ファイルの概要

このコミットは、EmacsのGo言語用メジャーモードであるgo-mode.elの定義を改善し、Emacs 24.1以降で導入されたprog-modeを親モードとして利用するように変更するものです。これにより、go-modeprog-modeが提供する共通のプログラミングモード機能群を継承し、コードの重複を避け、将来的な互換性と機能拡張性を向上させます。古いEmacsバージョンやXEmacsとの互換性を保つためのフォールバックロジックも含まれています。

コミット

commit 4ad1a87f8573b355b814589c63b75a7657df4069
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Mon Mar 25 08:58:13 2013 -0700

    misc/emacs: Derive mode from prog-mode if possible
    
    R=golang-dev, bradfitz
    CC=adonovan, cw, golang-dev, patrick.allen.higgins
    https://golang.org/cl/7956044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4ad1a87f8573b355b814589c63b75a7657df4069

元コミット内容

misc/emacs: Derive mode from prog-mode if possible

R=golang-dev, bradfitz
CC=adonovan, cw, golang-dev, patrick.allen.higgins
https://golang.org/cl/7956044

変更の背景

この変更の背景には、Emacsのメジャーモードの設計思想の進化があります。Emacs 24.1でprog-modeが導入される以前は、各プログラミング言語のメジャーモードは、通常fundamental-mode(基本的なテキスト編集モード)から派生し、それぞれのモードでプログラミングに特化した機能(コメントの扱い、インデント、シンタックスハイライトなど)を個別に実装する必要がありました。

しかし、多くのプログラミング言語モードで共通して必要とされる機能が多く存在するため、これらの共通機能を一元的に提供するprog-modeが設計されました。prog-modeは、プログラミング言語モードの共通の親として機能し、例えばコメントの自動挿入、括弧の対応、基本的なインデントルールなど、多くのプログラミングモードで共有されるべき振る舞いを定義します。

このコミットは、go-modefundamental-modeから直接派生するのではなく、より適切な親であるprog-modeから派生するように変更することで、以下の利点を得ることを目的としています。

  1. コードの簡素化と再利用: prog-modeが提供する共通機能をgo-modeが自動的に継承できるようになり、go-mode.el内でそれらの機能を再実装する必要がなくなります。
  2. 一貫性の向上: Emacsの他のプログラミングモードとの間で、基本的な振る舞いの一貫性が向上します。
  3. 将来的な互換性: prog-modeの改善や新機能が、go-modeにも自動的に適用されるようになります。
  4. 保守性の向上: 共通機能のバグ修正や改善がprog-modeで行われれば、それを継承するすべてのモードに恩恵がもたらされます。

ただし、この変更はEmacs 24.1以降のバージョンに依存するため、古いEmacsバージョンやXEmacsのようなprog-modeをサポートしない環境との互換性を維持するための考慮も必要とされました。

前提知識の解説

このコミットを理解するためには、以下のEmacs LispおよびEmacsのモードに関する基本的な知識が必要です。

Emacs Lisp (Elisp)

Emacsの拡張言語であり、Emacs自体がEmacs Lispで書かれています。Emacsの動作をカスタマイズしたり、新しい機能を追加したりするために使用されます。

メジャーモード (Major Mode)

Emacsのバッファ(ファイルの内容を表示する領域)は、常に一つのメジャーモードに関連付けられています。メジャーモードは、特定の種類のテキスト(例: プログラミング言語のソースコード、プレーンテキスト、Markdownファイルなど)を編集するための主要な機能セットを提供します。これには、シンタックスハイライト、インデントルール、特定のコマンドなどが含まれます。例えば、Go言語のソースコードを編集する際にはgo-modeが、Pythonのソースコードを編集する際にはpython-modeが使用されます。

define-derived-mode

Emacs Lispで新しいメジャーモードを定義するためのマクロです。このマクロは、既存のモード(親モード)から派生して新しいモードを作成する際に使用されます。親モードの機能や設定を継承しつつ、新しいモード固有のカスタマイズを追加できます。 構文は以下のようになります。 (define-derived-mode NEW-MODE PARENT-MODE NAME DOCSTRING BODY...)

  • NEW-MODE: 新しく定義するモードの名前(シンボル)。
  • PARENT-MODE: 派生元の親モードの名前(シンボル)。
  • NAME: モードラインに表示されるモードの短い名前(文字列)。
  • DOCSTRING: モードの目的を説明するドキュメント文字列。
  • BODY: モードがアクティブになったときに実行されるEmacs Lispコード。

fundamental-mode

Emacsで最も基本的なメジャーモードです。特別な機能やシンタックスハイライト、インデントルールなどは提供せず、純粋なテキスト編集機能のみを提供します。多くのプログラミングモードは、prog-modeが導入される前はfundamental-modeから派生していました。

prog-mode

Emacs 24.1で導入された、プログラミング言語モードのための汎用的なメジャーモードです。fundamental-modeの上に構築され、すべてのプログラミング言語モードに共通する基本的な機能(コメントの扱い、基本的なインデント、括弧の対応など)を提供します。これにより、個々の言語モードはprog-modeから派生することで、これらの共通機能を再実装する手間を省き、言語固有の機能に集中できるようになります。

fboundp

Emacs Lispの関数で、引数として与えられたシンボルが関数として定義されているかどうかをチェックします。このコミットでは、prog-modeが現在のEmacs環境で利用可能かどうか(関数として定義されているか)を判断するために使用されています。

defalias

Emacs Lispの関数で、あるシンボルを別のシンボルのエイリアス(別名)として定義します。これにより、既存の関数や変数を新しい名前で参照できるようになります。このコミットのコメントでは、XEmacsでの互換性の問題がなければdefaliasを使いたかったと述べられています。

XEmacs

Emacsの派生版の一つで、GNU Emacsとは異なる開発ラインを持っています。一部の機能やAPIの互換性がない場合があります。このコミットでは、XEmacsがprog-modeをサポートしていない可能性や、defaliasの挙動の違いが考慮されています。

技術的詳細

このコミットの技術的な核心は、go-mode.eldefine-derived-modeを使用してgo-modeを定義する際に、その親モードをfundamental-modeからprog-modeに変更することにあります。しかし、この変更はprog-modeが利用可能なEmacs 24.1以降の環境でのみ有効である必要があります。古いEmacsバージョンやXEmacsではprog-modeが存在しないため、エラーを避けるためのフォールバックメカニズムが必要です。

コミットは以下のロジックでこの問題を解決しています。

  1. prog-modeの存在チェック:

    (if (not (fboundp 'prog-mode))
        (define-derived-mode prog-mode fundamental-mode "" ""))
    

    このコードは、prog-modeという関数(またはモード)が現在のEmacs環境で定義されているかどうかをfboundp関数を使ってチェックします。

    • もしprog-modeが定義されていない場合(fboundp 'prog-modenilを返す場合)、prog-modefundamental-modeから派生した空のモードとしてdefine-derived-modeを使って定義します。
    • この「ダミーのprog-mode」は、実質的にはfundamental-modeと同じ振る舞いをしますが、go-modeprog-modeを親として定義する際に、シンボルprog-modeが存在しないことによるエラーを防ぎます。これにより、古いEmacsバージョンやXEmacsでもgo-modeが正常にロードされるようになります。
  2. go-modeの親モード変更:

    -(define-derived-mode go-mode fundamental-mode "Go"
    +(define-derived-mode go-mode prog-mode "Go"
    

    go-modeの定義において、親モードがfundamental-modeからprog-modeに変更されています。

    • Emacs 24.1以降の環境では、このprog-modeはEmacs標準のprog-modeを指し、go-modeはプログラミングモード共通の機能を継承します。
    • 古いEmacsバージョンやXEmacsでは、上記で定義されたダミーのprog-modeを親とすることになります。このダミーのprog-modefundamental-modeから派生しているため、結果的にgo-modeは以前と同様にfundamental-modeの機能セットを継承することになり、互換性が保たれます。

このアプローチは、新しいEmacsの機能を利用しつつ、古い環境での動作を損なわないための一般的なパターンです。コメントにあるように、理想的にはdefaliasを使ってprog-modefundamental-modeにエイリアスすることでより簡潔に記述できたかもしれませんが、XEmacsでの互換性の問題があったため、このような条件付きのdefine-derived-modeが選択されました。

コアとなるコードの変更箇所

変更はmisc/emacs/go-mode.elファイル内で行われています。

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -52,6 +52,14 @@
 (defun go--old-completion-list-style (list)
   (mapcar (lambda (x) (cons x nil)) list))\n
 +;; GNU Emacs 24 has prog-mode, older GNU Emacs and XEmacs do not.
+;; Ideally we\'d use defalias instead, but that breaks in XEmacs.
+;;\n
+;; TODO: If XEmacs decides to add prog-mode, change this to use
+;; defalias to alias prog-mode or fundamental-mode to go--prog-mode
+;; and use that in define-derived-mode.\n
+(if (not (fboundp \'prog-mode))\n
+    (define-derived-mode prog-mode fundamental-mode \"\" \"\"))\n
 \n (defun go--regexp-enclose-in-symbol (s)\n   ;; XEmacs does not support \\_<, GNU Emacs does. In GNU Emacs we make
@@ -370,7 +378,7 @@ current line will be returned.\"\n       (forward-char))))\n \n ;;;###autoload\n-(define-derived-mode go-mode fundamental-mode \"Go\"\n+(define-derived-mode go-mode prog-mode \"Go\"\n   \"Major mode for editing Go source text.\n \n This mode provides (not just) basic editing capabilities for\n```

## コアとなるコードの解説

変更は主に2つの部分に分けられます。

1.  **`prog-mode`のフォールバック定義(追加された部分)**:
    ```elisp
    ;; GNU Emacs 24 has prog-mode, older GNU Emacs and XEmacs do not.
    ;; Ideally we'd use defalias instead, but that breaks in XEmacs.
    ;;
    ;; TODO: If XEmacs decides to add prog-mode, change this to use
    ;; defalias to alias prog-mode or fundamental-mode to go--prog-mode
    ;; and use that in define-derived-mode.
    (if (not (fboundp 'prog-mode))
        (define-derived-mode prog-mode fundamental-mode "" ""))
    ```
    このブロックは、`go-mode.el`がロードされる際に実行されます。
    -   `fboundp 'prog-mode)`: `prog-mode`という名前の関数(またはモード)がEmacsに存在するかどうかをチェックします。
    -   `(not (fboundp 'prog-mode))`: `prog-mode`が存在しない場合に真となります。
    -   `(define-derived-mode prog-mode fundamental-mode "" "")`: `prog-mode`が存在しない場合、`fundamental-mode`から派生した新しいメジャーモード`prog-mode`を定義します。この定義は、モードラインに表示される名前もドキュメント文字列も空の、実質的に何もしないダミーのモードです。これにより、後続の`go-mode`の定義で`prog-mode`が親として指定されても、シンボルが見つからないというエラーが発生しなくなります。
    -   コメントでは、GNU Emacs 24以降で`prog-mode`が利用可能であること、古いEmacsやXEmacsでは利用できないこと、そして理想的には`defalias`を使いたかったがXEmacsで問題があることが説明されています。また、将来的にXEmacsが`prog-mode`をサポートした場合のTODOも記されています。

2.  **`go-mode`の親モードの変更(修正された部分)**:
    ```diff
    -(define-derived-mode go-mode fundamental-mode "Go"
    +(define-derived-mode go-mode prog-mode "Go"
    ```
    これは`go-mode`の実際の定義部分です。
    -   変更前は`(define-derived-mode go-mode fundamental-mode "Go" ...)`となっており、`go-mode`は`fundamental-mode`から直接派生していました。
    -   変更後は`(define-derived-mode go-mode prog-mode "Go" ...)`となり、`go-mode`は`prog-mode`から派生するようになりました。
    -   この変更により、Emacs 24.1以降の環境では`go-mode`が`prog-mode`の共通機能を継承し、古い環境では上記で定義されたダミーの`prog-mode`(実質的には`fundamental-mode`)を継承することで、後方互換性を維持しています。

このコミットは、Emacsの進化に合わせて`go-mode`の基盤を更新しつつ、幅広いEmacsユーザーが利用できるように互換性を考慮した堅牢な実装を示しています。

## 関連リンク

*   Go言語: [https://go.dev/](https://go.dev/)
*   Emacs: [https://www.gnu.org/software/emacs/](https://www.gnu.org/software/emacs/)
*   Emacs Lisp Reference Manual: [https://www.gnu.org/software/emacs/manual/html_node/elisp/](https://www.gnu.org/software/emacs/manual/html_node/elisp/)

## 参考にした情報源リンク

*   Emacs `prog-mode`に関する情報 (Web検索結果より):
    *   emacsredux.com: `prog-mode`の導入と目的について
    *   gnu.org: `prog-mode`と`prog-mode-hook`に関する公式ドキュメント
    *   github.com / stackoverflow.com: `prog-mode`の利用例や関連する議論
*   Git commit `4ad1a87f8573b355b814589c63b75a7657df4069` (golang/go): [https://github.com/golang/go/commit/4ad1a87f8573b355b814589c63b75a7657df4069](https://github.com/golang/go/commit/4ad1a87f8573b355b814589c63b75a7657df4069)
*   Gerrit Change-Id `7956044` (golang.org/cl): [https://golang.org/cl/7956044](https://golang.org/cl/7956044) (これはコミットメッセージに記載されているリンクであり、詳細なレビュー情報が含まれている可能性があります。)