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

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

このコミットは、Go言語のEmacsメジャーモードであるgo-mode.elにおける挙動の修正に関するものです。具体的には、gofmtgo-remove-unused-importsといったGoツールがEmacsバッファ内のテキストをプログラム的に削除する際に、その削除されたテキストがEmacsの「キルリング (kill ring)」に追加されないようにする変更が加えられています。これにより、ユーザーの意図しないキルリングの汚染を防ぎ、Emacsの操作性を向上させています。

コミット

commit 4e15dbe0826d79223b483098bfa9aa3abf9a1261
Author: Dominik Honnef <dominik.honnef@gmail.com>
Date:   Thu May 23 10:19:03 2013 -0700

    misc/emacs: Do not modify kill ring when programmatically deleting text
    
    Operations like gofmt and go-remove-unused-imports delete entire
    lines of text. Previously this put them on the kill-ring,
    negatively affecting user experience.
    
    R=adonovan
    CC=gobot, golang-dev
    https://golang.org/cl/9605043

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

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

元コミット内容

このコミットの元のメッセージは以下の通りです。

「misc/emacs: プログラムによるテキスト削除時にキルリングを変更しない」

「gofmtやgo-remove-unused-importsのような操作は、テキストの行全体を削除します。以前はこれがキルリングに追加され、ユーザーエクスペリエンスに悪影響を与えていました。」

変更の背景

Emacsでは、テキストを削除(「キル」)すると、その削除されたテキストは「キルリング」と呼ばれる履歴に保存されます。これにより、ユーザーは後でそのテキストを「ヤンク」(貼り付け)することができます。しかし、gofmt(Goコードのフォーマットツール)やgo-remove-unused-imports(未使用のインポート文を削除するツール)のようなプログラム的な操作がEmacsバッファ内のテキストを自動的に変更・削除する場合、これらの削除されたテキストがキルリングに意図せず追加されてしまうという問題がありました。

ユーザーが手動でテキストを削除したわけではないのに、キルリングに大量の不要なエントリが追加されると、本来ヤンクしたい過去のテキストがキルリングの奥に追いやられてしまい、ユーザーのワークフローを妨げ、不便さを引き起こしていました。このコミットは、このユーザーエクスペリエンスの低下を解消することを目的としています。

前提知識の解説

Emacs

Emacsは、高度にカスタマイズ可能なテキストエディタであり、統合開発環境(IDE)としても機能します。Emacsの機能の多くはEmacs Lispというプログラミング言語で記述されており、ユーザーはEmacs Lispを使ってエディタの挙動を自由に拡張・変更できます。

Emacsのキルリング (Kill Ring)

Emacsにおける「キルリング」は、削除(キル)されたテキストの履歴を保持するバッファです。テキストを削除するコマンド(例: kill-line, kill-region)を実行すると、削除されたテキストはキルリングの先頭に追加されます。ユーザーはyankコマンド(通常 C-y)でキルリングの最新のエントリを貼り付け、yank-popコマンド(通常 M-y)でキルリング内の過去のエントリを順に選択して貼り付けることができます。

gofmt

gofmtは、Go言語のソースコードを標準的なスタイルに自動的にフォーマットするツールです。Go言語の公式ツールチェーンの一部であり、Goコミュニティ全体で一貫したコードスタイルを維持するために広く利用されています。gofmtは、インデント、スペース、改行などを自動的に調整し、場合によっては不要なコード(例: 未使用のインポート)を削除することもあります。

go-remove-unused-imports

go-remove-unused-importsは、Goのソースコードから未使用のインポート文を自動的に削除するEmacs Lisp関数(またはそれに相当する機能)を指していると考えられます。Goのビルドシステムでは未使用のインポートはエラーとなるため、開発中に頻繁に発生する不要なインポートのクリーンアップを自動化する機能は非常に便利です。

Emacs Lispのflet

fletはEmacs Lispのスペシャルフォーム(特殊形式)の一つで、関数を一時的に再定義するために使用されます。fletブロック内で定義された関数は、そのfletブロックのスコープ内でのみ有効であり、ブロックを抜けると元の定義に戻ります。この機能は、既存の関数の挙動を一時的に変更したり、特定の操作中に副作用を抑制したりするのに非常に役立ちます。

技術的詳細

このコミットの核心は、Emacs Lispのfletスペシャルフォームを使用して、テキスト削除関数kill-regionkill-newの挙動を一時的に変更することにあります。

通常、Emacsでテキストを削除する関数(例: kill-line)は、内部的にkill-regionkill-newといったプリミティブな関数を呼び出します。

  • kill-regionは、指定された領域のテキストを削除し、それをキルリングに追加します。
  • kill-newは、与えられた文字列をキルリングの先頭に追加します。

このコミットでは、プログラム的な削除操作を行う際に、これらの関数がキルリングに影響を与えないように、以下のように再定義しています。

  • kill-regionを、単にテキストを削除するだけのdelete-regionに置き換えます。delete-regionはキルリングを変更しません。
  • kill-newを、何も行わない(no-op)関数に置き換えます。

