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

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

このコミットは、Go言語のEmacsメジャーモードであるgo-mode.elに対する修正です。具体的には、文字列やコメントからコピー&ペーストを行った際にEmacsがフリーズする問題を解決することを目的としています。

コミット

commit 28e9e15802aba9caef309edc5568c48c2965a19f
Author: Peter Kleiweg <pkleiweg@xs4all.nl>
Date:   Tue May 29 12:40:12 2012 -0400

    Emacs go-mode: fix Emacs freeze-up when copy/pasting from string or comment.
    
    Fixes #3509.
    Fixes #2767.
    
    R=golang-dev, sameer
    CC=golang-dev
    https://golang.org/cl/6139066

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

https://github.com/golang/go/commit/28e9e15802aba9caef309edc5568c48c2965a19f

元コミット内容

Emacs go-mode: 文字列またはコメントからのコピー/ペースト時にEmacsのフリーズを修正。

Fixes #3509. Fixes #2767.

R=golang-dev, sameer CC=golang-dev https://golang.org/cl/6139066

変更の背景

このコミットは、Go言語のEmacsモード(go-mode.el)を使用している際に発生していた深刻なパフォーマンス問題、特にEmacsがフリーズするというバグを修正するために導入されました。この問題は、Goのソースコード内で文字列リテラルやコメントブロックからテキストをコピー&ペーストする際に顕著に発生していました。コミットメッセージに記載されているFixes #3509Fixes #2767は、この問題に関連するGoプロジェクトのIssueトラッカーのエントリを示しており、ユーザーからの報告に基づいて修正が行われたことを示唆しています。

一般的なEmacsのメジャーモードでは、構文ハイライトやインデント、コード補完などの機能を提供するために、バッファの内容が変更されるたびに内部的な状態を更新する必要があります。go-mode.elも同様に、コメントや文字列の範囲を識別するための内部キャッシュやテキストプロパティを使用していたと考えられます。しかし、大量のテキストがペーストされたり、頻繁な変更が行われたりすると、これらの内部処理が非効率になり、Emacsの応答性が著しく低下し、最終的にフリーズに至るという問題が発生していました。

前提知識の解説

このコミットの変更内容を理解するためには、以下のEmacs Lispの概念に関する知識が必要です。

  • Emacs Lisp (Elisp): Emacsエディタの拡張言語であり、Emacsの動作のほとんどはElispで記述されています。
  • メジャーモード (Major Mode): 特定の種類のファイル(例: Goファイル、Pythonファイル)を編集するための特殊な動作を提供するEmacsのモードです。構文ハイライト、インデントルール、特定のコマンドなどが含まれます。go-mode.elはGo言語用のメジャーモードです。
  • フック (Hooks): Emacsの特定のイベント(例: ファイルを開く、バッファの内容が変更される)が発生したときに自動的に実行される関数のリストです。
    • before-change-functions: バッファの内容が変更される直前に実行されるフックのリスト。
    • after-change-functions: バッファの内容が変更された直後に実行されるフックのリスト。
  • テキストプロパティ (Text Properties): Emacsのバッファ内のテキストに付加できる属性です。フォント、色、クリック可能な領域、特定のモードが内部的に使用するメタデータなど、様々な情報を保持できます。go-mode-csのようなカスタムテキストプロパティは、go-modeがコメントや文字列の範囲を内部的に追跡するために使用していた可能性があります。
  • remove-text-properties: 指定された範囲のテキストから特定のテキストプロパティを削除するEmacs Lisp関数。
  • defun: Emacs Lispで関数を定義するためのマクロ。

この問題の根源は、go-mode.elが文字列やコメントの範囲を識別するために使用していたgo-mode-csというテキストプロパティの管理方法にありました。大量のテキストがペーストされると、このプロパティの更新処理がボトルネックとなり、Emacsが応答しなくなるという状況を引き起こしていました。

技術的詳細

このコミットの技術的な解決策は、Emacsのafter-change-functionsフックを利用して、バッファの内容が変更された後にgo-mode-csテキストプロパティを効率的にクリアすることです。

  1. go-mode-mark-clear-cs関数の追加:

    (defun go-mode-mark-clear-cs (b e l)
      "An after-change-function that removes the go-mode-cs text property"
      (remove-text-properties b e '(go-mode-cs)))
    

    この新しい関数go-mode-mark-clear-csは、after-change-functionsフックに登録されることを意図しています。after-change-functionsに登録される関数は、変更された範囲の開始位置(b)、終了位置(e)、および変更された行数(l)を引数として受け取ります。この関数は、変更された範囲bからeまでのテキストから、go-mode-csというテキストプロパティを削除します。

  2. go-mode-mark-clear-csのフックへの登録:

    (add-hook 'after-change-functions #'go-mode-mark-clear-cs nil t)
    

    go-mode.elの初期化部分(go-mode-hookが実行される箇所、またはgo-modeがアクティブになる箇所)に、上記の行が追加されました。これにより、バッファの内容が変更されるたびに(特にコピー&ペースト操作によって大量のテキストが挿入された後)、go-mode-mark-clear-cs関数が自動的に呼び出され、変更された範囲のgo-mode-csテキストプロパティがクリアされます。

このアプローチにより、go-modeが文字列やコメントの範囲を再計算する際に、古いまたは不正確なgo-mode-csプロパティに依存することなく、常に最新のバッファ内容に基づいて処理を行うことができるようになります。これにより、プロパティの不整合による無限ループや非効率な再計算が回避され、Emacsのフリーズが解消されます。

以前のgo-mode.elでは、before-change-functionsgo-mode-mark-clear-cacheが登録されており、変更前にキャッシュをクリアしていました。しかし、テキストプロパティの更新が追いつかない、あるいは特定の条件下でプロパティが正しくクリアされないケースがあったため、変更後に明示的にgo-mode-csプロパティを削除するメカニズムが追加されたと考えられます。

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

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -182,6 +182,10 @@ to and including character (1- go-mode-mark-comment-end)).\")
 marked from the beginning up to this point.\")
 (make-variable-buffer-local 'go-mode-mark-nesting-end)
 
+(defun go-mode-mark-clear-cs (b e l)
+  "An after-change-function that removes the go-mode-cs text property"
+  (remove-text-properties b e '(go-mode-cs)))
+
 (defun go-mode-mark-clear-cache (b e)
   "A before-change-function that clears the comment/string and
 nesting caches from the modified point on."
@@ -709,6 +713,7 @@ functions, and some types.  It also provides indentation that is
   (setq go-mode-mark-cs-end      1
         go-mode-mark-nesting-end 1)
   (add-hook 'before-change-functions #'go-mode-mark-clear-cache nil t)
+  (add-hook 'after-change-functions #'go-mode-mark-clear-cs nil t)
 
   ;; Indentation
   (set (make-local-variable 'indent-line-function)

コアとなるコードの解説

このコミットの核となる変更は、misc/emacs/go-mode.elファイル内の2つの追加です。

  1. go-mode-mark-clear-cs関数の定義: この関数は、Emacs Lispのdefunマクロを使って定義されています。

    • b, e, l は、Emacsのafter-change-functionsフックに登録される関数が受け取る標準的な引数です。bは変更が開始されたバッファ位置、eは変更が終了したバッファ位置、lは変更によって追加または削除された行数を示します。
    • (remove-text-properties b e '(go-mode-cs))がこの関数の主要な処理です。これは、バッファのbからeまでの範囲にあるテキストから、go-mode-csという名前のテキストプロパティをすべて削除します。go-mode-csは、go-modeがGoコード内のコメントや文字列の領域を内部的にマークするために使用していたカスタムプロパティであると推測されます。このプロパティを強制的にクリアすることで、モードが古い、または不正な状態に依存してフリーズするのを防ぎます。
  2. go-mode-mark-clear-cs関数のafter-change-functionsへの追加: go-modeの初期化部分(おそらくgo-modeが有効になったときに実行されるgo-mode-hook内)に、以下の行が追加されました。

    • (add-hook 'after-change-functions #'go-mode-mark-clear-cs nil t): これは、go-mode-mark-clear-cs関数をafter-change-functionsフックのリストに追加します。
      • 'after-change-functions: バッファの内容が変更された直後に実行されるフックのリストを指定します。
      • #'go-mode-mark-clear-cs: 登録する関数のシンボルです。
      • nil: ローカルフックではないことを示します(この場合はバッファローカルなフックとして追加されますが、add-hookの引数としてはnilが一般的です)。
      • t: このフックをバッファローカルなものとして追加することを示します。これにより、go-modeが有効なバッファでのみこの関数が実行され、他のバッファのパフォーマンスに影響を与えません。

この変更により、ユーザーがGoコードのバッファでテキストをコピー&ペーストするなどして内容を変更するたびに、go-mode-csテキストプロパティが自動的にクリアされるようになります。これにより、go-modeが内部状態を再計算する際に、常にクリーンな状態から開始できるようになり、フリーズ問題が解決されました。

関連リンク

参考にした情報源リンク