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

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

このコミットは、VimエディタのGo言語ファイルタイプ検出スクリプト(misc/vim/ftdetect/gofiletype.vim)における、fileencodingsおよびfileformatsというグローバルオプションの扱いを改善するものです。GoファイルをVimで開く際に、これらのグローバル設定が意図せず変更され、他のファイルタイプに影響を与える問題を解決し、Vimの挙動をより予測可能にします。

コミット

commit 2fc5dd66dfb36bfdce1f260e55bba07050a21423
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date:   Wed Mar 14 18:43:01 2012 +1100

    misc/vim: restore fileencodings.
    Currently, ftdetect/gofiletype.vim set fileencodings to open the file as
    utf-8 encoding event if the file does not contain multibyte characters.
    But fileencodings is global option.
    
    $ vim foo.txt
    :set fileencodings
    utf-8,ucs-bom,cp932
    
    $ vim foo.go
    :set fileencodings
    utf-8
    
    This change restore fileencodings before opening the file.
    Also added specify fileformats=unix.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/5718045

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

https://github.com/golang/go/commit/2fc5dd66dfb36bfdce1f260e55bba07050a21423

元コミット内容

misc/vim: restore fileencodings.

現在、ftdetect/gofiletype.vimは、ファイルにマルチバイト文字が含まれていない場合でも、ファイルをUTF-8エンコーディングとして開くためにfileencodingsを設定しています。しかし、fileencodingsはグローバルオプションです。

例: $ vim foo.txt :set fileencodings utf-8,ucs-bom,cp932

$ vim foo.go :set fileencodings utf-8

この変更は、ファイルを開く前にfileencodingsを復元します。 また、fileformats=unixの指定も追加しました。

R=golang-dev, dsymonds CC=golang-dev https://golang.org/cl/5718045

変更の背景

このコミットの背景には、Vimのグローバルオプションであるfileencodingsの挙動と、Go言語のファイルタイプ検出スクリプト(ftdetect/gofiletype.vim)の相互作用による問題がありました。

従来のftdetect/gofiletype.vimスクリプトは、Goファイルを開く際に、そのファイルのエンコーディングを確実にUTF-8として扱うために、Vimのfileencodingsオプションをutf-8に設定していました。Go言語のソースコードは通常UTF-8で記述されるため、これはGoファイル自体を正しく読み込む上では理にかなった設定でした。

しかし、fileencodingsはVimのグローバルオプションです。これは、一度設定されると、Vimセッション全体に影響を及ぼすことを意味します。したがって、ユーザーがGoファイルを開いた後、別の種類のファイル(例えば、Shift_JISやEUC-JPでエンコードされたテキストファイル)を開いた場合、fileencodingsutf-8に固定されたままであるため、Vimがそのファイルを正しくエンコーディングを判別できず、文字化けが発生する可能性がありました。

コミットメッセージの例がこの問題を明確に示しています。

  • vim foo.txt(通常のテキストファイル)を開いた後、:set fileencodingsを実行すると、ユーザーのデフォルト設定(例: utf-8,ucs-bom,cp932)が表示されます。
  • しかし、vim foo.goを開いた後、:set fileencodingsを実行すると、utf-8に変わってしまっています。

この挙動は、Goファイルを開くたびにVimのグローバルなエンコーディング検出設定が上書きされ、ユーザーの期待するVimの挙動を損なうという問題を引き起こしていました。このコミットは、Goファイルを開く際に一時的にfileencodingsを設定し、Goファイルの処理が完了した後に元の設定に戻すことで、このグローバルオプションの副作用を解消することを目的としています。

また、fileformats=unixの追加は、Go言語のコードが通常Unix形式の改行コード(LF)を使用することを明示的に指定し、異なるOS環境(WindowsのCRLFなど)で編集された場合に発生しうる改行コードの問題を防ぐためのものです。

前提知識の解説

