-- ~/.config/vis/build.lua -- Runs the nearest build.sh, shows the output, and can jump to the first -- Odin error. Loaded from visrc.lua. local M = {} -- args: list of 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') 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 -- 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 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 .. "'", '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 2>&1') tty = io.open('/dev/tty', 'w') if tty then tty:write('\27[?1049h') tty:close() end 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