Как ускорить разработку на Zig в Vim

Пока сервер LSP для Zig оставляет желать лучшего, можно вспомнить технологии прошлого и упростить себе жизнь. В этой заметке я хочу поделиться тем, как фича make в vim упрощает мне работу с zig.
Начнем с примера:
const std = @import("std");

pub fn main() !void {
     std.io.getStdOut().writer().print("Hi!", .{});
}
На момент написания статьи, zls не видел проблемы в пропущенном try. Узнавать об ошибке приходилось уже после компиляции вне редактора:
$ zig run ./example.zig
example.zig:4:39: error: error union is ignored
     std.io.getStdOut().writer().print("Hi!", .{});
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
example.zig:4:39: note: consider using 'try', 'catch', or 'if'
referenced by:
    callMain: ~/.zvm/0.13.0/lib/std/start.zig:524:32
    callMainWithArgs: ~/.zvm/0.13.0/lib/std/start.zig:482:12
    remaining reference traces hidden; 
    use '-freference-trace' to see all reference traces
Чем может нам помочь (neo)vim в сложившийся ситуации? Он может:
  1. Запустить компиляцию проекта
  2. Собрать все ошибки компиляции и определить места их возникновения
  3. Построить quickfixlist с ошибками компиляции
Вот так это может выглядеть:
Теперь все по порядку. В (neo)vim есть команда :make. Эта команда запускает в фоне процесс, определенный в переменной makeprg.

Команда :make по сути алиас для :!{makeprg} [arguments] {shellpipe} {errorfile}.
Где:

  1. makeprg строка с описанием запускаемого процесса;
  2. arguments строка с аргументами для запускаемого процесса переданными команде в редакторе;
  3. shellpipe строка описывающая пайп для результата выполнения программы;
  4. errorfile файл в который будет направлен вывод программы через указанный пайп;

По умолчанию это выглядит примерно так: make % 2>&1 | tee errors.err. Обратите внимание на % в инструкции, это стандартная подстановка имени текущего буффера. Комнда make допускает чуть более сложные подстановки, детальнее о которых можно почитать в документации :h make. В результате выполнения команды, содержимое файла errors.err становится содержимым quickfixlist. При этом строки парсятся согласно опции errorformat, которая позволяет регекспом вытащить конкретные позиции ошибок. Детали лучше искать в help файле :h errorformat, а здесь я покажу уже готовое решение для того, чтобы получить координаты ошибок для вывода компилятора zig:
%f:%l:%c:\ error:\ %m

Полная конфигурация для zig может выглядеть так (обратите внимание на экранирование символов):

:set makeprg=zig\ build-exe\ %
:set shellpipe=2>&1\|grep\ ':\ error:'\|tee
:set efm=%f:%l:%c:\ error:\ %m
Работоспособность аргументов можно проверить с помощью команды :cexpr которая строит quickfixlist из вывода переданного выражения:
" system выполнит переданную строку как фоновый процесс, аналогично :! 
" bel copen разделит окно горизонтально и откроет quickfixlist в нижнем окне
:cexpr system("zig build-exe " .. expand("%:t") .. " 2>&1 | grep ': error:' | tee") | bel copen
Небольшая проблема заключается в том, что в зависимости от проекта, параметры запуска для zig могут отличаться. Проблему можно решить, если в makeprg записать просто "zig", а build-exe, или build-lib, или просто build передавать аргументами команды :make.

Альтернативным решением может быть использование :compiler фичи, которая позволяет собрать все необходимые опции в одном файле, а затем переключаться между набором опций. К примеру, можно определить один набор опций для компиляции отдельных файлов и другой набор опций для сборки проекта командой zig build:

" Zig compiler for a single file
"~/.config/nvim/compiler/zig_exe.vim
if exists('current_compiler')
  finish
endif
runtime compiler/zig.vim
let current_compiler = 'zig_exe'

let s:save_cpo = &cpo
set cpo&vim


if exists(':CompilerSet') != 2
  command -nargs=* CompilerSet setlocal 
endif

CompilerSet makeprg=zig\ build-exe\ \"%\"\ \$*
CompilerSet shellpipe=2>&1\ \|\ grep\ ':\ error:'\ \|\ tee
CompilerSet efm=%f:%l:%c:\ error:\ %m

let &cpo = s:save_cpo
unlet s:save_cpo
" Zig compiler for a whole project
"~/.config/nvim/compiler/zig_build.vim
if exists('current_compiler')
  finish
endif
runtime compiler/zig.vim
let current_compiler = 'zig_build'

let s:save_cpo = &cpo
set cpo&vim


if exists(':CompilerSet') != 2
  command -nargs=* CompilerSet setlocal 
endif

CompilerSet makeprg=zig\ build\ test
CompilerSet shellpipe=2>&1\ \|\ grep\ ':\ error:'\ \|\ tee
CompilerSet efm=%f:%l:%c:\ error:\ %m

let &cpo = s:save_cpo
unlet s:save_cpo
Теперь можно быстро переключаться между настройками с помощью :compiler zig_exe или :compiler zig_build.

Обратите внимание, что в vim уже определены несколько настроек компилятора для zig: zig.vim, zig_build.vim, zig_build_exe.vim, zig_test.vim

И напоследок, можно воспользоваться алиасом для комманд, чтобы упростить себе жизнь и сократить набираемый текст:


  " алиас :mk переключает настройки команды :make на сборку проекта zig целиком, 
  " запускает :make в "silent mode",
  " и открывает quickfixlist внизу текущего окна
  
  :ca mk compiler zig_build | silent make | bel copen

Комментариев нет: