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

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

このコミットは、Go言語のEmacsメジャーモードであるgo-mode.elにおけるバグ修正に関するものです。具体的には、gofmtコマンドを実行した際に、Emacsフレーム内に複数のウィンドウが存在する場合、Goコードを編集していたウィンドウ以外のウィンドウが失われてしまう問題を解決します。

コミット

  • コミットハッシュ: 350a5ce64fc17d229137f66e855a9d733dc76e2e
  • Author: Jan Newmarch jan.newmarch@gmail.com
  • Date: Mon Oct 31 11:33:14 2011 -0400

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

https://github.com/golang/go/commit/350a5ce64fc17d229137f66e855a9d733dc76e2e

元コミット内容

    misc/emacs/go-lang.el: Fix restoration of multiple windows in a frame after gofmt
    If a frame has multiple windows then the windows must all be restored
    after gofmt has finished and the old windows must be restored.
    Before this fix, only the Go code edit window would be left.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5280050

変更の背景

Go言語の開発において、コードのフォーマットはgofmtというツールによって自動化されています。Emacsのようなエディタでは、ファイルを保存する際や特定のコマンドを実行する際に、このgofmtを自動的に実行するように設定することが一般的です。

このコミットが修正しようとしている問題は、Emacsのgo-mode.elgofmtを実行した後に発生していました。Emacsでは、一つのフレーム(ウィンドウシステムにおけるアプリケーションウィンドウに相当)内に複数のウィンドウ(バッファを表示する領域)を分割して表示することができます。例えば、コードを編集するウィンドウと、テスト結果を表示するウィンドウ、あるいは別の関連ファイルを表示するウィンドウを並べて表示するといった使い方です。

しかし、この修正前のgo-mode.elでは、gofmtを実行すると、Goコードを編集していたメインのウィンドウだけが残り、他の分割されていたウィンドウがすべて閉じてしまうという問題がありました。これは、ユーザーが複数の情報を同時に参照しながら作業している場合に、非常に不便な挙動でした。このコミットは、gofmt実行後もEmacsのウィンドウ構成が正しく復元されるようにすることで、このユーザーエクスペリエンスの低下を解消することを目的としています。

前提知識の解説

Emacsのウィンドウとフレーム

Emacsにおける「フレーム」は、通常、GUI環境におけるアプリケーションのトップレベルウィンドウを指します。一つのEmacsセッションで複数のフレームを開くことも可能です。 「ウィンドウ」は、そのフレーム内でバッファ(ファイルの内容やコマンドの出力など)を表示するための領域です。一つのフレームは、複数のウィンドウに分割することができます。例えば、垂直分割や水平分割によって、異なるバッファを同時に表示することが可能です。

gofmt

gofmtは、Go言語のソースコードを標準的なスタイルに自動的にフォーマットするツールです。Go言語のツールチェインに標準で含まれており、Goコミュニティではコードの可読性と一貫性を保つために広く利用されています。gofmtは、インデント、スペース、改行などをGoの公式スタイルガイドに従って整形します。

go-mode.el

go-mode.elは、EmacsでGo言語のコードを編集するためのメジャーモードです。シンタックスハイライト、インデント、gofmtとの連携など、Go言語開発を支援する様々な機能を提供します。Emacsのメジャーモードは、特定のプログラミング言語やファイルタイプに特化した編集機能を提供するLispコードの集合体です。

Emacs Lisp (Elisp)

Emacs Lispは、Emacsエディタの拡張言語です。Emacsのほぼ全ての機能はEmacs Lispで書かれており、ユーザーはEmacs Lispを使ってエディタの挙動をカスタマイズしたり、新しい機能を追加したりすることができます。go-mode.elもEmacs Lispで書かれています。

current-window-configurationset-window-configuration

Emacs Lispには、現在のウィンドウ構成を保存・復元するための関数が用意されています。

  • (current-window-configuration): 現在のフレームのウィンドウ構成を表すオブジェクトを返します。このオブジェクトは、どのバッファがどのウィンドウに表示されているか、ウィンドウのサイズや位置などの情報を含んでいます。
  • (set-window-configuration CONFIG): CONFIGで指定されたウィンドウ構成を現在のフレームに適用します。これにより、以前保存したウィンドウ構成を復元することができます。

技術的詳細

この問題の根本原因は、gofmtの実行プロセスにありました。gofmtは、Emacsのバッファの内容を外部プロセスに渡し、フォーマットされた結果を新しいバッファ(一時バッファ)に受け取ります。その後、元のバッファの内容をこの一時バッファの内容で置き換えます。

この処理自体は問題ありませんが、Emacsのshell-command-on-region関数(リージョンを外部コマンドにパイプし、その出力を処理する関数)が、場合によっては新しいウィンドウを開いてその出力を表示することがあります。そして、この新しいウィンドウの作成や、その後のバッファ内容の置き換え処理が、Emacsの既存のウィンドウ構成を破壊してしまうことがありました。特に、gofmtが成功した場合に、元のバッファの内容を置き換える際に、他のウィンドウが適切に扱われず、結果として閉じてしまうという挙動が見られました。