この一時的な再定義は、go--delete-whole-lineという新しいヘルパー関数の中でfletを使って行われます。これにより、go--delete-whole-lineが呼び出されている間だけ、キルリングへの追加が抑制されます。

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

misc/emacs/go-mode.el ファイルにおいて、以下の変更が行われています。

  1. 新しい関数 go--delete-whole-line の追加。
  2. 既存の関数 go--apply-rcs-patch 内で go--kill-whole-line の呼び出しを go--delete-whole-line に変更。
  3. 既存の関数 go-remove-unused-imports 内で go--kill-whole-line の呼び出しを go--delete-whole-line に変更。
--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -42,6 +42,19 @@
       'kill-whole-line
     'kill-entire-line))
 
+;; Delete the current line without putting it in the kill-ring.
+(defun go--delete-whole-line (&optional arg)
+  ;; Emacs uses both kill-region and kill-new, Xemacs only uses
+  ;; kill-region. In both cases we turn them into operations that do
+  ;; not modify the kill ring. This solution does depend on the
+  ;; implementation of kill-line, but it's the only viable solution
+  ;; that does not require to write kill-line from scratch.
+  (flet ((kill-region (beg end)
+                      (delete-region beg end))
+         (kill-new (s) ()))))
+    (go--kill-whole-line arg)))
+
+
 ;; XEmacs unfortunately does not offer position-bytes. We can fall
 ;; back to just using (point), but it will be incorrect as soon as
 ;; multibyte characters are being used.
@@ -524,7 +537,7 @@ buffer."
                 (goto-char (point-min))
                 (forward-line (- from line-offset 1))
                 (incf line-offset len)
-                (go--kill-whole-line len)))
+                (go--delete-whole-line len)))
              (t
               (error "invalid rcs patch or internal error in go--apply-rcs-patch")))))))))\n
@@ -853,7 +866,7 @@ will be commented, otherwise they will be removed completely."
           (beginning-of-line)
           (if arg
               (comment-region (line-beginning-position) (line-end-position))
-            (go--kill-whole-line)))
+            (go--delete-whole-line))))
         (message "Removed %d imports" (length lines))))
       (if flymake-state (flymake-mode-on)))))\n

コアとなるコードの解説

go--delete-whole-line 関数

(defun go--delete-whole-line (&optional arg)
  ;; Emacs uses both kill-region and kill-new, Xemacs only uses
  ;; kill-region. In both cases we turn them into operations that do
  ;; not modify the kill ring. This solution does depend on the
  ;; implementation of kill-line, but it's the only viable solution
  ;; that does not require to write kill-line from scratch.
  (flet ((kill-region (beg end)
                      (delete-region beg end))
         (kill-new (s) ()))
    (go--kill-whole-line arg)))

この関数がこのコミットの主要な変更点です。

  • (defun go--delete-whole-line (&optional arg))go--delete-whole-lineという名前の関数を定義しています。&optional argは、この関数がオプションの引数argを受け取ることを示します。
  • (flet ((kill-region (beg end) (delete-region beg end)) (kill-new (s) ()))):ここでfletスペシャルフォームが使用されています。
    • (kill-region (beg end) (delete-region beg end))kill-region関数を一時的に再定義しています。通常、kill-regionは指定された領域を削除し、その内容をキルリングに入れますが、ここではdelete-regionに置き換えられています。delete-regionはテキストを削除しますが、キルリングには追加しません。
    • (kill-new (s) ())kill-new関数を一時的に再定義しています。通常、kill-newは引数として与えられた文字列をキルリングに追加しますが、ここでは空のリスト()を返す(つまり何も行わない)関数に置き換えられています。
  • (go--kill-whole-line arg)fletブロックの内部で、元の行削除関数であるgo--kill-whole-lineを呼び出しています。この呼び出しは、上記で一時的に再定義されたkill-regionkill-newのコンテキストで行われるため、go--kill-whole-lineが内部でこれらの関数を呼び出したとしても、キルリングは変更されません。

go--apply-rcs-patchgo-remove-unused-imports での利用

変更された箇所は、go--apply-rcs-patchgo-remove-unused-importsの2つの関数です。これらの関数は、Goのツール(gofmtや未使用インポートの削除)によってテキストがプログラム的に変更される際に、行を削除する目的でgo--kill-whole-lineを呼び出していました。

変更前:

(go--kill-whole-line len)

変更後:

(go--delete-whole-line len)

これにより、これらのプログラム的な操作によって行が削除される際に、新しく定義されたgo--delete-whole-line関数が呼び出され、その内部でキルリングへの追加が抑制されるようになります。結果として、ユーザーはgofmtやインポート削除操作によってキルリングが汚染されることなく、快適にEmacsを使用できるようになります。

関連リンク

参考にした情報源リンク