Nearly there!!
This commit is contained in:
+53
-114
@@ -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
|
||||
Reference in New Issue
Block a user