このコミットでは、gofmtを実行する前に現在のウィンドウ構成を保存し、gofmtの処理が完了した後にその保存した構成を明示的に復元するというアプローチを取ることで、この問題を解決しています。これにより、gofmtの実行中に一時的にウィンドウ構成が変更されたとしても、処理完了後には元の状態に戻るため、ユーザーの作業環境が維持されるようになります。

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

変更はmisc/emacs/go-mode.elファイル内のgofmt関数に集中しています。

--- a/misc/emacs/go-mode.el
+++ b/misc/emacs/go-mode.el
@@ -500,47 +500,49 @@ Useful for development work."
 
 ;;;###autoload
 (defun gofmt ()
- "Pipe the current buffer through the external tool `gofmt`.
+  "Pipe the current buffer through the external tool `gofmt`.
 Replace the current buffer on success; display errors on failure."
 
- (interactive)
- (let ((srcbuf (current-buffer)))
-   (with-temp-buffer
-     (let ((outbuf (current-buffer))
-           (errbuf (get-buffer-create "*Gofmt Errors*"))
-           (coding-system-for-read 'utf-8)    ;; use utf-8 with subprocesses
-           (coding-system-for-write 'utf-8))\n-       (with-current-buffer errbuf (erase-buffer))\n-       (with-current-buffer srcbuf\n-         (save-restriction\n-           (let (deactivate-mark)\n-             (widen)\n-             (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt"\n-                                               outbuf nil errbuf))\n-                 ;; gofmt succeeded: replace the current buffer with outbuf,\n-                 ;; restore the mark and point, and discard errbuf.\n-                 (let ((old-mark (mark t)) (old-point (point)))\n-                   (erase-buffer)\n-                   (insert-buffer-substring outbuf)\n-                   (goto-char (min old-point (point-max)))\n-                   (if old-mark (push-mark (min old-mark (point-max)) t))\n-                   (kill-buffer errbuf))\n-\n-               ;; gofmt failed: display the errors\n-               (display-buffer errbuf)))))\n-\n-       ;; Collapse any window opened on outbuf if shell-command-on-region\n-       ;; displayed it.\n-       (delete-windows-on outbuf)))))\
+  (interactive)
+  (let ((currconf (current-window-configuration)))\n+    (let ((srcbuf (current-buffer)))\n+      (with-temp-buffer\n+\t(let ((outbuf (current-buffer))\n+\t      (errbuf (get-buffer-create "*Gofmt Errors*"))\n+\t      (coding-system-for-read 'utf-8)    ;; use utf-8 with subprocesses\n+\t      (coding-system-for-write 'utf-8))\n+\t  (with-current-buffer errbuf (erase-buffer))\n+\t  (with-current-buffer srcbuf\n+\t    (save-restriction\n+\t      (let (deactivate-mark)\n+\t\t(widen)\n+\t\t(if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt"\n+\t\t\t\t\t\t  outbuf nil errbuf))\n+\t\t    ;; restore window config\n+\t\t    ;; gofmt succeeded: replace the current buffer with outbuf,\n+\t\t    ;; restore the mark and point, and discard errbuf.\n+\t\t    (let ((old-mark (mark t)) (old-point (point)))\n+\t\t      (set-window-configuration currconf)\n+\t\t      (erase-buffer)\n+\t\t      (insert-buffer-substring outbuf)\n+\t\t      (goto-char (min old-point (point-max)))\n+\t\t      (if old-mark (push-mark (min old-mark (point-max)) t))\n+\t\t      (kill-buffer errbuf))\n+\n+\t\t  ;; gofmt failed: display the errors\n+\t\t  (display-buffer errbuf)))))\n+\n+\t  ;; Collapse any window opened on outbuf if shell-command-on-region\n+\t  ;; displayed it.\n+\t  (delete-windows-on outbuf))))))\

コアとなるコードの解説

変更の核心は、gofmt関数の冒頭に現在のウィンドウ構成を保存する処理が追加され、gofmtが成功した場合にその構成を復元する処理が追加された点です。

  1. (let ((currconf (current-window-configuration))): gofmt関数が実行される直前に、現在のEmacsフレームのウィンドウ構成が(current-window-configuration)によって取得され、currconfという変数に保存されます。これにより、gofmtの処理中にウィンドウ構成が変更されたとしても、元の状態に戻すための「スナップショット」が保持されます。

  2. (set-window-configuration currconf): gofmtの実行が成功し、バッファの内容がフォーマットされた結果で置き換えられた後、(set-window-configuration currconf)が呼び出されます。これは、gofmt実行前に保存しておいたウィンドウ構成currconfをEmacsに適用し、元のウィンドウレイアウトを復元する役割を果たします。

このシンプルな変更により、gofmtの実行によって他のウィンドウが意図せず閉じてしまう問題が解決され、EmacsでのGo開発のワークフローが改善されました。

関連リンク

参考にした情報源リンク

  • (Web検索は行っていませんが、上記の解説はEmacs Lispの基本的な概念とgofmtの動作に関する一般的な知識に基づいています。)