local luadev_inspect = require'luadev.inspect' local a = vim.api if _G._luadev_mod == nil then _G._luadev_mod = {execount = 0} end local s = _G._luadev_mod local function create_buf() if s.buf ~= nil then return end local buf = a.nvim_create_buf(true,true) a.nvim_buf_set_name(buf, "[nvim-lua]") s.buf = buf end local function open_win() if s.win and a.nvim_win_is_valid(s.win) and a.nvim_win_get_buf(s.win) == s.buf then return end create_buf() local w0 = a.nvim_get_current_win() a.nvim_command("split") local w = a.nvim_get_current_win() a.nvim_win_set_buf(w,s.buf) a.nvim_set_current_win(w0) s.win = w end local function dosplit(str, delimiter) local result = { } local from = 1 local delim_from, delim_to = string.find( str, delimiter, from ) while delim_from do table.insert( result, string.sub( str, from , delim_from-1 ) ) from = delim_to + 1 delim_from, delim_to = string.find( str, delimiter, from ) end table.insert( result, string.sub( str, from ) ) return result end local function splitlines(str) return vim.split(str, "\n", true) end local function append_buf(lines, hl, penda) if s.buf == nil then create_buf() end local l0 = a.nvim_buf_line_count(s.buf) if type(lines) == type("") then lines = splitlines(lines) end if penda then local linelen = #(a.nvim_buf_get_lines(s.buf, l0-1, l0, false)[1]) a.nvim_buf_set_text(s.buf, l0-1, linelen, l0-1, linelen, lines) else a.nvim_buf_set_lines(s.buf, l0, l0, true, lines) end local l1 = a.nvim_buf_line_count(s.buf) if hl ~= nil then for i = l0, l1-1 do a.nvim_buf_add_highlight(s.buf, -1, hl, i, 0, -1) end end local curwin = a.nvim_get_current_win() for _,win in ipairs(a.nvim_list_wins()) do if a.nvim_win_get_buf(win) == s.buf and win ~= curwin then a.nvim_win_set_cursor(win, {l1, 1e9}) end end return l0 end local function luadev_print(...) local strs = {} local args = {...} for i = 1,select('#', ...) do strs[i] = tostring(args[i]) end append_buf(table.concat(strs, ' ')) end local function dedent(str, leave_indent) -- find minimum common indent across lines local indent = nil for line in str:gmatch('[^\n]+') do local line_indent = line:match('^%s+') or '' if indent == nil or #line_indent < #indent then indent = line_indent end end if indent == nil or #indent == 0 then -- no minimum common indent return str end local left_indent = (' '):rep(leave_indent or 0) -- create a pattern for the indent indent = indent:gsub('%s', '[ \t]') -- strip it from the first line str = str:gsub('^'..indent, left_indent) -- strip it from the remaining lines str = str:gsub('[\n]'..indent, '\n' .. left_indent) return str end local function coro_run(chunk, doeval) local coro = coroutine.create(chunk) local thunk thunk = function(...) local oldprint = _G.print _G.print = luadev_print local res = vim.F.pack_len(coroutine.resume(coro, ...)) _G.print = oldprint if not res[1] then _G._errstack = coro -- if the only frame on the traceback is the chunk itself, skip the traceback if debug.getinfo(coro, 0,"f").func ~= chunk then res[2] = debug.traceback(coro, res[2], 0) end append_buf(res[2],"WarningMsg") end if coroutine.status(coro) == 'dead' then if doeval or res[2] ~= nil or res.n > 2 then append_buf(luadev_inspect(res[2])) if res.n > 2 then append_buf("MERE", "WarningMsg") -- TODO: implement me end end elseif coroutine.status(coro) == 'suspended' then if res[2] == nil then vim.schedule(thunk) elseif type(res[2]) == "string" then append_buf(res[2],nil,true) vim.cmd'redraw' -- uugh, should not be needed vim.schedule(thunk) elseif type(res[1]) == "function" then res[2](thunk) else error 'WHATTTAAF' end end return res end return thunk() end local function ld_pcall(chunk, ...) local coro = coroutine.create(chunk) local oldprint = _G.print _G.print = luadev_print local res = {coroutine.resume(coro, ...)} _G.print = oldprint if not res[1] then _G._errstack = coro -- if the only frame on the traceback is the chunk itself, skip the traceback if debug.getinfo(coro, 0,"f").func ~= chunk then res[2] = debug.traceback(coro, res[2], 0) end end return unpack(res) end local function default_reader(str, count) local name = "@[luadev "..count.."]" local doeval = true local chunk, err = loadstring("return \n"..str, name) if chunk == nil then chunk, err = loadstring(str, name) doeval = false end return chunk, err, doeval end local function exec(str) local count = s.execount + 1 s.execount = count local reader = s.reader or default_reader local chunk, err, doeval = reader(str, count) local inlines = splitlines(dedent(str)) if inlines[#inlines] == "" then inlines[#inlines] = nil end firstmark = tostring(count)..">" contmark = string.rep(".", string.len(firstmark)) for i,l in ipairs(inlines) do local marker = ((i == 1) and firstmark) or contmark inlines[i] = marker.." "..l end local start = append_buf(inlines) for i,_ in ipairs(inlines) do a.nvim_buf_add_highlight(s.buf, -1, "Question", start+i-1, 0, 2) end if chunk == nil then append_buf(err,"WarningMsg") else coro_run(chunk, doeval) end append_buf({""}) end local function start() open_win() end local function err_wrap(cb) return (function (...) local res = {ld_pcall(cb, ...)} if not res[1] then open_win() append_buf(res[2],"WarningMsg") return nil else table.remove(res, 1) return unpack(res) end end) end local function schedule_wrap(cb) return vim.schedule_wrap(err_wrap(cb)) end local funcs = { create_buf=create_buf, start=start, exec=exec, print=luadev_print, append_buf=append_buf, err_wrap = err_wrap, schedule_wrap = schedule_wrap, coro_run = coro_run, } -- TODO: export abstraction for autoreload for k,v in pairs(funcs) do s[k] = v end return s