vimでzigを書くと構文エラー(QuickFix)を出してくれる仕組みを理解し色を変更する

vim

環境

当方の環境は以下である。
zig version: 0.16.0
vim version: 9.1
Windows 11 + x86_64 + wsl2 + ubuntu24.04 + zsh

zigはどのエディタで書く?

当方はvimで書いている。それは、zigに限らず、普段使いのエディタがvimだからである。

vimのQuickFix

標準機能として存在しており、vimのversionを確認すると表示される+/-対象では無い。エラーや検索結果の該当ファイルや行、メッセージ等を一覧で表示し、Enterで実際の該当箇所に飛ぶことができる。以下コマンドで説明を呼べる。

vim

:help quickfix

QuickFixの例を確認するため、以下のファイルを書き、保存する。

zig: skel.zig

const std = @import("std");
const print = std.debug.print;

pub fn main(init: std.process.Init) !void {

}

保存時、以下のような挙動となり、QuickFixが開く。正確には、QuickFixの機能を使っているが、その該当ウィンドウが独立して持っているリストを使用している、Location Listと呼ばれるものが開く。

vim: skel.zig

:wq
zig fmt returned error
"skel.zig" 6L, 107B written
Press ENTER or type command to continue

Enterを押すと以下に遷移する。

vim: [Location List] Errors

skel.zig|4 col 13| : error: unused function parameter

1行目でEnterを押すと、上述のskel.zigの4行目に記述された、main関数の引数に指定したinitの、冒頭のiの位置にカーソルが飛ぶ。

QuickFixが動く仕組み

順に追っていく。

vim: skel.zig

:filetype
filetype detection:ON  plugin:ON  indent:ON

これにより、ファイルタイプの同定後に、プラグイン処理が走ることが分かる。

vim: skel.zig

:verbose autocmd * *.zig
--- Autocommands ---
filetypedetect  BufNewFile
    *.zig     setf zig
        Last set from /usr/share/vim/vim91/filetype.vim line 2025
filetypedetect  BufRead
    *.zig     setf zig
        Last set from /usr/share/vim/vim91/filetype.vim line 2025
Press ENTER or type command to continue

実際に、新規ファイル作成か既存ファイル読み込み時に、拡張子が.zigであるなら、filetypeをzigに設定している。setf(iletpye)と似た設定にset filetypeが存在するが、setfの場合は重複設定を回避する機能が備わっている。

vim: skel.zig

:set filetype?
  filetype=zig

確かにfiletypeがzigと設定されていることが分かる。

vim: skel.zig

:verbose autocmd BufWritePre
vim-zig  BufWrite
    <buffer=1>
              if get(g:, 'zig_fmt_autosave', 1) | call zig#fmt#Format() | endif
        Last set from /usr/share/vim/vim91/ftplugin/zig.vim line 61
Press ENTER or type command to continue

保存時に処理が走っているので、BufWritePreを確認する。filetypeのpluginがonなのでftplugin/zig.vimから設定が読み込まれていることが分かる。

vim: /usr/share/vim/vim91/ftplugin/zig.vim(抜粋、コメント追記)

抜粋のため、行番号も記載した。

 59 augroup vim-zig
 60     autocmd! * <buffer> "このバッファのみ、vim-zigのautocmdを全て削除
 61     autocmd BufWritePre <buffer> if get(g:, 'zig_fmt_autosave', 1) | call zig#fmt#Format() | endif "このバッファのみ、バッファ書き込み直前(:wで保存時など)に、g:zig_fmt_autosave=0の設定が無ければ、autoload/zig/fmt.vimのFormat()処理を実行
 62 augroup END

まとめると、保存時にautoload/zig/fmt.vimのFormat()を実行する。

vim: /usr/share/vim/vim91/autoload/zig/fmt.vim(抜粋) 注:BSDライセンス

抜粋のため、行番号も記載した。

  9 function! zig#fmt#Format() abort
"・・・省略・・・
 18   let cmdline = 'zig fmt --stdin --ast-check'
 19   let current_buf = bufnr('')
"・・・省略・・・
 21   " The formatted code is output on stdout, the errors go on stderr.
 22   if exists('*systemlist')
 23     silent let out = systemlist(cmdline, current_buf)
"・・・省略・・・
 37   let err = v:shell_error
"・・・省略・・・
 55   elseif get(g:, 'zig_fmt_parse_errors', 1)
 56     let errors = s:parse_errors(expand('%'), out)
 57
 58     call setloclist(0, [], 'r', {
 59         \ 'title': 'Errors',
 60         \ 'items': errors,
 61         \ })
