(1.0K lines)
1 C = require "curses"2 re = require 're'3 line_matcher = re.compile('lines<-{| line ("\n" line)* |} line<-{[^\n]*}')4 local ldb5 AUTO = {} -- Singleton6 PARENT = {} -- Singleton8 -- TODO: add support for stepping debugger9 -- TODO: add vim-style search functionality via "/"11 _error = error12 _assert = assert14 -- Return the callstack index of the code that actually caused an error and the max index15 callstack_range = ->16 min, max = 0, -117 for i=1,999 do18 info = debug.getinfo(i, 'f')19 if not info20 min = i-121 break22 if info.func == ldb.run_debugger23 min = i+224 break25 for i=min,99926 info = debug.getinfo(i, 'f')27 if not info or info.func == ldb.guard28 max = i-329 break30 return min, max33 wrap_text = (text, width)->34 lines = {}35 for line in *line_matcher\match(text)36 while #line > width37 table.insert(lines, line\sub(1,width))38 line = line\sub(width+1,-1)39 if #line == 040 line = nil41 if line42 table.insert(lines, line)43 return lines46 local Color47 do48 color_index = 049 existing = {}50 make_color = (fg=-1, bg=-1)->51 key = "#{fg},#{bg}"52 unless existing[key]53 color_index += 154 C.init_pair(color_index, fg, bg)55 existing[key] = C.color_pair(color_index)56 return existing[key]57 color_lang = re.compile[[58 x <- {|59 {:attrs: {| {attr} (" " {attr})* |} :}60 / (61 ({:bg: "on " {color} :} / ({:fg: color :} (" on " {:bg: color :})?))62 {:attrs: {| (" " {attr})* |} :})63 |}64 attr <- "blink" / "bold" / "dim" / "invis" / "normal" / "protect" / "reverse" / "standout" / "underline" / "altcharset"65 color <- "black" / "blue" / "cyan" / "green" / "magenta" / "red" / "white" / "yellow" / "default"66 ]]67 C.COLOR_DEFAULT = -168 Color = (s="default")->69 t = _assert(color_lang\match(s), "Invalid color: #{s}")70 if t.fg then t.fg = C["COLOR_"..t.fg\upper!]71 if t.bg then t.bg = C["COLOR_"..t.bg\upper!]72 c = make_color(t.fg, t.bg)73 for a in *t.attrs74 c |= C["A_"..a\upper!]75 return c78 class Pad79 new: (@label,@y,@x,height,width,...)=>80 @scroll_y, @scroll_x = 1, 181 @selected = nil83 @columns = {}84 @column_widths = {}85 @active_frame = Color("yellow bold")86 @inactive_frame = Color("blue dim")87 @colors = {}88 for i=1,select('#',...)-1,289 col = select(i, ...)90 table.insert(@columns, col)91 w = 092 for chunk in *col do w = math.max(w, #chunk)93 table.insert(@column_widths, w)94 color_fn = select(i+1,...) or ((i)=>Color())95 _assert(type(color_fn) == 'function', "Invalid color function type: #{type color_fn}")96 table.insert(@colors, color_fn)98 @configure_size height, width99 @_frame = C.newwin(@height, @width, @y, @x)100 @_frame\immedok(true)101 @_pad = C.newpad(@_height, @_width)102 @_pad\scrollok(true)103 @set_active false104 @chstrs = {}105 for i=1,#@columns[1]106 @chstrs[i] = C.new_chstr(@_width)107 @setup_chstr(i)108 @dirty = true110 configure_size: (@height, @width)=>111 @_height = math.max(#@columns[1], 1)112 if @height == AUTO113 @height = @_height + 2115 @_width = #@columns-1116 for i,col in ipairs(@columns)117 col_width = 0118 for chunk in *col do col_width = math.max(col_width, #chunk)119 @_width += col_width120 @_width = math.max(@_width, 1)121 if @width == AUTO122 @width = @_width + 2124 setup_chstr: (i)=>125 chstr = _assert(@chstrs[i], "Failed to find chstrs[#{i}]")126 x = 0127 for c=1,#@columns128 attr = @colors[c](@, i)129 chunk = @columns[c][i]130 chstr\set_str(x, chunk, attr)131 x += #chunk132 if #chunk < @column_widths[c]133 chstr\set_str(x, " ", attr, @column_widths[c]-#chunk)134 x += @column_widths[c]-#chunk135 if c < #@columns136 chstr\set_ch(x, C.ACS_VLINE, Color("black bold"))137 x += 1138 @_pad\mvaddchstr(i-1,0,chstr)139 @dirty = true141 set_active: (active)=>142 return if active == @active143 @active = active144 @_frame\attrset(active and @active_frame or @inactive_frame)145 @dirty = true147 select: (i)=>148 if #@columns[1] == 0 then i = nil149 if i == @selected then return @selected150 old_y, old_x = @scroll_y, @scroll_x151 if i != nil152 i = math.max(1, math.min(#@columns[1], i))154 old_selected,@selected = @selected,i156 if old_selected157 @setup_chstr(old_selected)159 if @selected160 @setup_chstr(@selected)162 scrolloff = 3163 if @selected > @scroll_y + (@height-2) - scrolloff164 @scroll_y = @selected - (@height-2) + scrolloff165 elseif @selected < @scroll_y + scrolloff166 @scroll_y = @selected - scrolloff167 @scroll_y = math.max(1, math.min(@_height, @scroll_y))169 if @scroll_y == old_y170 w = math.min(@width-2,@_width)171 if old_selected and @scroll_y <= old_selected and old_selected <= @scroll_y + @height-2172 @_pad\pnoutrefresh(old_selected-1,@scroll_x-1,@y+1+(old_selected-@scroll_y),@x+1,@y+1+(old_selected-@scroll_y)+1,@x+w)173 if @selected and @scroll_y <= @selected and @selected <= @scroll_y + @height-2174 @_pad\pnoutrefresh(@selected-1,@scroll_x-1,@y+1+(@selected-@scroll_y),@x+1,@y+1+(@selected-@scroll_y)+1,@x+w)175 else176 @dirty = true178 if @on_select then @on_select(@selected)179 return @selected181 scroll: (dy,dx)=>182 old_y, old_x = @scroll_y, @scroll_x183 if @selected != nil184 @select(@selected + (dy or 0))185 else186 @scroll_y = math.max(1, math.min(@_height-(@height-2-1), @scroll_y+(dy or 0)))187 @scroll_x = math.max(1, math.min(@_width-(@width-2-1), @scroll_x+(dx or 0)))188 if @scroll_y != old_y or @scroll_x != old_x189 @dirty = true191 refresh: (force=false)=>192 return if not force and not @dirty193 @_frame\border(C.ACS_VLINE, C.ACS_VLINE,194 C.ACS_HLINE, C.ACS_HLINE,195 C.ACS_ULCORNER, C.ACS_URCORNER,196 C.ACS_LLCORNER, C.ACS_LRCORNER)197 if @label198 @_frame\mvaddstr(0, math.floor((@width-#@label-2)/2), " #{@label} ")199 @_frame\refresh!200 --@_frame\prefresh(0,0,@y,@x,@y+@height-1,@x+@width-1)201 h,w = math.min(@height-2,@_height),math.min(@width-2,@_width)202 @_pad\pnoutrefresh(@scroll_y-1,@scroll_x-1,@y+1,@x+1,@y+h,@x+w)203 @dirty = false205 keypress: (c)=>206 switch c207 when C.KEY_DOWN, C.KEY_SR, ("j")\byte!208 @scroll(1,0)209 when ('J')\byte!210 @scroll(10,0)212 when C.KEY_UP, C.KEY_SF, ("k")\byte!213 @scroll(-1,0)214 when ('K')\byte!215 @scroll(-10,0)217 when C.KEY_RIGHT, ("l")\byte!218 @scroll(0,1)219 when ("L")\byte!220 @scroll(0,10)222 when C.KEY_LEFT, ("h")\byte!223 @scroll(0,-1)224 when ("H")\byte!225 @scroll(0,-10)227 erase: =>228 @dirty = true229 @_frame\erase!230 @_frame\refresh!232 __gc: =>233 @_frame\close!234 @_pad\close!236 class NumberedPad extends Pad237 new: (@label,@y,@x,height,width,...)=>238 col1 = select(1, ...)239 fmt = "%#{#tostring(#col1)}d"240 line_nums = [fmt\format(i) for i=1,#col1]241 cols = {line_nums, ((i)=> i == @selected and Color() or Color("yellow")), ...}242 super @label, @y, @x, height, width, unpack(cols)245 expansions = {}246 TOP_LOCATION, KEY, VALUE = {}, {}, {}247 locations = {}248 Location = (old_loc, kind, key)->249 if old_loc == nil250 return TOP_LOCATION251 unless locations[old_loc]252 locations[old_loc] = {}253 unless locations[old_loc][kind]254 locations[old_loc][kind] = {}255 unless locations[old_loc][kind][key]256 locations[old_loc][kind][key] = {:old_loc, :kind, :key}257 return locations[old_loc][kind][key]259 expand = (kind, key, location)->260 expansions[Location(location, kind, key)] = true261 collapse = (kind, key, location)->262 expansions[Location(location, kind, key)] = nil263 is_key_expanded = (location, key)->264 expansions[Location(location, KEY, key)]265 is_value_expanded = (location, key)->266 expansions[Location(location, VALUE, key)]268 TYPE_COLORS = setmetatable({}, {__index: 0})270 colored_repr = (x, width, depth=2)->271 depth -= 1272 x_type = type(x)273 if x_type == 'table' then274 if next(x) == nil275 return {"{}", TYPE_COLORS.table}276 if depth == 0277 return {"{", TYPE_COLORS.table, "...", Color('white'), "}", TYPE_COLORS.table}278 ret = {"{", TYPE_COLORS.table}279 i = 1280 for k, v in pairs(x)281 if k == i282 for s in *colored_repr(x[i], width, depth) do ret[#ret+1] = s283 i = i + 1284 else285 for s in *colored_repr(k, width, depth) do ret[#ret+1] = s286 ret[#ret+1] = ' = '287 ret[#ret+1] = Color('white')288 for s in *colored_repr(v, width, depth) do ret[#ret+1] = s289 ret[#ret+1] = ', '290 ret[#ret+1] = Color('white')291 if #ret > 2292 ret[#ret] = nil293 ret[#ret] = nil294 len = 0295 for i=1,#ret-1,2 do len += #ret[i]296 for i=#ret-1,3,-2297 if len <= width-1298 break299 if ret[i+2]300 ret[i+2], ret[i+3] = nil, nil301 ret[i] = '...'302 ret[i+1] = Color('white')303 ret[#ret+1] = '}'304 ret[#ret+1] = TYPE_COLORS.table305 return ret306 elseif x_type == 'string'307 ret = {(x\match('^[^\t\r\v\b\a\n]*')), TYPE_COLORS.string}308 for escape, line in x\gmatch('([\t\r\v\b\a\n])([^\t\r\v\b\a\n]*)')309 ret[#ret+1] = '\\'..({['\t']:'t',['\r']:'r',['\v']:'v',['\b']:'b',['\a']:'a',['\n']:'n'})[escape]310 ret[#ret+1] = Color('white on black')311 ret[#ret+1] = line312 ret[#ret+1] = TYPE_COLORS.string313 len = 0314 for i=1,#ret-1,2 do len += #ret[i]315 for i=#ret-1,1,-2316 if len <= width then break317 if ret[i+2]318 ret[i+2], ret[i+3] = nil, nil319 len -= #ret[i]320 if len <= width321 ret[i] = ret[i]\sub(1, width-len-3)322 ret[i+2] = '...'323 ret[i+3] = Color('blue')324 break325 return ret326 else327 ok,s = pcall(tostring,x)328 if not ok329 return {"tostring error: "..s, Color("red")}330 return if #s > width331 {s\sub(1,width-3), TYPE_COLORS[type(x)], '...', Color('blue')}332 else333 {s, TYPE_COLORS[type(x)]}335 make_lines = (location, x, width)->336 -- Return a list of {location=location, text1, color1, text2, color2, ...}337 switch type(x)338 when 'string'339 lines = {}340 for line in *line_matcher\match(x)341 wrapped = wrap_text(line, width-1)342 for i,subline in ipairs(wrapped)343 _line = {location:location}344 if i > 1345 table.insert(_line, C.ACS_BULLET)346 table.insert(_line, Color('black bold altcharset'))347 table.insert(_line, subline)348 table.insert(_line, Color('blue on black'))349 table.insert(lines, _line)350 if #lines == 0351 table.insert lines, {:location, "''", Color('blue')}352 return lines353 when 'table'354 prepend = (line, ...)->355 for i=1,select('#', ...)356 table.insert(line, i, (select(i, ...)))357 lines = {}358 for k,v in pairs(x)359 if is_key_expanded(location, k) and is_value_expanded(location, k)360 table.insert lines, {361 location:Location(location,KEY,k), 'key', Color('green bold'),362 '/', Color!, 'value', Color('blue bold'), ':', Color('white')}363 key_lines = make_lines(Location(location, KEY, k), k, width-1)364 for i,key_line in ipairs(key_lines)365 if i == 1366 prepend(key_line, ' ', Color!, C.ACS_DIAMOND, Color('green bold'), ' ', Color!)367 else368 prepend(key_line, ' ', Color!)369 table.insert(lines, key_line)370 value_lines = make_lines(Location(location, VALUE, k), v, width-2)371 for i,value_line in ipairs(value_lines)372 if i == 1373 prepend(value_line, ' ', Color!, C.ACS_DIAMOND, Color('blue bold'), ' ', Color!)374 else375 prepend(value_line, ' ', Color!)376 table.insert(lines, value_line)377 elseif is_value_expanded(location, k)378 k_str = colored_repr(k,width-1)379 table.insert lines, {380 location:Location(location, KEY, k),381 '-', Color('red'), unpack(k_str)382 }384 v_lines = make_lines(Location(location, VALUE, k), v, width-1)385 prepend(v_lines[1], ' ', Color!)386 for i=2,#v_lines387 prepend(v_lines[i], ' ', Color!)388 for v_line in *v_lines do table.insert(lines, v_line)389 elseif is_key_expanded(location, k)390 k_lines = make_lines(Location(location, KEY, k), k, width-4)391 for i=1,#k_lines392 prepend(k_lines[i], ' ', Color!)393 for k_line in *k_lines do table.insert(lines, k_line)395 v_str = colored_repr(v,width-2)396 table.insert(lines, {location:Location(location, VALUE, k), ' ', Color!, unpack(v_str)})397 else398 k_space = math.floor((width-4)/3)399 k_str = colored_repr(k,k_space)400 v_space = (width-4)-#k_str401 v_str = colored_repr(v,v_space)402 line = {403 location:Location(location, VALUE, k),404 '+', Color('green'),405 unpack(k_str)406 }407 table.insert line, ' = '408 table.insert line, Color('white')409 for s in *v_str do table.insert(line, s)410 table.insert(lines, line)411 if #lines == 0412 table.insert lines, {:location, '{}', TYPE_COLORS.table}413 return lines414 else415 if getmetatable(x) and getmetatable(x).__pairs416 lines = make_lines(location, {k,v for k,v in pairs(x)}, width)417 if getmetatable(x).__tostring418 s_lines = {}419 ok, s = pcall(tostring, x)420 if not ok then s = "tostring error: "..s421 for line in *line_matcher\match(s)422 wrapped = wrap_text(line, width)423 for i,subline in ipairs(wrapped)424 table.insert(s_lines, {:location, subline, ok and Color('yellow') or Color('red')})425 for i=1,#s_lines426 table.insert(lines, i, s_lines[i])427 return lines428 str = tostring(x)429 if #str > width430 str = str\sub(1,width-3)..'...'431 return {{:location, str, TYPE_COLORS[type(x)]}}434 class DataViewer extends Pad435 new: (@data,@label,@y,@x,height,width)=>436 @scroll_y, @scroll_x = 1, 1437 @selected = nil439 @active_frame = Color("yellow bold")440 @inactive_frame = Color("blue dim")442 @full_refresh = ->443 old_location = @selected and @chstr_locations and @chstr_locations[@selected]444 @chstrs, @chstr_locations = {}, {}445 W = width-3446 lines = make_lines(TOP_LOCATION, @data, W)447 for i,line in ipairs(lines)448 chstr = C.new_chstr(W)449 if i == @selected450 chstr\set_ch(0, C.ACS_RARROW, Color('yellow bold'))451 else452 chstr\set_str(0, ' ', Color('yellow bold'))453 offset = 1454 for j=1,#line-1,2455 chunk, attrs = line[j], line[j+1]456 if type(chunk) == 'number'457 chstr\set_ch(offset, chunk, attrs)458 offset += 1459 else460 chstr\set_str(offset, chunk, attrs)461 offset += #chunk462 if offset < W463 chstr\set_str(offset, ' ', attrs, W-offset)464 table.insert @chstrs, chstr465 table.insert @chstr_locations, line.location467 @_height, @_width = #@chstrs, @width-2468 @_pad\resize(@_height, @_width)469 for i,chstr in ipairs(@chstrs)470 @_pad\mvaddchstr(i-1,0,chstr)471 @dirty = true472 if old_location473 for i,loc in ipairs(@chstr_locations)474 if loc == old_location475 @select(i)476 break478 @height, @width = height, width479 @_frame = C.newwin(@height, @width, @y, @x)480 @_frame\immedok(true)481 @_pad = C.newpad(@height-2, @width-2)482 @_pad\scrollok(true)483 @set_active false485 @full_refresh!486 @select 1488 setup_chstr:(i)=>490 configure_size: (@height, @width)=>491 @_height, @_width = #@chstrs, @width-2493 select:(i)=>494 if #@chstrs == 0 then i = nil495 if i == @selected then return @selected496 old_y, old_x = @scroll_y, @scroll_x497 if i != nil498 i = math.max(1, math.min(#@chstrs, i))500 old_selected,@selected = @selected,i502 if old_selected and @chstrs[old_selected]503 @chstrs[old_selected]\set_str(0, ' ', Color('yellow bold'))504 @_pad\mvaddchstr(old_selected-1,0,@chstrs[old_selected])506 if @selected507 @chstrs[@selected]\set_ch(0, C.ACS_RARROW, Color('yellow bold'))508 @_pad\mvaddchstr(@selected-1,0,@chstrs[@selected])510 scrolloff = 3511 if @selected > @scroll_y + (@height-2) - scrolloff512 @scroll_y = @selected - (@height-2) + scrolloff513 elseif @selected < @scroll_y + scrolloff514 @scroll_y = @selected - scrolloff515 @scroll_y = math.max(1, math.min(@_height, @scroll_y))517 if @scroll_y == old_y518 w = math.min(@width-2,@_width)519 if old_selected and @scroll_y <= old_selected and old_selected <= @scroll_y + @height-2520 @_pad\pnoutrefresh(old_selected-1,@scroll_x-1,@y+1+(old_selected-@scroll_y),@x+1,@y+1+(old_selected-@scroll_y)+1,@x+w)521 if @selected and @scroll_y <= @selected and @selected <= @scroll_y + @height-2522 @_pad\pnoutrefresh(@selected-1,@scroll_x-1,@y+1+(@selected-@scroll_y),@x+1,@y+1+(@selected-@scroll_y)+1,@x+w)523 else524 @dirty = true526 if @on_select then @on_select(@selected)527 return @selected529 keypress: (c)=>530 switch c531 when C.KEY_DOWN, C.KEY_SR, ("j")\byte!532 @scroll(1,0)533 when ('J')\byte!534 @scroll(10,0)536 when C.KEY_UP, C.KEY_SF, ("k")\byte!537 @scroll(-1,0)538 when ('K')\byte!539 @scroll(-10,0)541 when C.KEY_RIGHT, ("l")\byte!542 expansions[@chstr_locations[@selected]] = true543 @full_refresh!544 when ("L")\byte!545 expansions[@chstr_locations[@selected]] = true546 @full_refresh!548 when C.KEY_LEFT, ("h")\byte!549 loc = @chstr_locations[@selected]550 if expansions[loc] == nil551 loc = Location(loc.old_loc, (loc.kind == KEY and VALUE or KEY), loc.key)552 while loc and expansions[loc] == nil553 loc = loc.old_loc554 if loc555 expansions[loc] = nil556 @full_refresh!557 if loc and @chstr_locations[@selected] != loc558 for i,chstr_loc in ipairs(@chstr_locations)559 if chstr_loc == loc560 @select(i)561 break562 elseif not loc563 @select(1)565 when ("H")\byte!566 loc = @chstr_locations[@selected]567 if expansions[loc] == nil568 loc = Location(loc.old_loc, (loc.kind == KEY and VALUE or KEY), loc.key)569 while loc and expansions[loc] == nil570 loc = loc.old_loc571 if loc572 expansions[loc] = nil573 @full_refresh!574 if loc and @chstr_locations[@selected] != loc575 for i,chstr_loc in ipairs(@chstr_locations)576 if chstr_loc == loc577 @select(i)578 break579 elseif not loc580 @select(1)582 ok, to_lua = pcall -> require('moonscript.base').to_lua583 if not ok then to_lua = -> nil584 file_cache = setmetatable({}, {__index:(filename)=>585 file = io.open(filename)586 if not file then return nil587 contents = file\read("a")\sub(1,-2) -- drop the trailing "\n" that Lua adds for some reason588 @[filename] = contents589 return contents590 })591 line_tables = setmetatable({}, {__index:(filename)=>592 file = file_cache[filename]593 if not file594 return nil595 ok, line_table = to_lua(file)596 if ok597 @[filename] = line_table598 return line_table599 })602 -- Cleanup curses and print the error to stdout like regular603 err_hand = (err)->604 C.endwin!605 print "Error in debugger."606 print(debug.traceback(err, 2))607 os.exit(2)609 ldb = {610 run_debugger: (err_msg)->611 local select_pad612 err_msg or= ''613 if type(err_msg) != 'string' then err_msg = tostring(err_msg)614 stdscr = C.initscr!615 SCREEN_H, SCREEN_W = stdscr\getmaxyx!616 C.cbreak!617 C.echo(false)618 C.nl(false)619 C.curs_set(0)620 C.start_color!621 C.use_default_colors!622 with TYPE_COLORS623 .string = Color('blue on black')624 .number = Color('magenta')625 .boolean = Color('cyan')626 .nil = Color('cyan')627 .table = Color('yellow')628 .function = Color('green')629 .userdata = Color('cyan bold')630 .thread = Color('blue')632 do -- Fullscreen flash633 stdscr\wbkgd(Color"yellow on red bold")634 stdscr\clear!635 stdscr\refresh!636 lines = wrap_text("ERROR!\n \n "..err_msg.."\n \npress any key...", math.floor(SCREEN_W-2))637 max_line = 0638 for line in *lines do max_line = math.max(max_line, #line)639 for i, line in ipairs(lines)640 if i == 1 or i == #lines641 stdscr\mvaddstr(math.floor(SCREEN_H/2 - #lines/2)+i, math.floor((SCREEN_W-#line)/2), line)642 else643 stdscr\mvaddstr(math.floor(SCREEN_H/2 - #lines/2)+i, math.floor((SCREEN_W-max_line)/2), line)644 stdscr\refresh!645 C.doupdate!646 stdscr\getch!648 stdscr\keypad!649 stdscr\wbkgd(Color!)650 stdscr\clear!651 stdscr\refresh!653 pads = {}655 do -- Err pad656 err_msg_lines = wrap_text(err_msg, SCREEN_W - 4)657 for i,line in ipairs(err_msg_lines)658 err_msg_lines[i] = (" ")\rep(2)..line659 height = math.min(#err_msg_lines+2, 7)660 pads.err = Pad("(E)rror Message", 0,0,height,SCREEN_W, err_msg_lines, (i)=> Color("red bold"))662 err_lines = {}663 stack_sources = {}664 stack_locations = {}665 -- Map from stack index -> expr -> value666 watch_exprs = setmetatable({}, {__index:(k)=>667 t = {}668 self[k] = t669 return t670 })671 do -- Stack pad672 stack_names = {}673 max_filename, max_fn_name = 0, 0674 stack_min, stack_max = callstack_range!675 for i=stack_min,stack_max676 info = debug.getinfo(i)677 if not info then break678 fn_name = info.name679 unless fn_name680 fn_name = if info.istailcall then "<tail call>"681 else "<anonymous>"683 table.insert(stack_names, fn_name)684 line = if info.short_src685 line_table = line_tables[info.short_src]686 if line_table687 char = line_table[info.currentline]688 line_num = 1689 file = file_cache[info.short_src] or info.source690 for _ in file\sub(1,char)\gmatch("\n") do line_num += 1691 "#{info.short_src}:#{line_num}"692 else693 info.short_src..":"..info.currentline694 else695 "???"696 err_lines[line] = true697 table.insert(stack_locations, line)698 table.insert(stack_sources, info.source)699 max_filename = math.max(max_filename, #line)700 max_fn_name = math.max(max_fn_name, #fn_name)701 max_fn_name, max_filename = 0, 0702 for i=1,#stack_names do703 max_fn_name = math.max(max_fn_name, #stack_names[i])704 max_filename = math.max(max_filename, #stack_locations[i])706 stack_h = math.floor(SCREEN_H*.6)--math.max(#stack_names+2, math.floor(2/3*SCREEN_H))707 stack_w = math.min(max_fn_name + 3 + max_filename, math.floor(1/3*SCREEN_W))708 pads.stack = Pad "(C)allstack",pads.err.height,SCREEN_W-stack_w,stack_h,stack_w,709 stack_names, ((i)=> (i == @selected) and Color("black on green") or Color("green bold")),710 stack_locations, ((i)=> (i == @selected) and Color("black on cyan") or Color("cyan bold"))712 show_src = (filename, line_no, file_contents=nil)->713 if pads.src714 if pads.src.filename == filename715 pads.src\select(line_no)716 pads.src.colors[2] = (i)=>717 return if i == line_no and i == @selected then Color("yellow on red bold")718 elseif i == line_no then Color("yellow on red")719 elseif err_lines["#{filename}:#{i}"] == true then Color("red on black bold")720 elseif i == @selected then Color("reverse")721 else Color()722 for line,_ in pairs(err_lines)723 _filename, i = line\match("([^:]*):(%d*).*")724 if _filename == filename and tonumber(i)725 pads.src\setup_chstr(tonumber(i))726 pads.src\select(line_no)727 return728 else729 pads.src\erase!730 file_contents = file_contents or file_cache[filename]731 if file_contents732 src_lines = line_matcher\match(file_contents)733 pads.src = NumberedPad "(S)ource Code", pads.err.height,0,734 pads.stack.height,pads.stack.x, src_lines, (i)=>735 return if i == line_no and i == @selected then Color("yellow on red bold")736 elseif i == line_no then Color("yellow on red")737 elseif err_lines["#{filename}:#{i}"] == true then Color("red on black bold")738 elseif i == @selected then Color("reverse")739 else Color()740 pads.src\select(line_no)741 else742 lines = {}743 for i=1,math.floor(pads.stack.height/2)-1 do table.insert(lines, "")744 s = "<no source code found>"745 s = (" ")\rep(math.floor((pads.stack.x-2-#s)/2))..s746 table.insert(lines, s)747 pads.src = Pad "(S)ource Code", pads.err.height,0,pads.stack.height,pads.stack.x,lines, ->Color("red")748 pads.src.filename = filename750 local stack_env751 show_vars = (stack_index)->752 if pads.vars753 pads.vars\erase!754 if pads.data755 pads.data\erase!756 callstack_min, _ = callstack_range!757 var_names, values = {}, {}758 stack_env = setmetatable({}, {__index:_G})759 for loc=1,999760 name, value = debug.getlocal(callstack_min+stack_index-1, loc)761 if name == nil then break762 table.insert(var_names, tostring(name))763 table.insert(values, value)764 stack_env[name] = value765 num_locals = #var_names766 info = debug.getinfo(callstack_min+stack_index-1,"uf")767 for upval=1,info.nups768 name,value = debug.getupvalue(info.func, upval)769 if name == "_ENV" then continue770 table.insert(var_names, tostring(name))771 table.insert(values, value)772 stack_env[name] = value773 for watch in *watch_exprs[stack_index]774 continue if stack_env[watch.expr] != nil775 table.insert(var_names, watch.expr)776 table.insert(values, watch.value)777 stack_env[watch.expr] = watch.value779 var_y = pads.stack.y + pads.stack.height780 var_x = 0781 --height = math.min(2+#var_names, SCREEN_H-pads.err.height-pads.stack.height)782 height = SCREEN_H-(pads.err.height+pads.stack.height)783 pads.vars = Pad "(V)ars", var_y,var_x,height,AUTO,var_names, (i)=>784 color = if i <= num_locals then Color()785 elseif i <= num_locals + info.nups - 1 then Color("blue")786 else Color("green")787 if i == @selected then color += C.A_REVERSE788 return color790 pads.vars.keypress = (key)=>791 if key == ('l')\byte! or key == C.KEY_RIGHT792 select_pad(pads.data)793 else Pad.keypress(self, key)795 pads.vars.on_select = (var_index)=>796 if var_index == nil then return797 value_x = pads.vars.x+pads.vars.width798 value_w = SCREEN_W-(value_x)799 value = stack_env[var_names[var_index]]--values[var_index]800 type_str = tostring(type(value))801 -- Show single value:802 pads.data = DataViewer value, "(D)ata [#{type_str}]", var_y,value_x,pads.vars.height,value_w803 pads.data.keypress = (key)=>804 if (key == ('h')\byte! or key == C.KEY_LEFT) and @selected == 1805 select_pad(pads.vars)806 else DataViewer.keypress(self, key)807 collectgarbage()808 collectgarbage()810 pads.vars\select(1)812 pads.stack.on_select = (stack_index)=>813 filename,line_no = pads.stack.columns[2][stack_index]\match("^(.*):(%d*)$")814 --filename, line_no = pads.stack.lines[stack_index]\match("[^|]*| ([^:]*):(%d*).*")815 line_no = tonumber(line_no)816 show_src(filename, line_no, filename and file_cache[filename] or stack_sources[stack_index])817 show_vars(stack_index)819 pads.stack\select(1)821 selected_pad = nil822 select_pad = (pad)->823 if selected_pad != pad824 if selected_pad825 selected_pad\set_active(false)826 selected_pad\refresh!827 selected_pad = pad828 if selected_pad829 selected_pad\set_active(true)830 selected_pad\refresh!832 select_pad(pads.stack)834 while true835 for _,p in pairs(pads)836 p\refresh!837 s = " press 'q' to quit "838 stdscr\mvaddstr(math.floor(SCREEN_H - 1), math.floor((SCREEN_W-#s)), s)839 --C.doupdate!840 c = stdscr\getch!841 switch c842 when (':')\byte!, ('>')\byte!, ('?')\byte!843 C.echo(true)844 print_nil = false845 local user_input846 code = ''847 if c == ('?')\byte!848 stdscr\mvaddstr(SCREEN_H-1, 0, "? "..(' ')\rep(SCREEN_W-1))849 stdscr\move(SCREEN_H-1, 2)850 user_input = stdscr\getstr!851 code = 'return '..user_input852 print_nil = true853 elseif c == (':')\byte! or c == ('>')\byte!854 numlines = 1855 stdscr\mvaddstr(SCREEN_H-1, 0, "> "..(' ')\rep(SCREEN_W-1))856 stdscr\move(SCREEN_H-1, 2)857 while true858 line = stdscr\getstr!859 if line == '' then break860 code ..= line..'\n'861 numlines += 1862 stdscr\mvaddstr(SCREEN_H-numlines, 0, "> "..((' ')\rep(SCREEN_W)..'\n')\rep(numlines))863 stdscr\mvaddstr(SCREEN_H-numlines, 2, code)864 stdscr\mvaddstr(SCREEN_H-1, 0, (' ')\rep(SCREEN_W))865 stdscr\move(SCREEN_H-1, 0)866 C.echo(false)867 output = ""868 if not stack_env869 stack_env = setmetatable({}, {__index:_G})870 stack_env.print = (...)->871 for i=1,select('#',...)872 if i > 1 then output ..= '\t'873 output ..= tostring(select(i, ...))874 output ..= "\n"876 for _,p in pairs(pads)877 p\refresh(true)879 run_fn, err_msg = load(code, 'user input', 't', stack_env)880 if not run_fn881 stdscr\attrset(Color('red bold'))882 stdscr\addstr(err_msg)883 stdscr\attrset(Color!)884 else885 ok, ret = pcall(run_fn)886 if not ok887 stdscr\attrset(Color('red bold'))888 stdscr\addstr(ret)889 stdscr\attrset(Color!)890 elseif ret != nil or print_nil891 value_bits = {'= ', Color('yellow'), unpack(colored_repr(ret, SCREEN_W-2, 4))}892 numlines = 1893 for i=1,#value_bits-1,2894 for nl in value_bits[i]\gmatch('\n') do numlines += 1895 for nl in output\gmatch('\n') do numlines += 1896 y, x = SCREEN_H-numlines, 0897 if output != ""898 stdscr\mvaddstr(SCREEN_H-numlines, 0, output)899 for nl in output\gmatch('\n') do y += 1900 for i=1,#value_bits-1,2901 stdscr\attrset(value_bits[i+1])902 first_line = value_bits[i]\match('^[^\n]*')903 stdscr\mvaddstr(y, x, first_line)904 x += #first_line905 for line in value_bits[i]\gmatch('\n([^\n]*)')906 stdscr\mvaddstr(y,x,(' ')\rep(SCREEN_W-x))907 y += 1908 x = 0909 stdscr\mvaddstr(y, x, line)910 stdscr\attrset(Color!)911 stdscr\mvaddstr(y,x,(' ')\rep(SCREEN_W-x))913 if c == ("?")\byte! and ret != nil914 replacing = false915 watch_index = nil916 watches = watch_exprs[pads.stack.selected]917 for i,w in ipairs watches918 if w.expr == user_input919 w.value = ret920 watch_index = i921 break922 unless watch_index923 table.insert watches, {expr:user_input, value:ret}924 watch_index = #watches925 show_vars(pads.stack.selected)926 --pads.vars\select(#pads.vars.columns[1] - #watches + watch_index)927 for i,s in ipairs(pads.vars.columns[1])928 if s == user_input929 pads.vars\select(i)930 break931 select_pad(pads.data)932 else933 numlines = 0934 for nl in output\gmatch('\n') do numlines += 1935 stdscr\mvaddstr(SCREEN_H-numlines, 0, output)938 when ('o')\byte!939 file = stack_locations[pads.stack.selected]940 filename,line_no = file\match("([^:]*):(.*)")941 line_no = tostring(pads.src.selected)942 -- Launch system editor and then redraw everything943 C.endwin!944 os.execute((os.getenv("EDITOR") or "nano").." +"..line_no.." "..filename)945 stdscr = C.initscr!946 C.cbreak!947 C.echo(false)948 C.nl(false)949 C.curs_set(0)950 C.start_color!951 C.use_default_colors!952 stdscr\clear!953 stdscr\refresh!954 for _,pad in pairs(pads) do pad\refresh(true)956 when C.KEY_RESIZE957 SCREEN_H, SCREEN_W = stdscr\getmaxyx!958 stdscr\clear!959 stdscr\refresh!960 for _,pad in pairs(pads) do pad\refresh(true)961 C.doupdate!963 when ('q')\byte!, ("Q")\byte!964 pads = {}965 C.endwin!966 return968 when ('c')\byte!969 select_pad(pads.stack) -- (C)allstack971 when ('s')\byte!972 select_pad(pads.src) -- (S)ource Code974 when ('v')\byte!975 select_pad(pads.vars) -- (V)ars977 when ('d')\byte!978 select_pad(pads.data) -- (D)ata980 when ('e')\byte!981 select_pad(pads.err) -- (E)rror983 when C.KEY_DC, C.KEY_DL, C.KEY_BACKSPACE984 if selected_pad == pads.vars985 watches = watch_exprs[pads.stack.selected]986 expr = pads.vars.columns[1][pads.vars.selected]987 for i,w in ipairs watches988 if w.expr == expr989 table.remove(watches, i)990 show_vars(pads.stack.selected)991 select_pad(pads.vars)992 break994 else995 if selected_pad996 selected_pad\keypress(c)998 C.endwin!1000 guard: (fn, ...)->1001 handler = (err_msg)->1002 print(debug.traceback(err_msg, 2))1003 xpcall(ldb.run_debugger, err_hand, err_msg)1004 return xpcall(fn, handler, ...)1006 breakpoint: ->1007 return xpcall(ldb.run_debugger, err_hand, "Breakpoint triggered!")1009 hijack: ->1010 export error, assert1011 error = (err_msg)->1012 print(debug.traceback(err_msg, 2))1013 xpcall(ldb.run_debugger, err_hand, err_msg)1014 os.exit(2)1016 assert = (condition, err_msg)->1017 if not condition1018 err_msg or= 'Assertion failed!'1019 print(debug.traceback(err_msg, 2))1020 xpcall(ldb.run_debugger, err_hand, err_msg)1021 os.exit(2)1022 return condition1024 }1025 return ldb