このコミットを理解するためには、以下のVimの概念とVimscriptの知識が必要です。

  1. fileencodings (fencs):

    • Vimがファイルを読み込む際に、どのエンコーディングで試行するかを順序付けたリストです。Vimはリストの先頭から順にエンコーディングを試行し、正しく読み込めたと判断したエンコーディングを使用します。
    • 例: set fileencodings=utf-8,ucs-bom,cp932 は、まずUTF-8を試し、次にUCS-BOM、最後にCP932を試すことを意味します。
    • これはグローバルオプションであり、一度設定されるとVimセッション全体に影響します。
  2. fileformat (ff):

    • Vimがファイルを書き込む際に使用する改行コードを指定するオプションです。
    • 主な値: unix (LF), dos (CRLF), mac (CR)。
    • これはバッファローカルオプションであり、各バッファ(開いているファイル)ごとに異なる設定を持つことができます。
  3. ftdetect (Filetype Detection):

    • Vimがファイルの拡張子や内容に基づいてファイルタイプを自動的に検出するための仕組みです。
    • 通常、~/.vim/ftdetect/やVimのランタイムパス内のftdetect/ディレクトリに配置されたVimscriptファイル(例: gofiletype.vim)によって定義されます。
    • これらのスクリプトは、Vimがファイルを開く際に実行され、filetypeオプションを設定します。
  4. autocmd (au):

    • Vimの特定のイベント(例: ファイルを開く、バッファを保存する)が発生したときに、指定されたコマンドを自動的に実行するための機能です。
    • 構文: au {event} {pattern} {command}
    • このコミットで使われているイベント:
      • BufNewFile: 新しいファイルを作成する直前。
      • BufRead: 既存のファイルを読み込む直前。
      • BufReadPost: 既存のファイルを読み込んだ直後。
  5. setsetlocal:

    • set: グローバルオプションを設定します。
    • setlocal: 現在のバッファのローカルオプションを設定します。グローバルオプションに対してsetlocalを使用すると、そのバッファにのみ有効なローカル値が設定され、グローバル値は変更されません。
  6. Vimscriptの変数:

    • let: 変数を宣言・代入します。
    • s:: スクリプトローカル変数(現在のスクリプトファイル内でのみ有効)。他のスクリプトやユーザー定義関数と名前が衝突するのを防ぎます。
    • &g:: グローバルオプションの現在の値を取得します。例: &g:fileencodings はグローバルなfileencodingsの値を取得します。
  7. Vimscriptの関数:

    • function! {name}() ... endfunction: ユーザー定義関数を宣言します。!は、同名の関数が既に存在する場合に上書きすることを許可します。
    • call {function_name}(): 関数を呼び出します。

これらの知識を組み合わせることで、コミットがどのようにVimの挙動を制御しているかを深く理解できます。

技術的詳細

このコミットは、Vimのfileencodingsオプションがグローバルであるという特性に起因する問題を、Vimのautocmdとスクリプトローカル変数、そしてユーザー定義関数を巧みに利用して解決しています。

主要な技術的アプローチは以下の通りです。

  1. グローバルオプションの一時的な保存と復元:

    • Goファイルを開く直前(BufReadイベント)に実行されるs:gofiletype_pre()関数内で、現在のグローバルなfileformatsfileencodingsの値をスクリプトローカル変数(s:current_fileformatss:current_fileencodings)に保存します。
    • その後、fileencodingsutf-8に、fileformatsunixに設定します。これにより、Goファイルは確実にUTF-8エンコーディングとUnix改行で読み込まれます。
    • Goファイルの読み込みが完了した後(BufReadPostイベント)に実行されるs:gofiletype_post()関数内で、保存しておいた元のグローバルなfileformatsfileencodingsの値をVimのグローバルオプションに復元します。
  2. setlocalの適切な使用:

    • au BufNewFile *.go setlocal filetype=go fileencoding=utf-8 fileformat=unix の行では、新しいGoファイルを作成する際に、filetypefileencodingfileformatバッファローカルに設定しています。これにより、これらの設定が他のバッファに影響を与えることはありません。特にfileencodingは、fileencodingsとは異なり、バッファローカルに設定できるため、新しいファイルに対しては直接設定しています。
  3. BufReadBufReadPostの使い分け:

    • BufReadイベントはファイルが読み込まれる直前に発生するため、このタイミングでs:gofiletype_pre()を呼び出し、グローバルオプションを一時的に変更します。
    • BufReadPostイベントはファイルが読み込まれた直後に発生するため、このタイミングでs:gofiletype_post()を呼び出し、グローバルオプションを元の状態に戻します。このシーケンスにより、Goファイルの読み込み中のみ特定のグローバル設定が適用され、読み込み完了後にはVimが通常の挙動に戻ります。

このアプローチにより、Goファイルの読み込みに必要なエンコーディングと改行コードの設定を強制しつつ、Vimの他のファイルタイプに対するグローバルなfileencodings設定への副作用を完全に排除しています。これは、Vimscriptにおけるグローバルオプションの管理と、イベント駆動型プログラミングの典型的なパターンを示しています。

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

--- a/misc/vim/ftdetect/gofiletype.vim
+++ b/misc/vim/ftdetect/gofiletype.vim
@@ -1 +1,23 @@
-au BufRead,BufNewFile *.go set filetype=go fileencoding=utf-8 fileencodings=utf-8
+\" We take care to preserve the user\'s fileencodings and fileformats,\n+\" because those settings are global (not buffer local), yet we want\n+\" to override them for loading Go files, which are defined to be UTF-8.\n+let s:current_fileformats = \'\'\n+let s:current_fileencodings = \'\'\n+\n+\" define fileencodings to open as utf-8 encoding even if it\'s ascii.\n+function! s:gofiletype_pre()\n+  let s:current_fileformats = &g:fileformats\n+  let s:current_fileencodings = &g:fileencodings\n+  set fileencodings=utf-8 fileformats=unix\n+  setlocal filetype=go\n+endfunction\n+\n+\" restore fileencodings as others\n+function! s:gofiletype_post()\n+  let &g:fileformats = s:current_fileformats\n+  let &g:fileencodings = s:current_fileencodings\n+endfunction\n+\n+au BufNewFile *.go setlocal filetype=go fileencoding=utf-8 fileformat=unix\n+au BufRead *.go call s:gofiletype_pre()\n+au BufReadPost *.go call s:gofiletype_post()\n```