"・・・省略・・・
 66     " Open the loclist, but only if there's at least one error to show.
 67     execute 'silent! lwindow ' . win_height
 68   endif
"・・・省略・・・
 72   if err != 0
 73     echohl Error | echomsg "zig fmt returned error" | echohl None
 74     return
 75   endif
"・・・省略・・・
 80 endfunction
"・・・省略・・・
 82 " parse_errors parses the given errors and returns a list of parsed errors
 83 function! s:parse_errors(filename, lines) abort
 84   " list of errors to be put into location list
 85   let errors = []
 86   for line in a:lines
 87     let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)')
 88     if !empty(tokens)
 89       call add(errors,{
 90             \"filename": a:filename,
 91             \"lnum":     tokens[2],
 92             \"col":      tokens[3],
 93             \"text":     tokens[4],
 94             \ })
 95     endif
 96   endfor
 97
 98   return errors
 99 endfunction

まとめると、zig fmt --stdin --ast-checkコマンドを実行し、エラーを構文解析してLocation Listに入れ、表示している。

zsh

実際にコマンドを叩くと、確かに同一のエラーが表示される。

% zig fmt --stdin --ast-check < skel.zig
<stdin>:4:13: error: unused function parameter
pub fn main(init: std.process.Init) !void {
            ^~~~
%

続いて、skel.zigの保存時に開くLocation Listについて確認する。

vim: [Location List] Errors

:set filetype?
  filetype=qf

Location ListはQuickFixの機能を使用していることが分かる。

vim: [Location List] Errors

:messages
"skel.zig" 6L, 119B
zig fmt returned error
"skel.zig" 6L, 119B written
Press ENTER or type command to continue

zig fmtが実行されていたことが分かる。

vim: [Location List] Errors

:verbose set errorformat?
  errorformat=%*[^"]"%f"%*\D%l: %m,"%f"%*\D%l: %m,%-Gg%\?make[%*\d]: *** [%f:%l:%m,%-Gg%\?make: *** [%f:%l:%m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each
 function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\,,%-GIn file included from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%
*[ ]from %f:%l:,%-G%*[ ]from %f:%l\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,"%f"\, line %l%*\D%c%*[^ ] %m,%D%*\a[%*\d]: Entering directory %*[`']%f',%X%*\a[%*\d]: Leaving direct
ory %*[`']%f',%D%*\a: Entering directory %*[`']%f',%X%*\a: Leaving directory %*[`']%f',%DMaking %*\a in %f,%f|%l| %m

%f:%l:%c:%mに該当する。

カーソル行のハイライトが明るい黄色でエラーメッセージが読めない

QickFixは色んなことができそうでとてもわくわくするのだが、如何せん、ハイライトが明るい黄色でエラーのメッセージ部が白色なので、読めないにゅんちゅ。そこで、以下のコマンドでハイライト一覧から色が一致する設定を探した。

vim

:verbose highlight
"・・・省略・・・
QuickFixLine   xxx links to Search
        Last set from /usr/share/vim/vim91/colors/default.vim line 15
"・・・省略・・・

色が一致しQuickFixに関係する設定として、QuickFixLineが見つかった。Searchの設定を流用しているようだ。そこで、Searchを確認する。

vim

:verbose highlight Search
Search         xxx term=reverse ctermbg=11 guibg=Yellow
        Last set from /usr/share/vim/vim91/colors/default.vim line 15
Press ENTER or type command to continue

11というのは、ANSIカラーに対応した数字であり、明るい黄色を指す。以下の表はANSIエスケープシーケンスでSGR(Select Graphic Rendition)コマンドの値を示す。SGRのフォーマットはESC[値mであり、実際にESCは文字コード:0x1bのため例えば8進数表記で\033と記述する(例:\033[93m)。ANSIカラーは0~7の8色だが、vimでは明るい色にも連番を振っているため、例えば11であれば8以上なので明るい色であり、11-8=3で黄色を指すということになる。

namefgbgvim
Black30400
Red31411
Green32422
Yellow33433
Blue34444
Magenta35455
Cyan36466
White37477
Bright Black (Gray)901008
Bright Red911019
Bright Green9210210
Bright Yellow9310311
Bright Blue9410412
Bright Magenta9510513
Bright Cyan9610614
Bright White9710715

ここで、背景が明るい黄色で文字が白色だと読めないため、以下のように個人設定ファイルにコマンドを追記し、今回対象となるctermの背景のみ暗い黄色に上書き設定する。

vim: ~/.vimrc

highlight Search ctermbg=3

それでは再度、該当ソースファイルを開き保存することで、QuickFixを確認する。

読めるようになったにゅんちゅ!