[インデックス 13244] ファイルの概要
このコミットは、EmacsエディタのGo言語用メジャーモードであるgo-mode.el
における、バッファの「変更済み」状態が不必要に設定されてしまう問題を修正するものです。具体的には、go-mode
がファイルをロードする際に内部的なテキストプロパティを削除する処理が、意図せずバッファを「変更済み」とマークしてしまう挙動を改善し、ユーザー体験を向上させています。
コミット
このコミットは、Emacsのgo-mode
がファイルをロードする際に、バッファが不必要に「変更済み」とマークされる問題を解決します。これは、remove-text-properties
関数がバッファの変更状態を更新してしまう副作用を回避するために、元の変更状態を保存し、処理後に復元するという手法を用いています。
- コミットハッシュ:
c9e698bdfb77bd74bc6faab88e39203ae0571eb0
- 作者: Ryan Barrett ryanb@google.com
- コミット日時: 2012年6月1日 金曜日 16:55:03 -0400
- コミットメッセージ:
misc/emacs: stop go-mode from spuriously marking the buffer modified when it loads R=golang-dev, sameer, bradfitz CC=golang-dev, jba https://golang.org/cl/6213056
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c9e698bdfb77bd74bc6faab88e39203ae0571eb0
元コミット内容
misc/emacs: stop go-mode from spuriously marking the buffer modified when it loads
R=golang-dev, sameer, bradfitz
CC=golang-dev, jba
https://golang.org/cl/6213056
変更の背景
Emacsでは、バッファの内容が変更されると、そのバッファは「変更済み (modified)」としてマークされます。これは、ユーザーがファイルを保存せずに閉じようとした際に、変更を保存するかどうかを尋ねるプロンプトを表示するために重要な機能です。
しかし、go-mode.el
がファイルをロードする際、内部的な処理としてremove-text-properties
関数を呼び出して、古いテキストプロパティ(例えば、以前の構文解析結果など)を削除していました。Emacs Lispの設計上、remove-text-properties
のような関数は、テキストの内容が変更されていなくても、バッファの「変更済み」フラグを立ててしまう副作用がありました。
この副作用により、ユーザーがGoのソースコードファイルを開いただけなのに、Emacsがそのバッファを「変更済み」と誤って認識し、保存を促すプロンプトが不必要に表示されるという問題が発生していました。これはユーザーにとって煩わしく、本来変更されていないファイルを保存する手間を強いるものでした。このコミットは、この「誤った変更済みマーク」を抑制し、ユーザー体験を改善することを目的としています。
前提知識の解説
Emacs (エディタ)
Emacsは、高度にカスタマイズ可能なテキストエディタであり、プログラミング、文書作成、メール、ウェブブラウジングなど、多岐にわたる用途で使用されます。Emacs Lispという独自のプログラミング言語で拡張されており、ユーザーはLispコードを記述することで、エディタの挙動を細かく制御できます。
Emacs Lisp (Elisp)
Emacsの機能を拡張するために使用されるプログラミング言語です。Emacsのほぼ全ての機能はEmacs Lispで実装されており、ユーザーは既存の関数を呼び出したり、新しい関数を定義したりすることで、エディタをカスタマイズできます。
メジャーモード (Major Mode)
Emacsには、編集するファイルのタイプ(例: Go、Python、Markdownなど)に応じて、エディタの挙動を最適化するための「メジャーモード」という概念があります。各メジャーモードは、その言語に特化した構文ハイライト、インデント、コマンドなどを提供します。go-mode
はGo言語のソースコードを編集するためのメジャーモードです。
バッファ (Buffer)
Emacsにおいて、ファイルの内容やその他のテキストデータが一時的に保持されるメモリ上の領域を「バッファ」と呼びます。ユーザーがファイルを編集する際、実際にはそのファイルのバッファを操作しています。
テキストプロパティ (Text Properties)
Emacsでは、バッファ内の特定のテキスト範囲に「プロパティ」と呼ばれるメタデータを付与できます。これは、構文ハイライトの色、フォント、クリック可能なリンク、スペルチェックの状態など、テキストの表示や挙動を制御するために使用されます。go-mode
では、構文解析の結果(例: 識別子の種類、ネストの深さなど)をテキストプロパティとして保持し、これに基づいてハイライトやインデントを調整することがあります。
buffer-modified-p
関数
Emacs Lispの関数で、現在のバッファが変更されているかどうかを真偽値(t
またはnil
)で返します。t
は変更済み、nil
は未変更を意味します。
set-buffer-modified-p
関数
Emacs Lispの関数で、現在のバッファの変更済みフラグを明示的に設定します。引数にt
を渡すと変更済み、nil
を渡すと未変更に設定されます。
remove-text-properties
関数
Emacs Lispの関数で、指定されたテキスト範囲から特定のテキストプロパティを削除します。この関数は、テキストの内容自体を変更しない場合でも、バッファの「変更済み」フラグを立てる副作用を持つことがあります。これは、プロパティの変更もバッファの状態変更と見なされるためです。
let
特殊形式
Emacs Lispにおける変数のスコープを限定するための特殊形式です。let ((var1 val1) (var2 val2) ...)
の形式で、ローカル変数を定義し、その変数に初期値を割り当てます。let
ブロック内で定義された変数は、そのブロック内でのみ有効です。
技術的詳細
このコミットの技術的な核心は、Emacs Lispのremove-text-properties
関数が持つ副作用、すなわち「バッファの変更済みフラグを立ててしまう」挙動を、元の状態を維持したまま回避することにあります。
従来のコードでは、go-mode
がロードされる際に、バッファ全体(1
からpoint-max
まで)に対してgo-mode-cs
とgo-mode-nesting
というテキストプロパティを削除していました。この操作自体は、以前のセッションや解析結果が残っている場合にそれらをクリアするためのもので、バッファの内容をユーザーが直接変更するものではありません。しかし、Emacsの内部的な挙動により、テキストプロパティの変更もバッファの「変更済み」状態と見なされ、フラグが立てられてしまっていました。
この修正では、以下の手順でこの問題を解決しています。
- 現在の変更状態の保存:
(buffer-modified-p)
関数を呼び出し、remove-text-properties
が実行される前のバッファの変更状態(変更済みか未変更か)を取得し、modified
というローカル変数に保存します。これはlet
特殊形式を使って行われます。 - テキストプロパティの削除: 従来通り
remove-text-properties
関数を呼び出し、不要なテキストプロパティを削除します。この時点で、バッファは(たとえ元々未変更であっても)「変更済み」とマークされる可能性があります。 - 元の変更状態の復元:
(set-buffer-modified-p modified)
関数を呼び出し、ステップ1で保存しておいた元の変更状態をバッファに再設定します。これにより、remove-text-properties
によって誤って立てられた「変更済み」フラグが、元の状態(未変更であれば未変更)に戻されます。
このアプローチにより、go-mode
は必要な内部クリーンアップ処理を実行しつつも、ユーザーが実際にバッファの内容を変更していない限り、バッファが「変更済み」と誤って表示されることを防ぎ、よりスムーズなユーザー体験を提供します。
コアとなるコードの変更箇所
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -706,8 +706,12 @@ functions, and some types. It also provides indentation that is
;; Remove stale text properties
(save-restriction
(widen)
- (remove-text-properties 1 (point-max)
- '(go-mode-cs nil go-mode-nesting nil)))
+ (let ((modified (buffer-modified-p)))
+ (remove-text-properties 1 (point-max)
+ '(go-mode-cs nil go-mode-nesting nil))
+ ;; remove-text-properties marks the buffer modified. undo that if it
+ ;; wasn't originally marked modified.
+ (set-buffer-modified-p modified)))
;; Reset the syntax mark caches
(setq go-mode-mark-cs-end 1
コアとなるコードの解説
変更されたコードブロックは、go-mode.el
のgo-mode-load-hook
(またはそれに類する初期化処理)内で実行される部分です。
変更前:
(remove-text-properties 1 (point-max)
'(go-mode-cs nil go-mode-nesting nil)))
この行は、バッファの先頭(1
)から末尾(point-max
)までの範囲から、go-mode-cs
とgo-mode-nesting
というテキストプロパティを削除していました。この操作がバッファを「変更済み」とマークする原因となっていました。
変更後:
(let ((modified (buffer-modified-p)))
(remove-text-properties 1 (point-max)
'(go-mode-cs nil go-mode-nesting nil))
;; remove-text-properties marks the buffer modified. undo that if it
;; wasn't originally marked modified.
(set-buffer-modified-p modified)))
-
(let ((modified (buffer-modified-p))) ...)
:let
特殊形式は、ローカル変数modified
を定義し、その初期値として(buffer-modified-p)
の評価結果を割り当てます。(buffer-modified-p)
は、remove-text-properties
が実行される前のバッファの変更状態(t
またはnil
)を返します。この値がmodified
変数に保存されます。
-
(remove-text-properties 1 (point-max) '(go-mode-cs nil go-mode-nesting nil))
:- この行は変更前と同じで、テキストプロパティを削除します。この操作により、バッファが「変更済み」とマークされる可能性があります。
-
;; remove-text-properties marks the buffer modified. undo that if it
;; wasn't originally marked modified.
- これはコードコメントで、この修正の目的を説明しています。「
remove-text-properties
はバッファを修正済みとマークする。もし元々修正済みでなかったなら、それを元に戻す。」という意味です。
- これはコードコメントで、この修正の目的を説明しています。「
-
(set-buffer-modified-p modified)
:set-buffer-modified-p
関数を呼び出し、modified
変数に保存されていた元の変更状態をバッファに設定し直します。- もし
modified
がnil
(元々未変更)であれば、バッファは未変更状態に戻されます。 - もし
modified
がt
(元々変更済み)であれば、バッファは変更済み状態のまま維持されます。
この一連の処理により、remove-text-properties
の副作用を打ち消し、バッファの変更状態を正確に保つことが可能になっています。
関連リンク
- Gerrit Change-ID:
https://golang.org/cl/6213056
- これはGoプロジェクトでコードレビューに使用されるGerritシステムにおける変更セットのIDです。このリンクを辿ることで、このコミットに至るまでの議論や、関連する変更、レビューコメントなどを確認できる場合があります。
参考にした情報源リンク
- Emacs Lisp Reference Manual (特に、
buffer-modified-p
,set-buffer-modified-p
,remove-text-properties
,let
に関するセクション) - Emacsのテキストプロパティに関するドキュメントやチュートリアル
- Go言語の
go-mode
に関する情報 - Gerrit Code Review Systemの利用方法に関する情報
remove-text-properties
がバッファをmodifiedにする挙動に関するEmacsコミュニティの議論(一般的な知識として)- 例: https://emacs.stackexchange.com/questions/1000/how-to-prevent-a-function-from-marking-the-buffer-as-modified (類似の問題と解決策の議論)
- 例: https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffer-Modification.html (Emacs Lisp Manual: Buffer Modification)
- 例: https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Properties.html (Emacs Lisp Manual: Text Properties)
- これらのリンクは、このコミットの背景にあるEmacs Lispの挙動を理解する上で参考になります。