2018-03-13 02:02:51 -07:00
|
|
|
local C = require "curses"
|
|
|
|
local repr = require 'repr'
|
2018-03-12 23:14:40 -07:00
|
|
|
local REGULAR, INVERTED, HIGHLIGHTED, RED
|
2018-03-12 23:35:10 -07:00
|
|
|
local run_debugger, cursey
|
2018-03-13 02:02:51 -07:00
|
|
|
local AUTO = -1
|
2018-03-12 23:35:10 -07:00
|
|
|
|
|
|
|
-- Return the callstack index of the code that actually caused an error and the max index
|
|
|
|
local function callstack_range()
|
|
|
|
local min, max = 0, -1
|
2018-03-12 23:14:40 -07:00
|
|
|
for i=1,999 do
|
2018-03-12 23:35:10 -07:00
|
|
|
if debug.getinfo(i,'f').func == run_debugger then
|
|
|
|
min = i+2
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for i=min,999 do
|
|
|
|
if debug.getinfo(i,'f').func == cursey then
|
|
|
|
max = i-3
|
|
|
|
break
|
|
|
|
end
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
2018-03-12 23:35:10 -07:00
|
|
|
return min, max
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
|
|
|
|
2018-03-12 23:35:10 -07:00
|
|
|
local Pad = setmetatable({
|
2018-03-12 23:14:40 -07:00
|
|
|
select = function(self, i)
|
|
|
|
if i == self.selected then return end
|
|
|
|
if i then
|
|
|
|
i = math.max(1, math.min(#self.lines, i))
|
|
|
|
end
|
|
|
|
if self.selected then
|
|
|
|
local j = self.selected
|
|
|
|
local attr = j % 2 == 0 and INVERTED or REGULAR
|
|
|
|
self.chstrs[j]:set_str(0, self.lines[j], attr)
|
|
|
|
self.chstrs[j]:set_str(#self.lines[j], ' ', attr, self.chstrs[j]:len()-#self.lines[j])
|
|
|
|
self._pad:mvaddchstr(j-1+1,0+1,self.chstrs[j])
|
|
|
|
end
|
|
|
|
|
|
|
|
if i then
|
|
|
|
self.chstrs[i]:set_str(0, self.lines[i], HIGHLIGHTED)
|
|
|
|
self.chstrs[i]:set_str(#self.lines[i], ' ', HIGHLIGHTED, self.chstrs[i]:len()-#self.lines[i])
|
|
|
|
self._pad:mvaddchstr(i-1+1,0+1,self.chstrs[i])
|
|
|
|
end
|
|
|
|
|
|
|
|
self.selected = i
|
|
|
|
|
|
|
|
if self.selected then
|
2018-03-13 02:02:51 -07:00
|
|
|
if self.offset + self.height-1 < self.selected then
|
|
|
|
self.offset = math.min(self.selected - (self.height-1), #self.lines-self.height)
|
|
|
|
elseif self.offset + 1 > self.selected then
|
|
|
|
self.offset = self.selected - 1
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
self:refresh()
|
|
|
|
if self.on_select then self:on_select(self.selected) end
|
|
|
|
end,
|
|
|
|
refresh = function(self)
|
2018-03-13 02:02:51 -07:00
|
|
|
self._pad:border(C.ACS_VLINE, C.ACS_VLINE,
|
|
|
|
C.ACS_HLINE, C.ACS_HLINE,
|
|
|
|
C.ACS_ULCORNER, C.ACS_URCORNER,
|
|
|
|
C.ACS_LLCORNER, C.ACS_LRCORNER)
|
|
|
|
self._pad:pnoutrefresh(self.offset,0,self.y,self.x,self.y+self.height,self.x+self.width)
|
2018-03-12 23:14:40 -07:00
|
|
|
end,
|
2018-03-12 23:35:10 -07:00
|
|
|
add_line = function(self, line, attr)
|
2018-03-13 02:02:51 -07:00
|
|
|
self:erase()
|
|
|
|
self._height = 2 + #self.lines + 1
|
|
|
|
if self.resize_height then
|
|
|
|
self.height = self._height
|
|
|
|
end
|
|
|
|
self._width = math.max(self._width, #line+2)
|
|
|
|
if self.resize_width then
|
|
|
|
self.width = self._width
|
|
|
|
end
|
2018-03-12 23:14:40 -07:00
|
|
|
table.insert(self.lines, line)
|
|
|
|
local i = #self.lines
|
2018-03-13 02:02:51 -07:00
|
|
|
local chstr = C.new_chstr(self.width-2)
|
2018-03-12 23:35:10 -07:00
|
|
|
attr = attr or (i % 2 == 0 and INVERTED or REGULAR)
|
2018-03-12 23:14:40 -07:00
|
|
|
self.chstrs[i] = chstr
|
|
|
|
chstr:set_str(0, line, attr)
|
|
|
|
chstr:set_str(#line+0, ' ', attr, chstr:len()-#line)
|
|
|
|
self._pad:mvaddchstr(i-1+1,0+1,chstr)
|
2018-03-13 02:02:51 -07:00
|
|
|
self._pad:resize(self._height, self._width)
|
|
|
|
if self.on_resize then self:on_resize() end
|
2018-03-12 23:14:40 -07:00
|
|
|
end,
|
2018-03-12 23:35:10 -07:00
|
|
|
add_lines = function(self, lines)
|
|
|
|
for i,line in ipairs(lines) do
|
|
|
|
self:add_line(line)
|
|
|
|
end
|
|
|
|
end,
|
2018-03-13 02:02:51 -07:00
|
|
|
erase = function(self)
|
2018-03-12 23:14:40 -07:00
|
|
|
self._pad:erase()
|
2018-03-13 02:02:51 -07:00
|
|
|
self._pad:pnoutrefresh(self.offset,0,self.y,self.x,self.y+self.height,self.x+self.width)
|
|
|
|
end,
|
|
|
|
clear = function(self)
|
|
|
|
self:erase()
|
2018-03-12 23:14:40 -07:00
|
|
|
self.lines = {}
|
|
|
|
self.chstrs = {}
|
2018-03-13 02:02:51 -07:00
|
|
|
self._height = 2
|
|
|
|
if self.resize_height then
|
|
|
|
self.height = self._height
|
|
|
|
end
|
|
|
|
self._width = 2
|
|
|
|
if self.resize_width then
|
|
|
|
self.width = self._width
|
|
|
|
end
|
|
|
|
self._pad:resize(self._height, self._width)
|
2018-03-12 23:14:40 -07:00
|
|
|
self.selected = nil
|
|
|
|
self.offset = 0
|
|
|
|
self:refresh()
|
|
|
|
end,
|
|
|
|
scroll = function(self, delta)
|
2018-03-13 02:02:51 -07:00
|
|
|
self:select(self.selected and (self.selected + delta) or 1)
|
2018-03-12 23:14:40 -07:00
|
|
|
end,
|
2018-03-12 23:35:10 -07:00
|
|
|
}, {
|
|
|
|
__call = function(Pad, y, x, height, width)
|
|
|
|
local pad = setmetatable({
|
2018-03-13 02:02:51 -07:00
|
|
|
x = x, y = y, width = width, height = height,
|
|
|
|
offset = 0, selected = nil,
|
2018-03-12 23:35:10 -07:00
|
|
|
chstrs = {}, lines = {},
|
|
|
|
}, Pad)
|
2018-03-13 02:02:51 -07:00
|
|
|
if height == AUTO then
|
|
|
|
pad.resize_height = true
|
|
|
|
pad.height = 2
|
|
|
|
end
|
|
|
|
if width == AUTO then
|
|
|
|
pad.resize_width = true
|
|
|
|
pad.width = 2
|
|
|
|
end
|
|
|
|
pad._height = pad.height
|
|
|
|
pad._width = pad.width
|
2018-03-12 23:35:10 -07:00
|
|
|
|
2018-03-13 02:02:51 -07:00
|
|
|
pad._pad = C.newpad(pad.height, pad.width)
|
2018-03-12 23:35:10 -07:00
|
|
|
pad._pad:scrollok(true)
|
|
|
|
return pad
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
Pad.__index = Pad
|
2018-03-12 23:14:40 -07:00
|
|
|
|
|
|
|
run_debugger = function(err_msg)
|
2018-03-13 02:02:51 -07:00
|
|
|
local initial_index = 1
|
|
|
|
::restart::
|
|
|
|
local stdscr = C.initscr()
|
2018-03-12 23:14:40 -07:00
|
|
|
local SCREEN_H, SCREEN_W = stdscr:getmaxyx()
|
|
|
|
|
2018-03-13 02:02:51 -07:00
|
|
|
C.cbreak()
|
|
|
|
C.echo(false)
|
|
|
|
C.nl(false)
|
|
|
|
C.curs_set(0)
|
|
|
|
C.start_color()
|
|
|
|
C.use_default_colors()
|
2018-03-12 23:14:40 -07:00
|
|
|
|
|
|
|
stdscr:clear()
|
|
|
|
stdscr:refresh()
|
|
|
|
stdscr:keypad(true)
|
|
|
|
|
2018-03-13 02:02:51 -07:00
|
|
|
local _
|
|
|
|
_, REGULAR = C.init_pair(1, -1, -1), C.color_pair(1)
|
|
|
|
_, INVERTED = C.init_pair(2, -1, C.COLOR_BLACK), C.color_pair(2)
|
|
|
|
_, HIGHLIGHTED = C.init_pair(3, C.COLOR_BLACK, C.COLOR_YELLOW), C.color_pair(3)
|
|
|
|
_, RED = C.init_pair(4, C.COLOR_RED, -1), C.color_pair(4) | C.A_BOLD
|
|
|
|
|
2018-03-12 23:14:40 -07:00
|
|
|
local stack_names = {}
|
|
|
|
local stack_locations = {}
|
|
|
|
local max_filename = 0
|
2018-03-12 23:35:10 -07:00
|
|
|
local stack_min, stack_max = callstack_range()
|
|
|
|
for i=stack_min,stack_max do
|
2018-03-12 23:14:40 -07:00
|
|
|
local info = debug.getinfo(i)
|
|
|
|
if not info then break end
|
|
|
|
table.insert(stack_names, info.name or "???")
|
|
|
|
local filename = info.short_src..":"..info.currentline
|
|
|
|
table.insert(stack_locations, filename)
|
|
|
|
max_filename = math.max(max_filename, #filename)
|
|
|
|
end
|
|
|
|
local callstack = {}
|
|
|
|
for i=1,#stack_names do
|
|
|
|
callstack[i] = stack_locations[i]..(" "):rep(max_filename-#stack_locations[i]).." | "..stack_names[i].." "
|
|
|
|
end
|
|
|
|
|
2018-03-13 02:02:51 -07:00
|
|
|
local err_pad = Pad(0,0,AUTO,SCREEN_W)
|
2018-03-12 23:35:10 -07:00
|
|
|
err_pad._pad:attrset(RED)
|
|
|
|
for line in err_msg:gmatch("[^\n]*") do
|
|
|
|
local buff = ""
|
|
|
|
for word in line:gmatch("%S%S*%s*") do
|
|
|
|
if #buff + #word > SCREEN_W - 4 then
|
|
|
|
err_pad:add_line(" "..buff, RED)
|
|
|
|
buff = word
|
|
|
|
else
|
|
|
|
buff = buff .. word
|
|
|
|
end
|
|
|
|
end
|
|
|
|
err_pad:add_line(" "..buff, RED)
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
2018-03-12 23:35:10 -07:00
|
|
|
err_pad:refresh()
|
2018-03-12 23:14:40 -07:00
|
|
|
|
2018-03-13 02:02:51 -07:00
|
|
|
local stack_pad = Pad(err_pad.height,0,AUTO,50)
|
2018-03-12 23:35:10 -07:00
|
|
|
stack_pad:add_lines(callstack)
|
2018-03-13 02:02:51 -07:00
|
|
|
local var_names = Pad(err_pad.height,stack_pad.x+stack_pad.width,10,AUTO)
|
|
|
|
local var_values = Pad(err_pad.height,var_names.x+var_names.width,10,AUTO)
|
|
|
|
function stack_pad:on_resize()
|
|
|
|
var_names:erase()
|
|
|
|
var_names.x = self.x+self.width
|
|
|
|
var_names:refresh()
|
|
|
|
var_names:on_resize(var_names.height, var_names.width)
|
|
|
|
end
|
|
|
|
function var_names:on_resize()
|
|
|
|
var_values:erase()
|
|
|
|
var_values.x = self.x+self.width
|
|
|
|
var_values.width = SCREEN_W - (self.x+self.width)
|
|
|
|
var_values:refresh()
|
|
|
|
end
|
2018-03-12 23:14:40 -07:00
|
|
|
|
|
|
|
function stack_pad:on_select(i)
|
|
|
|
var_names:clear()
|
|
|
|
var_values:clear()
|
2018-03-12 23:35:10 -07:00
|
|
|
local callstack_min, _ = callstack_range()
|
2018-03-12 23:14:40 -07:00
|
|
|
for loc=1,999 do
|
2018-03-12 23:35:10 -07:00
|
|
|
local name, value = debug.getlocal(callstack_min+i-1, loc)
|
2018-03-12 23:14:40 -07:00
|
|
|
if value == nil then break end
|
|
|
|
var_names:add_line(tostring(name))
|
2018-03-13 02:02:51 -07:00
|
|
|
if type(value) == 'function' then
|
|
|
|
local info = debug.getinfo(value, 'nS')
|
|
|
|
--var_values:add_line(("function: %s @ %s:%s"):format(info.name or '???', info.short_src, info.linedefined))
|
|
|
|
var_values:add_line(repr(info))
|
|
|
|
else
|
|
|
|
var_values:add_line(repr(value))
|
|
|
|
end
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
|
|
|
var_names:refresh()
|
|
|
|
var_values:refresh()
|
|
|
|
end
|
2018-03-13 02:02:51 -07:00
|
|
|
stack_pad:select(initial_index)
|
2018-03-12 23:14:40 -07:00
|
|
|
|
|
|
|
while true do
|
2018-03-13 02:02:51 -07:00
|
|
|
C.doupdate()
|
2018-03-12 23:14:40 -07:00
|
|
|
local c = stdscr:getch()
|
|
|
|
local old_index = index
|
2018-03-13 02:02:51 -07:00
|
|
|
if ({[C.KEY_DOWN]=1, [C.KEY_SF]=1, [("j"):byte()]=1})[c] then
|
2018-03-12 23:14:40 -07:00
|
|
|
stack_pad:scroll(1)
|
2018-03-13 02:02:51 -07:00
|
|
|
elseif ({[C.KEY_UP]=1, [C.KEY_SR]=1, [("k"):byte()]=1})[c] then
|
2018-03-12 23:14:40 -07:00
|
|
|
stack_pad:scroll(-1)
|
2018-03-13 02:02:51 -07:00
|
|
|
elseif c == ('n'):byte() then
|
|
|
|
var_names.offset = var_names.offset + 1
|
|
|
|
var_names:refresh()
|
|
|
|
--var_names:scroll(1)
|
|
|
|
elseif c == ('m'):byte() then
|
|
|
|
var_names.offset = var_names.offset - 1
|
|
|
|
var_names:refresh()
|
|
|
|
--var_names:scroll(-1)
|
|
|
|
elseif c == ('J'):byte() then
|
2018-03-12 23:14:40 -07:00
|
|
|
stack_pad:scroll(10)
|
2018-03-13 02:02:51 -07:00
|
|
|
elseif c == ('K'):byte() then
|
2018-03-12 23:14:40 -07:00
|
|
|
stack_pad:scroll(-10)
|
2018-03-13 02:02:51 -07:00
|
|
|
elseif c == ('o'):byte() then
|
|
|
|
local file = stack_locations[stack_pad.selected]
|
|
|
|
local filename,line_no = file:match("([^:]*):(.*)")
|
|
|
|
-- Launch system editor and then redraw everything
|
|
|
|
C.endwin()
|
|
|
|
os.execute((os.getenv("EDITOR") or "nano").." +"..line_no.." "..filename)
|
|
|
|
initial_index = stack_pad.selected
|
|
|
|
goto restart
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
|
|
|
|
2018-03-13 02:02:51 -07:00
|
|
|
if c == ('q'):byte() or c == ("Q"):byte() then
|
2018-03-12 23:14:40 -07:00
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2018-03-13 02:02:51 -07:00
|
|
|
C.endwin()
|
2018-03-12 23:14:40 -07:00
|
|
|
end
|
|
|
|
|
2018-03-12 23:35:10 -07:00
|
|
|
cursey = function(fn, ...)
|
2018-03-12 23:14:40 -07:00
|
|
|
-- To display Lua errors, we must close curses to return to
|
|
|
|
-- normal terminal mode, and then write the error to stdout.
|
|
|
|
local function err(err)
|
2018-03-13 02:02:51 -07:00
|
|
|
C.endwin()
|
2018-03-12 23:14:40 -07:00
|
|
|
print "Caught an error:"
|
|
|
|
print(debug.traceback(err, 2))
|
|
|
|
os.exit(2)
|
|
|
|
end
|
|
|
|
|
|
|
|
return xpcall(fn, function(err_msg)
|
|
|
|
xpcall(run_debugger, err, err_msg)
|
|
|
|
end, ...)
|
|
|
|
end
|
2018-03-12 23:35:10 -07:00
|
|
|
|
|
|
|
return cursey
|