go-ts-modeでgofmt(goimports)の設定を修正した
TL;DR Go開発をgo-ts-mode(tree-sitterベース)に寄せたら、問題が発生していた。 1つ目: gofmt(goimportsで代替)を実行した直後は正しく整列されているのに、TABキーを1回押すとインデントが縮む。 2つ目: before-save-hookにgofmt-before-saveを登録しても、保存時に一切フォーマットされない。しかもエラーは出ない。 両方とも「go-mode.el(tree-sitter以前のパッケージ)がgo-ts-modeの存在を想定していない」ことが根っこの原因だった。 環境 Emacs、Go開発はgo-ts-mode(tree-sitterベース)を使用 OS: Arch Linux gofmt-commandは"goimports"に上書き設定(gofmtの代わりにgoimportsを使う) 対象ファイル例: internal/api/server.go(構造体フィールドやjsonタグを縦に整列させるコードスタイル) 試したこと 症状1: 手動でTABキーを押すとインデントが縮む 手動でM-x gofmtを実行して整形した直後のファイルは正しく整列されているが、既存行にカーソルを置いてTABキーを押すと、その行のインデントが浅くなる。 思い出したがこれはかなり前から存在してる不備だ。 ちょうどよかったのでここで直してみよう。 切り分けは以下の手順で進めた。 M-x describe-variable RET indent-tabs-mode RET → 値はt。タブを使う設定として正常。 対象行の文字をC-u C-x =で確認 → タブ文字であることを確認(Char: TAB)。 タブの表示幅を疑い、行頭でタブ1個分右に移動してからC-x = → column=8。tab-widthも8(Emacsのデフォルト)。表示幅そのものは正常。 「短くなった行」と「正常に見える上の行」でM-m(back-to-indentation)からのC-x =を比較 → 両方column=16で一致。インデント幅そのものは揃っていた。 ここで「タブ幅の表示問題ではない」と判明。 真因は次の通り。gofmtは構造体フィールドやjsonタグを縦に整列させるために、構文的に必要な数以上のタブを意図的に挿入する(整列目的の余分なタブ)。一方go-ts-modeはtree-sitterによる構文解析で「その行の構文的必要インデント深さ」だけを計算し、TABキー押下時に行頭の空白をその計算値に置き換える。gofmtが足した「整列用の余分なタブ」は構文的には不要なので、go-ts-modeがそれを認識せず削ってしまう。 これはEmacs設定のバグではなく仕様通りの動作だ。go-ts-modeには「整列のための余分な空白」を認識する機能がそもそもない。 調査の途中で、副原因ももう1つ見つかった。go-ts-mode-indent-offsetが4に設定されていた。 (use-package treesit :straight (:type built-in) :config (setq treesit-font-lock-level 4) (setq go-ts-mode-indent-offset 4)) go-ts-mode-indent-offset = 4だと「1インデントレベル=4列」で計算される。一方tab-widthはEmacsデフォルトの8のまま。gofmtの出力は「1インデントレベル=タブ1個(実質8列相当)」が前提なので、この不一致でTABキー押下時の再インデント計算が4列刻みでずれる。 対処はgo-ts-mode-indent-offsetをtab-widthと同じ値に揃えるだけ。 ;; 変更前 (setq go-ts-mode-indent-offset 4) ;; 変更後(効果確認済み) (setq go-ts-mode-indent-offset 8) これで症状は解消した。 ...