Nearly there!!

This commit is contained in:
2026-10-07 08:21:36 +02:00
parent aeb9df3e0e
commit ee1bb98515
3 changed files with 218 additions and 168 deletions
+53 -114
View File
@@ -1,129 +1,68 @@
-- ~/.config/vis/build.lua
-- Runs the nearest build.sh, shows the output, and can jump to the first
-- Odin error. Loaded from visrc.lua.
-- Runs ./build.sh from vis's cwd (project root), logs the output for the
-- error picker (pickers.errors), and returns to vis on Enter/Esc.
local M = {}
-- args: list of flags passed through to build.sh, e.g. { 'run' }
-- last build's output; pickers.errors() reads it
M.log = '/tmp/vis-build.log'
-- single-quote a string for the shell
local function q(s) return "'" .. s:gsub("'", "'\\''") .. "'" end
-- write a raw escape sequence straight to the terminal
local function tty(s)
local t = io.open('/dev/tty', 'w')
if t then t:write(s) t:close() end
end
local function exists(path)
local f = io.open(path, 'r')
if f then f:close() end
return f ~= nil
end
-- args: flags passed through to build.sh, e.g. { 'run' }
function M.run(args)
-- find the nearest build.sh, searching upward from vis's working dir
local p = io.popen('pwd')
local dir = p and p:read('*l')
if p then p:close() end
while dir and dir ~= '' do
local f = io.open(dir .. '/build.sh', 'r')
if f then
f:close()
break
end
dir = dir:match('^(.*)/[^/]*$')
end
if not dir or dir == '' then
vis:info('No build.sh found in this directory or any parent')
if not exists('build.sh') then
vis:info('No build.sh in current directory')
return
end
-- save the current buffer so the build sees your latest changes
local win = vis.win
if win and win.file and win.file.modified and win.file.path then
vis:command('w')
end
-- save so the build sees your latest changes
local file = vis.win and vis.win.file
if file and file.modified and file.path then vis:command('w') end
-- shell-quote each flag and the directory
local quoted = {}
for _, a in ipairs(args) do
table.insert(quoted, "'" .. a:gsub("'", "'\\''") .. "'")
end
local argstr = table.concat(quoted, ' ')
local qdir = "'" .. dir:gsub("'", "'\\''") .. "'"
local display = ('./build.sh ' .. table.concat(args, ' ')):gsub('%s+$', '')
local qdisplay = "'" .. display:gsub("'", "'\\''") .. "'"
local out_file = os.tmpname()
local status_file = os.tmpname()
local answer_file = os.tmpname()
local parts = {}
for _, a in ipairs(args or {}) do parts[#parts + 1] = q(a) end
local argstr = table.concat(parts, ' ')
local script = table.concat({
'saved=$(stty -g)',
'stty sane',
'cd ' .. qdir .. ' || exit 1',
'printf "\\n\\033[1m$ %s\\033[0m (in %s)\\n\\n" ' .. qdisplay .. ' ' .. qdir,
'{ if [ -x ./build.sh ]; then ./build.sh ' .. argstr
.. '; else bash ./build.sh ' .. argstr .. '; fi; echo $? > \''
.. status_file .. "'; } 2>&1 | tee '" .. out_file .. "'",
"st=$(cat '" .. status_file .. "')",
'if [ "$st" = 0 ]; then',
' printf "\\n\\033[32m[build ok]\\033[0m enter: back to vis "',
'else',
' printf "\\n\\033[31m[build failed: exit %s]\\033[0m '
.. 'enter: back to vis | e + enter: jump to first error " "$st"',
'fi',
'read -r ans',
"printf '%s' \"$ans\" > '" .. answer_file .. "'",
'saved=$(stty -g); stty sane',
-- script runs the build on a pty (colors stay on) and logs it to M.log
'script -qefc ' .. q('bash ./build.sh ' .. argstr) .. ' ' .. q(M.log),
'st=$?',
[[printf '\n[exit %s] enter/esc: back to vis ' "$st"]],
[[while IFS= read -rsn1 k; do [ -z "$k" ] || [ "$k" = $'\e' ] && break; done]],
'stty "$saved"',
}, '\n')
-- leave the alternate screen so the build output lands in your normal
-- terminal scrollback, run it, then come back to vis
local tty = io.open('/dev/tty', 'w')
if tty then tty:write('\27[?1049l') tty:close() end
os.execute('{\n' .. script .. '\n} </dev/tty >/dev/tty 2>&1')
tty = io.open('/dev/tty', 'w')
if tty then tty:write('\27[?1049h') tty:close() end
-- leave the alt screen and clear it, so each run starts fresh and the
-- output lands in normal scrollback; run; come back to vis
tty('\27[?1049l\27[H\27[2J')
os.execute('bash -c ' .. q(script) .. ' </dev/tty >/dev/tty 2>&1')
tty('\27[?1049h')
vis:redraw()
-- read back exit status and the key you answered with
local f = io.open(status_file, 'r')
local status = f and f:read('*l')
if f then f:close() end
f = io.open(answer_file, 'r')
local answer = f and f:read('*l')
if f then f:close() end
-- first Odin error: `file.odin(12:5)` (default) or `file.odin:12:5` (unix)
local path, line, col
f = io.open(out_file, 'r')
if f then
for l in f:lines() do
path, line, col = l:match('^%s*(.-%.odin)%((%d+):(%d+)%)')
if not path then
path, line, col = l:match('^%s*(.-%.odin):(%d+):(%d+)')
end
if path then break end
end
f:close()
end
if path and path:sub(1, 1) ~= '/' then path = dir .. '/' .. path end
os.remove(out_file)
os.remove(status_file)
os.remove(answer_file)
if status == '0' then
vis:info('build ok: ' .. display)
return
end
if not path then
vis:info('build failed: exit ' .. (status or '?'))
return
end
if answer ~= 'e' then
vis:info(string.format('build failed: first error at %s:%s', path, line))
return
end
-- jump to the error: open the file unless it's already the current one
local current = vis.win and vis.win.file and vis.win.file.path
if current ~= path then
if vis.win.file.modified then
vis:info('Buffer has unsaved changes — :w first (or use :e! manually)')
return
end
vis:command(string.format("e '%s'", path))
end
vis.win.selection:to(tonumber(line), tonumber(col))
end
return M
-- ---------------------------------------------------------------------------
-- events
-- ---------------------------------------------------------------------------
-- re-index tags in the background after saving an .odin file
-- (build.sh -t re-scans src and merges it with the cached stdlib tags)
vis.events.subscribe(vis.events.FILE_SAVE_POST, function(file, path) -- luacheck: no unused args
if path and path:match('%.odin$') and exists('build.sh') then
os.execute('bash ./build.sh -t </dev/null >/dev/null 2>&1 &')
end
end)
return M