## コアとなるコードの解説

変更された`misc/vim/ftdetect/gofiletype.vim`スクリプトの各行について解説します。

**削除された行:**
```vim
-au BufRead,BufNewFile *.go set filetype=go fileencoding=utf-8 fileencodings=utf-8

この行は、Goファイル(.go)を読み込むか新規作成する際に、ファイルタイプをgoに、ファイルエンコーディングをutf-8に、そしてグローバルなfileencodingsutf-8に設定していました。このグローバルなfileencodingsの設定が問題の原因でした。

追加された行:

+" We take care to preserve the user's fileencodings and fileformats,
+" because those settings are global (not buffer local), yet we want
+" to override them for loading Go files, which are defined to be UTF-8.

コメント行です。ユーザーのfileencodingsfileformats設定がグローバルであるため、GoファイルをUTF-8として読み込むためにそれらを一時的に上書きし、後で復元する必要があることを説明しています。

+let s:current_fileformats = ''
+let s:current_fileencodings = ''

スクリプトローカル変数s:current_fileformatss:current_fileencodingsを初期化しています。これらの変数は、Goファイルを開く前に現在のグローバルなfileformatsfileencodingsの値を一時的に保存するために使用されます。s:プレフィックスは、これらの変数がこのスクリプト内でのみ有効であることを示し、他のVimscriptとの名前の衝突を防ぎます。

+" define fileencodings to open as utf-8 encoding even if it's ascii.
+function! s:gofiletype_pre()
+  let s:current_fileformats = &g:fileformats
+  let s:current_fileencodings = &g:fileencodings
+  set fileencodings=utf-8 fileformats=unix
+  setlocal filetype=go
+endfunction

s:gofiletype_pre()というスクリプトローカル関数を定義しています。この関数は、Goファイルを読み込む直前に呼び出されます。

  • let s:current_fileformats = &g:fileformats: 現在のグローバルなfileformatsの値をs:current_fileformatsに保存します。&g:はグローバルオプションの値を参照するためのVimscriptの構文です。
  • let s:current_fileencodings = &g:fileencodings: 現在のグローバルなfileencodingsの値をs:current_fileencodingsに保存します。
  • set fileencodings=utf-8 fileformats=unix: グローバルなfileencodingsutf-8に、グローバルなfileformatsunixに設定します。これにより、Goファイルが確実にUTF-8エンコーディングとUnix改行で読み込まれるようになります。
  • setlocal filetype=go: 現在のバッファのファイルタイプをgoに設定します。これはバッファローカルな設定であり、他のバッファには影響しません。
+" restore fileencodings as others
+function! s:gofiletype_post()
+  let &g:fileformats = s:current_fileformats
+  let &g:fileencodings = s:current_fileencodings
+endfunction

s:gofiletype_post()というスクリプトローカル関数を定義しています。この関数は、Goファイルの読み込みが完了した直後に呼び出されます。

  • let &g:fileformats = s:current_fileformats: s:gofiletype_pre()で保存しておいた元のグローバルなfileformatsの値をVimのグローバルオプションに復元します。
  • let &g:fileencodings = s:current_fileencodings: s:gofiletype_pre()で保存しておいた元のグローバルなfileencodingsの値をVimのグローバルオプションに復元します。
+au BufNewFile *.go setlocal filetype=go fileencoding=utf-8 fileformat=unix

新しいGoファイル(.go)を作成する際に実行される自動コマンドです。

  • setlocal filetype=go: ファイルタイプをgoに設定します。
  • fileencoding=utf-8: 新しいファイルのエンコーディングをutf-8に設定します。これはバッファローカルなオプションです。
  • fileformat=unix: 新しいファイルの改行コードをunix形式に設定します。これもバッファローカルなオプションです。
+au BufRead *.go call s:gofiletype_pre()

既存のGoファイル(.go)を読み込む直前(BufReadイベント)に、s:gofiletype_pre()関数を呼び出す自動コマンドです。これにより、ファイル読み込み前にグローバルオプションが一時的に変更されます。

+au BufReadPost *.go call s:gofiletype_post()

既存のGoファイル(.go)の読み込みが完了した直後(BufReadPostイベント)に、s:gofiletype_post()関数を呼び出す自動コマンドです。これにより、ファイル読み込み後にグローバルオプションが元の状態に復元されます。

これらの変更により、Goファイルの読み込み時のみ一時的にVimのグローバル設定が変更され、読み込み完了後には元の設定に戻るため、他のファイルタイプへの影響がなくなります。

関連リンク

参考にした情報源リンク