[インデックス 19083] ファイルの概要
このコミットは、EmacsエディタのGo言語モード(go-mode
)におけるシンタックスハイライトのバグを修正するものです。具体的には、コメント内や通常の文字列リテラル内に存在するバッククォート(`
)が、Go言語のRaw String Literal(生文字列リテラル)の開始と誤って認識され、不適切なシンタックスハイライトが行われる問題を解決します。
コミット
commit 3d63ec240edfc596a8840f7a7e5218fa28e55c04
Author: Rui Ueyama <ruiu@google.com>
Date: Wed Apr 9 12:28:27 2014 -0400
misc/emacs: ignore backquote in comment or string
go-mode on Emacs 23 wrongly recognizes a backquote in a comment or
a string as a start of a raw string literal. Below is an example
that go-mode does not work well. This patch is to fix that issue.
// `
var x = 1
// `
LGTM=dominik.honnef
R=golang-codereviews, dominik.honnef, adonovan
CC=golang-codereviews
https://golang.org/cl/84900043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3d63ec240edfc596a8840f7a7e5218fa28e55c04
元コミット内容
misc/emacs: ignore backquote in comment or string
go-mode on Emacs 23 wrongly recognizes a backquote in a comment or
a string as a start of a raw string literal. Below is an example
that go-mode does not work well. This patch is to fix that issue.
// `
var x = 1
// `
LGTM=dominik.honnef
R=golang-codereviews, dominik.honnef, adonovan
CC=golang-codereviews
https://golang.org/cl/84900043
変更の背景
Emacs 23のgo-mode
において、Go言語のソースコードを編集する際に、コメント内や通常の文字列リテラル内にバッククォート(`
)が存在すると、go-mode
がそれをGoのRaw String Literalの開始と誤認識してしまう問題がありました。この誤認識により、それ以降のコードのシンタックスハイライトが崩れてしまい、コードの可読性が著しく低下するというユーザーエクスペリエンス上の問題が発生していました。
例えば、以下のようなコードがあった場合、
// `
var x = 1
// `
go-mode
は最初の//
の後のバッククォートをRaw String Literalの開始と見なし、var x = 1
の行もRaw String Literalの一部としてハイライトしてしまうため、開発者はコードの構造を正確に把握することが困難になっていました。このコミットは、この誤認識を修正し、正しいシンタックスハイライトを提供することを目的としています。
前提知識の解説
Emacs
Emacsは、強力なカスタマイズ性と拡張性を持つテキストエディタです。Lisp言語(Emacs Lisp)で記述されており、ユーザーはEmacs Lispを用いてエディタの挙動を細かく制御したり、新しい機能を追加したりすることができます。
go-mode
go-mode
は、Emacs上でGo言語のソースコードを編集するためのメジャーモードです。Go言語のシンタックスハイライト、自動インデント、コード補完、フォーマットなどの機能を提供し、Go開発者の生産性を向上させます。
Font-lock mode
font-lock-mode
は、Emacsの主要なシンタックスハイライト機能を提供するマイナーモードです。バッファ(開いているファイルの内容)を解析し、キーワード、文字列、コメント、変数名、関数名などの構文要素に異なる色やスタイルを適用します。これにより、コードの構造が視覚的に分かりやすくなり、可読性が向上します。font-lock-mode
は、各メジャーモードが定義するfont-lock-syntactic-keywords
などの変数に設定されたパターン(正規表現や関数)に基づいて、どの部分をどのようにハイライトするかを決定します。
Go言語のRaw String Literal (生文字列リテラル)
Go言語には、通常の文字列リテラル(ダブルクォート"
で囲む)とは別に、Raw String Literal(生文字列リテラル)というものがあります。これはバッククォート(`
)で囲まれた文字列で、その内部ではエスケープシーケンス(例: \n
, \t
)が一切解釈されず、記述された文字がそのまま文字列の値となります。複数行にわたる文字列や、正規表現、HTML/XMLなどの記述において、エスケープ文字を気にすることなく記述できるため非常に便利です。
例:
// 通常の文字列リテラル
s1 := "Hello\nWorld" // 改行文字が含まれる
// Raw String Literal
s2 := `Hello\nWorld` // "\n"がそのまま文字列に含まれる
s3 := `
<html>
<body>
<h1>Hello</h1>
</body>
</html>
` // 複数行のHTMLをそのまま記述
技術的詳細
go-mode
におけるシンタックスハイライトは、Emacsのfont-lock-mode
を利用して実現されています。font-lock-mode
は、font-lock-syntactic-keywords
という変数に定義されたパターンリストを基に、バッファ内のテキストをスキャンし、対応する構文要素に適切なフェイス(色やスタイル)を適用します。
このコミット以前のgo-mode.el
では、Raw String Literalを認識するために、font-lock-syntactic-keywords
リスト内に以下のような正規表現パターンが直接定義されていました。
'("`\\(`\\)\\(.*\\)`\\(`\\)`"
(1 (7 . ?`))
(2 (15 . nil)) ;; 15 = "generic string"
(3 (7 . ?`))))
この正規表現は、バッククォートで始まりバッククォートで終わる任意の文字列をRaw String Literalとしてマッチさせようとします。しかし、この単純な正規表現では、バッククォートがコメント内(//
や/* */
)や通常のダブルクォートで囲まれた文字列リテラル内に現れた場合でも、それをRaw String Literalの開始と誤って解釈してしまうという問題がありました。これは、正規表現が文脈(コメント内か、文字列内かなど)を考慮せずにパターンマッチングを行うためです。
このコミットでは、この問題を解決するために、Raw String Literalの認識方法を根本的に変更しました。具体的には、font-lock-syntactic-keywords
リストから直接正規表現を削除し、代わりにgo--match-raw-string-literal
というEmacs Lisp関数を呼び出すように変更しました。
新しいgo--match-raw-string-literal
関数は、バッククォートを検出した際に、そのバッククォートが実際にRaw String Literalの開始であるかどうかをより厳密に判断するロジックを含んでいます。この判断には、go-in-string-or-comment-p
という補助関数が利用されます。この補助関数は、現在のカーソル位置がコメント内または文字列リテラル内にあるかどうかを判定するもので、これにより、コメントや通常の文字列内のバッククォートをRaw String Literalとして誤認識することを防ぎます。
つまり、変更前は「バッククォートで囲まれていればRaw String Literal」という単純なルールだったのに対し、変更後は「バッククォートで囲まれており、かつそれがコメント内や通常の文字列内ではない場合にのみRaw String Literal」という、より洗練された文脈依存のルールが適用されるようになりました。
コアとなるコードの変更箇所
misc/emacs/go-mode.el
ファイルが変更されました。
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -294,7 +294,7 @@ For mode=set, all covered lines will have this weight."
(defconst go--font-lock-syntactic-keywords
;; Override syntax property of raw string literal contents, so that
;; backslashes have no special meaning in ``. Used in Emacs 23 or older.
- '((go--match-raw-string-literal
+ '((go--match-raw-string-literal
(1 (7 . ?`))
(2 (15 . nil)) ;; 15 = "generic string"
(3 (7 . ?`))))
@@ -367,6 +367,18 @@ STOP-AT-STRING is not true, over strings."
(- (point-max)
(point-min))))
+(defun go--match-raw-string-literal (end)
+ "Search for a raw string literal. Set point to the end of the
+occurence found on success. Returns nil on failure."
+ (when (search-forward "`" end t)
+ (goto-char (match-beginning 0))
+ (if (go-in-string-or-comment-p)
+ (progn (goto-char (match-end 0))
+ (go--match-raw-string-literal end))
+ (when (looking-at "`\\(`\\)\\(.*\\)`\\(`\\)`")
+ (goto-char (match-end 0))
+ t))))
+
(defun go-previous-line-has-dangling-op-p ()
"Returns non-nil if the current line is a continuation line."
(let* ((cur-line (line-number-at-pos))
コアとなるコードの解説
go--font-lock-syntactic-keywords
の変更
以前は、go--font-lock-syntactic-keywords
の定義内で、Raw String Literalを認識するための正規表現が直接記述されていました。
'("`\\(`\\)\\(.*\\)`\\(`\\)`"
(1 (7 . ?`))
(2 (15 . nil)) ;; 15 = "generic string"
(3 (7 . ?`))))
このコミットでは、この正規表現を削除し、代わりにgo--match-raw-string-literal
という関数を呼び出すように変更されました。
'((go--match-raw-string-literal
(1 (7 . ?`))
(2 (15 . nil)) ;; 15 = "generic string"
(3 (7 . ?`))))
これは、シンタックスハイライトのロジックを正規表現による単純なパターンマッチングから、より複雑な条件分岐を伴うEmacs Lisp関数へと移行したことを意味します。
新しく追加された関数 go--match-raw-string-literal
この関数が、Raw String Literalを正確に識別するための新しいロジックを実装しています。
(defun go--match-raw-string-literal (end)
"Search for a raw string literal. Set point to the end of the
occurence found on success. Returns nil on failure."
(when (search-forward "`" end t)
(goto-char (match-beginning 0))
(if (go-in-string-or-comment-p)
(progn (goto-char (match-end 0))
(go--match-raw-string-literal end))
(when (looking-at "`\\(`\\)\\(.*\\)`\\(`\\)`")
(goto-char (match-end 0))
t))))
この関数の動作は以下の通りです。
(when (search-forward "
" end t): 現在のカーソル位置から
end(検索範囲の終端)まで、バッククォート(``
``)を前方検索します。バッククォートが見つかれば、その位置にカーソルを移動し、when
ブロック内の処理を実行します。見つからなければnil
を返します。(goto-char (match-beginning 0))
:search-forward
で見つかったバッククォートの開始位置にカーソルを移動します。(if (go-in-string-or-comment-p)
: ここがこの修正の核心部分です。go-in-string-or-comment-p
関数を呼び出し、現在のカーソル位置(バッククォートが見つかった位置)がコメント内または通常の文字列リテラル内にあるかどうかを判定します。- もしコメント内または文字列内であれば:
(progn (goto-char (match-end 0)) (go--match-raw-string-literal end))
:progn
は複数の式を順に評価します。まず(goto-char (match-end 0))
で、見つかったバッククォートの直後にカーソルを移動します。次に、(go--match-raw-string-literal end)
を再帰的に呼び出し、現在のバッククォートをスキップして、検索範囲の終端まで次のバッククォートを探し続けます。これにより、コメントや文字列内のバッククォートはRaw String Literalとして扱われなくなります。
- もしコメント内または文字列内でなければ:
(when (looking-at "
\(\\)\\(.*\\)
\(\\)
"): 現在のカーソル位置から、GoのRaw String Literalの正規表現パターン(バッククォートで始まりバッククォートで終わる)にマッチするかどうかを確認します。
looking-at`は、現在のカーソル位置からパターンがマッチするかどうかをチェックします。(goto-char (match-end 0))
: もしパターンにマッチすれば、マッチしたRaw String Literalの終端にカーソルを移動します。t
: そしてt
(真)を返します。font-lock-mode
は、このt
を受け取ると、マッチした部分をRaw String Literalとしてハイライトします。
- もしコメント内または文字列内であれば:
このgo--match-raw-string-literal
関数と、その中で利用されるgo-in-string-or-comment-p
関数(このコミットでは変更されていませんが、既存のgo-mode
の機能として存在します)の組み合わせにより、go-mode
はバッククォートがRaw String Literalの一部であるべきか、それとも単なるコメントや文字列内の文字として無視すべきかを正確に判断できるようになりました。
関連リンク
- Go CL 84900043: https://golang.org/cl/84900043
参考にした情報源リンク
- Emacs
font-lock-mode
に関する情報: Web search results for "Emacs font-lock-mode go-mode.el raw string literal"