code / lua-debug-tui

Lines1.5K Lua1.5K Markdown33
(1.5K lines)
1 local C = require("curses")
2 local re = require('re')
3 local line_matcher = re.compile('lines<-{| line ("\n" line)* |} line<-{[^\n]*}')
4 local ldb
5 local AUTO = { }
6 local PARENT = { }
7 local _error = error
8 local _assert = assert
9 local callstack_range
10 callstack_range = function()
11 local min, max = 0, -1
12 for i = 1, 999 do
13 local info = debug.getinfo(i, 'f')
14 if not info then
15 min = i - 1
16 break
17 end
18 if info.func == ldb.run_debugger then
19 min = i + 2
20 break
21 end
22 end
23 for i = min, 999 do
24 local info = debug.getinfo(i, 'f')
25 if not info or info.func == ldb.guard then
26 max = i - 3
27 break
28 end
29 end
30 return min, max
31 end
32 local wrap_text
33 wrap_text = function(text, width)
34 local lines = { }
35 local _list_0 = line_matcher:match(text)
36 for _index_0 = 1, #_list_0 do
37 local line = _list_0[_index_0]
38 while #line > width do
39 table.insert(lines, line:sub(1, width))
40 line = line:sub(width + 1, -1)
41 if #line == 0 then
42 line = nil
43 end
44 end
45 if line then
46 table.insert(lines, line)
47 end
48 end
49 return lines
50 end
51 local Color
52 do
53 local color_index = 0
54 local existing = { }
55 local make_color
56 make_color = function(fg, bg)
57 if fg == nil then
58 fg = -1
59 end
60 if bg == nil then
61 bg = -1
62 end
63 local key = tostring(fg) .. "," .. tostring(bg)
64 if not (existing[key]) then
65 color_index = color_index + 1
66 C.init_pair(color_index, fg, bg)
67 existing[key] = C.color_pair(color_index)
68 end
69 return existing[key]
70 end
71 local color_lang = re.compile([[ x <- {|
72 {:attrs: {| {attr} (" " {attr})* |} :}
73 / (
74 ({:bg: "on " {color} :} / ({:fg: color :} (" on " {:bg: color :})?))
75 {:attrs: {| (" " {attr})* |} :})
76 |}
77 attr <- "blink" / "bold" / "dim" / "invis" / "normal" / "protect" / "reverse" / "standout" / "underline" / "altcharset"
78 color <- "black" / "blue" / "cyan" / "green" / "magenta" / "red" / "white" / "yellow" / "default"
79 ]])
80 C.COLOR_DEFAULT = -1
81 Color = function(s)
82 if s == nil then
83 s = "default"
84 end
85 local t = _assert(color_lang:match(s), "Invalid color: " .. tostring(s))
86 if t.fg then
87 t.fg = C["COLOR_" .. t.fg:upper()]
88 end
89 if t.bg then
90 t.bg = C["COLOR_" .. t.bg:upper()]
91 end
92 local c = make_color(t.fg, t.bg)
93 local _list_0 = t.attrs
94 for _index_0 = 1, #_list_0 do
95 local a = _list_0[_index_0]
96 c = c | C["A_" .. a:upper()]
97 end
98 return c
99 end
100 end
101 local Pad
103 local _class_0
104 local _base_0 = {
105 configure_size = function(self, height, width)
106 self.height, self.width = height, width
107 self._height = math.max(#self.columns[1], 1)
108 if self.height == AUTO then
109 self.height = self._height + 2
110 end
111 self._width = #self.columns - 1
112 for i, col in ipairs(self.columns) do
113 local col_width = 0
114 for _index_0 = 1, #col do
115 local chunk = col[_index_0]
116 col_width = math.max(col_width, #chunk)
117 end
118 self._width = self._width + col_width
119 end
120 self._width = math.max(self._width, 1)
121 if self.width == AUTO then
122 self.width = self._width + 2
123 end
124 end,
125 setup_chstr = function(self, i)
126 local chstr = _assert(self.chstrs[i], "Failed to find chstrs[" .. tostring(i) .. "]")
127 local x = 0
128 for c = 1, #self.columns do
129 local attr = self.colors[c](self, i)
130 local chunk = self.columns[c][i]
131 chstr:set_str(x, chunk, attr)
132 x = x + #chunk
133 if #chunk < self.column_widths[c] then
134 chstr:set_str(x, " ", attr, self.column_widths[c] - #chunk)
135 x = x + (self.column_widths[c] - #chunk)
136 end
137 if c < #self.columns then
138 chstr:set_ch(x, C.ACS_VLINE, Color("black bold"))
139 x = x + 1
140 end
141 end
142 self._pad:mvaddchstr(i - 1, 0, chstr)
143 self.dirty = true
144 end,
145 set_active = function(self, active)
146 if active == self.active then
147 return
148 end
149 self.active = active
150 self._frame:attrset(active and self.active_frame or self.inactive_frame)
151 self.dirty = true
152 end,
153 select = function(self, i)
154 if #self.columns[1] == 0 then
155 i = nil
156 end
157 if i == self.selected then
158 return self.selected
159 end
160 local old_y, old_x = self.scroll_y, self.scroll_x
161 if i ~= nil then
162 i = math.max(1, math.min(#self.columns[1], i))
163 end
164 local old_selected
165 old_selected, self.selected = self.selected, i
166 if old_selected then
167 self:setup_chstr(old_selected)
168 end
169 if self.selected then
170 self:setup_chstr(self.selected)
171 local scrolloff = 3
172 if self.selected > self.scroll_y + (self.height - 2) - scrolloff then
173 self.scroll_y = self.selected - (self.height - 2) + scrolloff
174 elseif self.selected < self.scroll_y + scrolloff then
175 self.scroll_y = self.selected - scrolloff
176 end
177 self.scroll_y = math.max(1, math.min(self._height, self.scroll_y))
178 end
179 if self.scroll_y == old_y then
180 local w = math.min(self.width - 2, self._width)
181 if old_selected and self.scroll_y <= old_selected and old_selected <= self.scroll_y + self.height - 2 then
182 self._pad:pnoutrefresh(old_selected - 1, self.scroll_x - 1, self.y + 1 + (old_selected - self.scroll_y), self.x + 1, self.y + 1 + (old_selected - self.scroll_y) + 1, self.x + w)
183 end
184 if self.selected and self.scroll_y <= self.selected and self.selected <= self.scroll_y + self.height - 2 then
185 self._pad:pnoutrefresh(self.selected - 1, self.scroll_x - 1, self.y + 1 + (self.selected - self.scroll_y), self.x + 1, self.y + 1 + (self.selected - self.scroll_y) + 1, self.x + w)
186 end
187 else
188 self.dirty = true
189 end
190 if self.on_select then
191 self:on_select(self.selected)
192 end
193 return self.selected
194 end,
195 scroll = function(self, dy, dx)
196 local old_y, old_x = self.scroll_y, self.scroll_x
197 if self.selected ~= nil then
198 self:select(self.selected + (dy or 0))
199 else
200 self.scroll_y = math.max(1, math.min(self._height - (self.height - 2 - 1), self.scroll_y + (dy or 0)))
201 end
202 self.scroll_x = math.max(1, math.min(self._width - (self.width - 2 - 1), self.scroll_x + (dx or 0)))
203 if self.scroll_y ~= old_y or self.scroll_x ~= old_x then
204 self.dirty = true
205 end
206 end,
207 refresh = function(self, force)
208 if force == nil then
209 force = false
210 end
211 if not force and not self.dirty then
212 return
213 end
214 self._frame: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)
215 if self.label then
216 self._frame:mvaddstr(0, math.floor((self.width - #self.label - 2) / 2), " " .. tostring(self.label) .. " ")
217 end
218 self._frame:refresh()
219 local h, w = math.min(self.height - 2, self._height), math.min(self.width - 2, self._width)
220 self._pad:pnoutrefresh(self.scroll_y - 1, self.scroll_x - 1, self.y + 1, self.x + 1, self.y + h, self.x + w)
221 self.dirty = false
222 end,
223 keypress = function(self, c)
224 local _exp_0 = c
225 if C.KEY_DOWN == _exp_0 or C.KEY_SR == _exp_0 or ("j"):byte() == _exp_0 then
226 return self:scroll(1, 0)
227 elseif ('J'):byte() == _exp_0 then
228 return self:scroll(10, 0)
229 elseif C.KEY_UP == _exp_0 or C.KEY_SF == _exp_0 or ("k"):byte() == _exp_0 then
230 return self:scroll(-1, 0)
231 elseif ('K'):byte() == _exp_0 then
232 return self:scroll(-10, 0)
233 elseif C.KEY_RIGHT == _exp_0 or ("l"):byte() == _exp_0 then
234 return self:scroll(0, 1)
235 elseif ("L"):byte() == _exp_0 then
236 return self:scroll(0, 10)
237 elseif C.KEY_LEFT == _exp_0 or ("h"):byte() == _exp_0 then
238 return self:scroll(0, -1)
239 elseif ("H"):byte() == _exp_0 then
240 return self:scroll(0, -10)
241 end
242 end,
243 erase = function(self)
244 self.dirty = true
245 self._frame:erase()
246 return self._frame:refresh()
247 end,
248 __gc = function(self)
249 self._frame:close()
250 return self._pad:close()
251 end
253 _base_0.__index = _base_0
254 _class_0 = setmetatable({
255 __init = function(self, label, y, x, height, width, ...)
256 self.label, self.y, self.x = label, y, x
257 self.scroll_y, self.scroll_x = 1, 1
258 self.selected = nil
259 self.columns = { }
260 self.column_widths = { }
261 self.active_frame = Color("yellow bold")
262 self.inactive_frame = Color("blue dim")
263 self.colors = { }
264 for i = 1, select('#', ...) - 1, 2 do
265 local col = select(i, ...)
266 table.insert(self.columns, col)
267 local w = 0
268 for _index_0 = 1, #col do
269 local chunk = col[_index_0]
270 w = math.max(w, #chunk)
271 end
272 table.insert(self.column_widths, w)
273 local color_fn = select(i + 1, ...) or (function(self, i)
274 return Color()
275 end)
276 _assert(type(color_fn) == 'function', "Invalid color function type: " .. tostring(type(color_fn)))
277 table.insert(self.colors, color_fn)
278 end
279 self:configure_size(height, width)
280 self._frame = C.newwin(self.height, self.width, self.y, self.x)
281 self._frame:immedok(true)
282 self._pad = C.newpad(self._height, self._width)
283 self._pad:scrollok(true)
284 self:set_active(false)
285 self.chstrs = { }
286 for i = 1, #self.columns[1] do
287 self.chstrs[i] = C.new_chstr(self._width)
288 self:setup_chstr(i)
289 end
290 self.dirty = true
291 end,
292 __base = _base_0,
293 __name = "Pad"
294 }, {
295 __index = _base_0,
296 __call = function(cls, ...)
297 local _self_0 = setmetatable({}, _base_0)
298 cls.__init(_self_0, ...)
299 return _self_0
300 end
302 _base_0.__class = _class_0
303 Pad = _class_0
304 end
305 local NumberedPad
307 local _class_0
308 local _parent_0 = Pad
309 local _base_0 = { }
310 _base_0.__index = _base_0
311 setmetatable(_base_0, _parent_0.__base)
312 _class_0 = setmetatable({
313 __init = function(self, label, y, x, height, width, ...)
314 self.label, self.y, self.x = label, y, x
315 local col1 = select(1, ...)
316 local fmt = "%" .. tostring(#tostring(#col1)) .. "d"
317 local line_nums
319 local _accum_0 = { }
320 local _len_0 = 1
321 for i = 1, #col1 do
322 _accum_0[_len_0] = fmt:format(i)
323 _len_0 = _len_0 + 1
324 end
325 line_nums = _accum_0
326 end
327 local cols = {
328 line_nums,
329 (function(self, i)
330 return i == self.selected and Color() or Color("yellow")
331 end),
332 ...
334 return _class_0.__parent.__init(self, self.label, self.y, self.x, height, width, unpack(cols))
335 end,
336 __base = _base_0,
337 __name = "NumberedPad",
338 __parent = _parent_0
339 }, {
340 __index = function(cls, name)
341 local val = rawget(_base_0, name)
342 if val == nil then
343 local parent = rawget(cls, "__parent")
344 if parent then
345 return parent[name]
346 end
347 else
348 return val
349 end
350 end,
351 __call = function(cls, ...)
352 local _self_0 = setmetatable({}, _base_0)
353 cls.__init(_self_0, ...)
354 return _self_0
355 end
357 _base_0.__class = _class_0
358 if _parent_0.__inherited then
359 _parent_0.__inherited(_parent_0, _class_0)
360 end
361 NumberedPad = _class_0
362 end
363 local expansions = { }
364 local TOP_LOCATION, KEY, VALUE = { }, { }, { }
365 local locations = { }
366 local Location
367 Location = function(old_loc, kind, key)
368 if old_loc == nil then
369 return TOP_LOCATION
370 end
371 if not (locations[old_loc]) then
372 locations[old_loc] = { }
373 end
374 if not (locations[old_loc][kind]) then
375 locations[old_loc][kind] = { }
376 end
377 if not (locations[old_loc][kind][key]) then
378 locations[old_loc][kind][key] = {
379 old_loc = old_loc,
380 kind = kind,
381 key = key
383 end
384 return locations[old_loc][kind][key]
385 end
386 local expand
387 expand = function(kind, key, location)
388 expansions[Location(location, kind, key)] = true
389 end
390 local collapse
391 collapse = function(kind, key, location)
392 expansions[Location(location, kind, key)] = nil
393 end
394 local is_key_expanded
395 is_key_expanded = function(location, key)
396 return expansions[Location(location, KEY, key)]
397 end
398 local is_value_expanded
399 is_value_expanded = function(location, key)
400 return expansions[Location(location, VALUE, key)]
401 end
402 local TYPE_COLORS = setmetatable({ }, {
403 __index = 0
405 local colored_repr
406 colored_repr = function(x, width, depth)
407 if depth == nil then
408 depth = 2
409 end
410 depth = depth - 1
411 local x_type = type(x)
412 if x_type == 'table' then
413 if next(x) == nil then
414 return {
415 "{}",
416 TYPE_COLORS.table
418 end
419 if depth == 0 then
420 return {
421 "{",
422 TYPE_COLORS.table,
423 "...",
424 Color('white'),
425 "}",
426 TYPE_COLORS.table
428 end
429 local ret = {
430 "{",
431 TYPE_COLORS.table
433 local i = 1
434 for k, v in pairs(x) do
435 if k == i then
436 local _list_0 = colored_repr(x[i], width, depth)
437 for _index_0 = 1, #_list_0 do
438 local s = _list_0[_index_0]
439 ret[#ret + 1] = s
440 end
441 i = i + 1
442 else
443 local _list_0 = colored_repr(k, width, depth)
444 for _index_0 = 1, #_list_0 do
445 local s = _list_0[_index_0]
446 ret[#ret + 1] = s
447 end
448 ret[#ret + 1] = ' = '
449 ret[#ret + 1] = Color('white')
450 local _list_1 = colored_repr(v, width, depth)
451 for _index_0 = 1, #_list_1 do
452 local s = _list_1[_index_0]
453 ret[#ret + 1] = s
454 end
455 end
456 ret[#ret + 1] = ', '
457 ret[#ret + 1] = Color('white')
458 end
459 if #ret > 2 then
460 ret[#ret] = nil
461 ret[#ret] = nil
462 end
463 local len = 0
464 for i = 1, #ret - 1, 2 do
465 len = len + #ret[i]
466 end
467 for i = #ret - 1, 3, -2 do
468 if len <= width - 1 then
469 break
470 end
471 if ret[i + 2] then
472 ret[i + 2], ret[i + 3] = nil, nil
473 end
474 ret[i] = '...'
475 ret[i + 1] = Color('white')
476 end
477 ret[#ret + 1] = '}'
478 ret[#ret + 1] = TYPE_COLORS.table
479 return ret
480 elseif x_type == 'string' then
481 local ret = {
482 (x:match('^[^\t\r\v\b\a\n]*')),
483 TYPE_COLORS.string
485 for escape, line in x:gmatch('([\t\r\v\b\a\n])([^\t\r\v\b\a\n]*)') do
486 ret[#ret + 1] = '\\' .. ({
487 ['\t'] = 't',
488 ['\r'] = 'r',
489 ['\v'] = 'v',
490 ['\b'] = 'b',
491 ['\a'] = 'a',
492 ['\n'] = 'n'
493 })[escape]
494 ret[#ret + 1] = Color('white on black')
495 ret[#ret + 1] = line
496 ret[#ret + 1] = TYPE_COLORS.string
497 end
498 local len = 0
499 for i = 1, #ret - 1, 2 do
500 len = len + #ret[i]
501 end
502 for i = #ret - 1, 1, -2 do
503 if len <= width then
504 break
505 end
506 if ret[i + 2] then
507 ret[i + 2], ret[i + 3] = nil, nil
508 end
509 len = len - #ret[i]
510 if len <= width then
511 ret[i] = ret[i]:sub(1, width - len - 3)
512 ret[i + 2] = '...'
513 ret[i + 3] = Color('blue')
514 break
515 end
516 end
517 return ret
518 else
519 local ok, s = pcall(tostring, x)
520 if not ok then
521 return {
522 "tostring error: " .. s,
523 Color("red")
525 end
526 if #s > width then
527 return {
528 s:sub(1, width - 3),
529 TYPE_COLORS[type(x)],
530 '...',
531 Color('blue')
533 else
534 return {
536 TYPE_COLORS[type(x)]
538 end
539 end
540 end
541 local make_lines
542 make_lines = function(location, x, width)
543 local _exp_0 = type(x)
544 if 'string' == _exp_0 then
545 local lines = { }
546 local _list_0 = line_matcher:match(x)
547 for _index_0 = 1, #_list_0 do
548 local line = _list_0[_index_0]
549 local wrapped = wrap_text(line, width - 1)
550 for i, subline in ipairs(wrapped) do
551 local _line = {
552 location = location
554 if i > 1 then
555 table.insert(_line, C.ACS_BULLET)
556 table.insert(_line, Color('black bold altcharset'))
557 end
558 table.insert(_line, subline)
559 table.insert(_line, Color('blue on black'))
560 table.insert(lines, _line)
561 end
562 end
563 if #lines == 0 then
564 table.insert(lines, {
565 location = location,
566 "''",
567 Color('blue')
569 end
570 return lines
571 elseif 'table' == _exp_0 then
572 local prepend
573 prepend = function(line, ...)
574 for i = 1, select('#', ...) do
575 table.insert(line, i, (select(i, ...)))
576 end
577 end
578 local lines = { }
579 for k, v in pairs(x) do
580 if is_key_expanded(location, k) and is_value_expanded(location, k) then
581 table.insert(lines, {
582 location = Location(location, KEY, k),
583 'key',
584 Color('green bold'),
585 '/',
586 Color(),
587 'value',
588 Color('blue bold'),
589 ':',
590 Color('white')
592 local key_lines = make_lines(Location(location, KEY, k), k, width - 1)
593 for i, key_line in ipairs(key_lines) do
594 if i == 1 then
595 prepend(key_line, ' ', Color(), C.ACS_DIAMOND, Color('green bold'), ' ', Color())
596 else
597 prepend(key_line, ' ', Color())
598 end
599 table.insert(lines, key_line)
600 end
601 local value_lines = make_lines(Location(location, VALUE, k), v, width - 2)
602 for i, value_line in ipairs(value_lines) do
603 if i == 1 then
604 prepend(value_line, ' ', Color(), C.ACS_DIAMOND, Color('blue bold'), ' ', Color())
605 else
606 prepend(value_line, ' ', Color())
607 end
608 table.insert(lines, value_line)
609 end
610 elseif is_value_expanded(location, k) then
611 local k_str = colored_repr(k, width - 1)
612 table.insert(lines, {
613 location = Location(location, KEY, k),
614 '-',
615 Color('red'),
616 unpack(k_str)
618 local v_lines = make_lines(Location(location, VALUE, k), v, width - 1)
619 prepend(v_lines[1], ' ', Color())
620 for i = 2, #v_lines do
621 prepend(v_lines[i], ' ', Color())
622 end
623 for _index_0 = 1, #v_lines do
624 local v_line = v_lines[_index_0]
625 table.insert(lines, v_line)
626 end
627 elseif is_key_expanded(location, k) then
628 local k_lines = make_lines(Location(location, KEY, k), k, width - 4)
629 for i = 1, #k_lines do
630 prepend(k_lines[i], ' ', Color())
631 end
632 for _index_0 = 1, #k_lines do
633 local k_line = k_lines[_index_0]
634 table.insert(lines, k_line)
635 end
636 local v_str = colored_repr(v, width - 2)
637 table.insert(lines, {
638 location = Location(location, VALUE, k),
639 ' ',
640 Color(),
641 unpack(v_str)
643 else
644 local k_space = math.floor((width - 4) / 3)
645 local k_str = colored_repr(k, k_space)
646 local v_space = (width - 4) - #k_str
647 local v_str = colored_repr(v, v_space)
648 local line = {
649 location = Location(location, VALUE, k),
650 '+',
651 Color('green'),
652 unpack(k_str)
654 table.insert(line, ' = ')
655 table.insert(line, Color('white'))
656 for _index_0 = 1, #v_str do
657 local s = v_str[_index_0]
658 table.insert(line, s)
659 end
660 table.insert(lines, line)
661 end
662 end
663 if #lines == 0 then
664 table.insert(lines, {
665 location = location,
666 '{}',
667 TYPE_COLORS.table
669 end
670 return lines
671 else
672 if getmetatable(x) and getmetatable(x).__pairs then
673 local lines = make_lines(location, (function()
674 local _tbl_0 = { }
675 for k, v in pairs(x) do
676 _tbl_0[k] = v
677 end
678 return _tbl_0
679 end)(), width)
680 if getmetatable(x).__tostring then
681 local s_lines = { }
682 local ok, s = pcall(tostring, x)
683 if not ok then
684 s = "tostring error: " .. s
685 end
686 local _list_0 = line_matcher:match(s)
687 for _index_0 = 1, #_list_0 do
688 local line = _list_0[_index_0]
689 local wrapped = wrap_text(line, width)
690 for i, subline in ipairs(wrapped) do
691 table.insert(s_lines, {
692 location = location,
693 subline,
694 ok and Color('yellow') or Color('red')
696 end
697 end
698 for i = 1, #s_lines do
699 table.insert(lines, i, s_lines[i])
700 end
701 end
702 return lines
703 end
704 local str = tostring(x)
705 if #str > width then
706 str = str:sub(1, width - 3) .. '...'
707 end
708 return {
710 location = location,
711 str,
712 TYPE_COLORS[type(x)]
715 end
716 end
717 local DataViewer
719 local _class_0
720 local _parent_0 = Pad
721 local _base_0 = {
722 setup_chstr = function(self, i) end,
723 configure_size = function(self, height, width)
724 self.height, self.width = height, width
725 self._height, self._width = #self.chstrs, self.width - 2
726 end,
727 select = function(self, i)
728 if #self.chstrs == 0 then
729 i = nil
730 end
731 if i == self.selected then
732 return self.selected
733 end
734 local old_y, old_x = self.scroll_y, self.scroll_x
735 if i ~= nil then
736 i = math.max(1, math.min(#self.chstrs, i))
737 end
738 local old_selected
739 old_selected, self.selected = self.selected, i
740 if old_selected and self.chstrs[old_selected] then
741 self.chstrs[old_selected]:set_str(0, ' ', Color('yellow bold'))
742 self._pad:mvaddchstr(old_selected - 1, 0, self.chstrs[old_selected])
743 end
744 if self.selected then
745 self.chstrs[self.selected]:set_ch(0, C.ACS_RARROW, Color('yellow bold'))
746 self._pad:mvaddchstr(self.selected - 1, 0, self.chstrs[self.selected])
747 local scrolloff = 3
748 if self.selected > self.scroll_y + (self.height - 2) - scrolloff then
749 self.scroll_y = self.selected - (self.height - 2) + scrolloff
750 elseif self.selected < self.scroll_y + scrolloff then
751 self.scroll_y = self.selected - scrolloff
752 end
753 self.scroll_y = math.max(1, math.min(self._height, self.scroll_y))
754 end
755 if self.scroll_y == old_y then
756 local w = math.min(self.width - 2, self._width)
757 if old_selected and self.scroll_y <= old_selected and old_selected <= self.scroll_y + self.height - 2 then
758 self._pad:pnoutrefresh(old_selected - 1, self.scroll_x - 1, self.y + 1 + (old_selected - self.scroll_y), self.x + 1, self.y + 1 + (old_selected - self.scroll_y) + 1, self.x + w)
759 end
760 if self.selected and self.scroll_y <= self.selected and self.selected <= self.scroll_y + self.height - 2 then
761 self._pad:pnoutrefresh(self.selected - 1, self.scroll_x - 1, self.y + 1 + (self.selected - self.scroll_y), self.x + 1, self.y + 1 + (self.selected - self.scroll_y) + 1, self.x + w)
762 end
763 else
764 self.dirty = true
765 end
766 if self.on_select then
767 self:on_select(self.selected)
768 end
769 return self.selected
770 end,
771 keypress = function(self, c)
772 local _exp_0 = c
773 if C.KEY_DOWN == _exp_0 or C.KEY_SR == _exp_0 or ("j"):byte() == _exp_0 then
774 return self:scroll(1, 0)
775 elseif ('J'):byte() == _exp_0 then
776 return self:scroll(10, 0)
777 elseif C.KEY_UP == _exp_0 or C.KEY_SF == _exp_0 or ("k"):byte() == _exp_0 then
778 return self:scroll(-1, 0)
779 elseif ('K'):byte() == _exp_0 then
780 return self:scroll(-10, 0)
781 elseif C.KEY_RIGHT == _exp_0 or ("l"):byte() == _exp_0 then
782 expansions[self.chstr_locations[self.selected]] = true
783 return self:full_refresh()
784 elseif ("L"):byte() == _exp_0 then
785 expansions[self.chstr_locations[self.selected]] = true
786 return self:full_refresh()
787 elseif C.KEY_LEFT == _exp_0 or ("h"):byte() == _exp_0 then
788 local loc = self.chstr_locations[self.selected]
789 if expansions[loc] == nil then
790 loc = Location(loc.old_loc, (loc.kind == KEY and VALUE or KEY), loc.key)
791 end
792 while loc and expansions[loc] == nil do
793 loc = loc.old_loc
794 end
795 if loc then
796 expansions[loc] = nil
797 end
798 self:full_refresh()
799 if loc and self.chstr_locations[self.selected] ~= loc then
800 for i, chstr_loc in ipairs(self.chstr_locations) do
801 if chstr_loc == loc then
802 self:select(i)
803 break
804 end
805 end
806 elseif not loc then
807 return self:select(1)
808 end
809 elseif ("H"):byte() == _exp_0 then
810 local loc = self.chstr_locations[self.selected]
811 if expansions[loc] == nil then
812 loc = Location(loc.old_loc, (loc.kind == KEY and VALUE or KEY), loc.key)
813 end
814 while loc and expansions[loc] == nil do
815 loc = loc.old_loc
816 end
817 if loc then
818 expansions[loc] = nil
819 end
820 self:full_refresh()
821 if loc and self.chstr_locations[self.selected] ~= loc then
822 for i, chstr_loc in ipairs(self.chstr_locations) do
823 if chstr_loc == loc then
824 self:select(i)
825 break
826 end
827 end
828 elseif not loc then
829 return self:select(1)
830 end
831 end
832 end
834 _base_0.__index = _base_0
835 setmetatable(_base_0, _parent_0.__base)
836 _class_0 = setmetatable({
837 __init = function(self, data, label, y, x, height, width)
838 self.data, self.label, self.y, self.x = data, label, y, x
839 self.scroll_y, self.scroll_x = 1, 1
840 self.selected = nil
841 self.active_frame = Color("yellow bold")
842 self.inactive_frame = Color("blue dim")
843 self.full_refresh = function()
844 local old_location = self.selected and self.chstr_locations and self.chstr_locations[self.selected]
845 self.chstrs, self.chstr_locations = { }, { }
846 local W = width - 3
847 local lines = make_lines(TOP_LOCATION, self.data, W)
848 for i, line in ipairs(lines) do
849 local chstr = C.new_chstr(W)
850 if i == self.selected then
851 chstr:set_ch(0, C.ACS_RARROW, Color('yellow bold'))
852 else
853 chstr:set_str(0, ' ', Color('yellow bold'))
854 end
855 local offset = 1
856 for j = 1, #line - 1, 2 do
857 local chunk, attrs = line[j], line[j + 1]
858 if type(chunk) == 'number' then
859 chstr:set_ch(offset, chunk, attrs)
860 offset = offset + 1
861 else
862 chstr:set_str(offset, chunk, attrs)
863 offset = offset + #chunk
864 end
865 end
866 if offset < W then
867 chstr:set_str(offset, ' ', attrs, W - offset)
868 end
869 table.insert(self.chstrs, chstr)
870 table.insert(self.chstr_locations, line.location)
871 end
872 self._height, self._width = #self.chstrs, self.width - 2
873 self._pad:resize(self._height, self._width)
874 for i, chstr in ipairs(self.chstrs) do
875 self._pad:mvaddchstr(i - 1, 0, chstr)
876 end
877 self.dirty = true
878 if old_location then
879 for i, loc in ipairs(self.chstr_locations) do
880 if loc == old_location then
881 self:select(i)
882 break
883 end
884 end
885 end
886 end
887 self.height, self.width = height, width
888 self._frame = C.newwin(self.height, self.width, self.y, self.x)
889 self._frame:immedok(true)
890 self._pad = C.newpad(self.height - 2, self.width - 2)
891 self._pad:scrollok(true)
892 self:set_active(false)
893 self:full_refresh()
894 return self:select(1)
895 end,
896 __base = _base_0,
897 __name = "DataViewer",
898 __parent = _parent_0
899 }, {
900 __index = function(cls, name)
901 local val = rawget(_base_0, name)
902 if val == nil then
903 local parent = rawget(cls, "__parent")
904 if parent then
905 return parent[name]
906 end
907 else
908 return val
909 end
910 end,
911 __call = function(cls, ...)
912 local _self_0 = setmetatable({}, _base_0)
913 cls.__init(_self_0, ...)
914 return _self_0
915 end
917 _base_0.__class = _class_0
918 if _parent_0.__inherited then
919 _parent_0.__inherited(_parent_0, _class_0)
920 end
921 DataViewer = _class_0
922 end
923 local ok, to_lua = pcall(function()
924 return require('moonscript.base').to_lua
925 end)
926 if not ok then
927 to_lua = function()
928 return nil
929 end
930 end
931 local file_cache = setmetatable({ }, {
932 __index = function(self, filename)
933 local file = io.open(filename)
934 if not file then
935 return nil
936 end
937 local contents = file:read("a"):sub(1, -2)
938 self[filename] = contents
939 return contents
940 end
942 local line_tables = setmetatable({ }, {
943 __index = function(self, filename)
944 local file = file_cache[filename]
945 if not file then
946 return nil
947 end
948 local line_table
949 ok, line_table = to_lua(file)
950 if ok then
951 self[filename] = line_table
952 return line_table
953 end
954 end
956 local err_hand
957 err_hand = function(err)
958 C.endwin()
959 print("Error in debugger.")
960 print(debug.traceback(err, 2))
961 return os.exit(2)
962 end
963 ldb = {
964 run_debugger = function(err_msg)
965 local select_pad
966 err_msg = err_msg or ''
967 if type(err_msg) ~= 'string' then
968 err_msg = tostring(err_msg)
969 end
970 local stdscr = C.initscr()
971 local SCREEN_H, SCREEN_W = stdscr:getmaxyx()
972 C.cbreak()
973 C.echo(false)
974 C.nl(false)
975 C.curs_set(0)
976 C.start_color()
977 C.use_default_colors()
979 TYPE_COLORS.string = Color('blue on black')
980 TYPE_COLORS.number = Color('magenta')
981 TYPE_COLORS.boolean = Color('cyan')
982 TYPE_COLORS["nil"] = Color('cyan')
983 TYPE_COLORS.table = Color('yellow')
984 TYPE_COLORS["function"] = Color('green')
985 TYPE_COLORS.userdata = Color('cyan bold')
986 TYPE_COLORS.thread = Color('blue')
987 end
989 stdscr:wbkgd(Color("yellow on red bold"))
990 stdscr:clear()
991 stdscr:refresh()
992 local lines = wrap_text("ERROR!\n \n " .. err_msg .. "\n \npress any key...", math.floor(SCREEN_W - 2))
993 local max_line = 0
994 for _index_0 = 1, #lines do
995 local line = lines[_index_0]
996 max_line = math.max(max_line, #line)
997 end
998 for i, line in ipairs(lines) do
999 if i == 1 or i == #lines then
1000 stdscr:mvaddstr(math.floor(SCREEN_H / 2 - #lines / 2) + i, math.floor((SCREEN_W - #line) / 2), line)
1001 else
1002 stdscr:mvaddstr(math.floor(SCREEN_H / 2 - #lines / 2) + i, math.floor((SCREEN_W - max_line) / 2), line)
1003 end
1004 end
1005 stdscr:refresh()
1006 C.doupdate()
1007 stdscr:getch()
1008 end
1009 stdscr:keypad()
1010 stdscr:wbkgd(Color())
1011 stdscr:clear()
1012 stdscr:refresh()
1013 local pads = { }
1015 local err_msg_lines = wrap_text(err_msg, SCREEN_W - 4)
1016 for i, line in ipairs(err_msg_lines) do
1017 err_msg_lines[i] = (" "):rep(2) .. line
1018 end
1019 local height = math.min(#err_msg_lines + 2, 7)
1020 pads.err = Pad("(E)rror Message", 0, 0, height, SCREEN_W, err_msg_lines, function(self, i)
1021 return Color("red bold")
1022 end)
1023 end
1024 local err_lines = { }
1025 local stack_sources = { }
1026 local stack_locations = { }
1027 local watch_exprs = setmetatable({ }, {
1028 __index = function(self, k)
1029 local t = { }
1030 self[k] = t
1031 return t
1032 end
1035 local stack_names = { }
1036 local max_filename, max_fn_name = 0, 0
1037 local stack_min, stack_max = callstack_range()
1038 for i = stack_min, stack_max do
1039 local info = debug.getinfo(i)
1040 if not info then
1041 break
1042 end
1043 local fn_name = info.name
1044 if not (fn_name) then
1045 if info.istailcall then
1046 fn_name = "<tail call>"
1047 else
1048 fn_name = "<anonymous>"
1049 end
1050 end
1051 table.insert(stack_names, fn_name)
1052 local line
1053 if info.short_src then
1054 local line_table = line_tables[info.short_src]
1055 if line_table then
1056 local char = line_table[info.currentline]
1057 local line_num = 1
1058 local file = file_cache[info.short_src] or info.source
1059 for _ in file:sub(1, char):gmatch("\n") do
1060 line_num = line_num + 1
1061 end
1062 line = tostring(info.short_src) .. ":" .. tostring(line_num)
1063 else
1064 line = info.short_src .. ":" .. info.currentline
1065 end
1066 else
1067 line = "???"
1068 end
1069 err_lines[line] = true
1070 table.insert(stack_locations, line)
1071 table.insert(stack_sources, info.source)
1072 max_filename = math.max(max_filename, #line)
1073 max_fn_name = math.max(max_fn_name, #fn_name)
1074 end
1075 max_fn_name, max_filename = 0, 0
1076 for i = 1, #stack_names do
1077 max_fn_name = math.max(max_fn_name, #stack_names[i])
1078 max_filename = math.max(max_filename, #stack_locations[i])
1079 end
1080 local stack_h = math.floor(SCREEN_H * .6)
1081 local stack_w = math.min(max_fn_name + 3 + max_filename, math.floor(1 / 3 * SCREEN_W))
1082 pads.stack = Pad("(C)allstack", pads.err.height, SCREEN_W - stack_w, stack_h, stack_w, stack_names, (function(self, i)
1083 return (i == self.selected) and Color("black on green") or Color("green bold")
1084 end), stack_locations, (function(self, i)
1085 return (i == self.selected) and Color("black on cyan") or Color("cyan bold")
1086 end))
1087 end
1088 local show_src
1089 show_src = function(filename, line_no, file_contents)
1090 if file_contents == nil then
1091 file_contents = nil
1092 end
1093 if pads.src then
1094 if pads.src.filename == filename then
1095 pads.src:select(line_no)
1096 pads.src.colors[2] = function(self, i)
1097 if i == line_no and i == self.selected then
1098 return Color("yellow on red bold")
1099 elseif i == line_no then
1100 return Color("yellow on red")
1101 elseif err_lines[tostring(filename) .. ":" .. tostring(i)] == true then
1102 return Color("red on black bold")
1103 elseif i == self.selected then
1104 return Color("reverse")
1105 else
1106 return Color()
1107 end
1108 end
1109 for line, _ in pairs(err_lines) do
1110 local _filename, i = line:match("([^:]*):(%d*).*")
1111 if _filename == filename and tonumber(i) then
1112 pads.src:setup_chstr(tonumber(i))
1113 end
1114 end
1115 pads.src:select(line_no)
1116 return
1117 else
1118 pads.src:erase()
1119 end
1120 end
1121 file_contents = file_contents or file_cache[filename]
1122 if file_contents then
1123 local src_lines = line_matcher:match(file_contents)
1124 pads.src = NumberedPad("(S)ource Code", pads.err.height, 0, pads.stack.height, pads.stack.x, src_lines, function(self, i)
1125 if i == line_no and i == self.selected then
1126 return Color("yellow on red bold")
1127 elseif i == line_no then
1128 return Color("yellow on red")
1129 elseif err_lines[tostring(filename) .. ":" .. tostring(i)] == true then
1130 return Color("red on black bold")
1131 elseif i == self.selected then
1132 return Color("reverse")
1133 else
1134 return Color()
1135 end
1136 end)
1137 pads.src:select(line_no)
1138 else
1139 local lines = { }
1140 for i = 1, math.floor(pads.stack.height / 2) - 1 do
1141 table.insert(lines, "")
1142 end
1143 local s = "<no source code found>"
1144 s = (" "):rep(math.floor((pads.stack.x - 2 - #s) / 2)) .. s
1145 table.insert(lines, s)
1146 pads.src = Pad("(S)ource Code", pads.err.height, 0, pads.stack.height, pads.stack.x, lines, function()
1147 return Color("red")
1148 end)
1149 end
1150 pads.src.filename = filename
1151 end
1152 local stack_env
1153 local show_vars
1154 show_vars = function(stack_index)
1155 if pads.vars then
1156 pads.vars:erase()
1157 end
1158 if pads.data then
1159 pads.data:erase()
1160 end
1161 local callstack_min, _ = callstack_range()
1162 local var_names, values = { }, { }
1163 stack_env = setmetatable({ }, {
1164 __index = _G
1166 for loc = 1, 999 do
1167 local name, value = debug.getlocal(callstack_min + stack_index - 1, loc)
1168 if name == nil then
1169 break
1170 end
1171 table.insert(var_names, tostring(name))
1172 table.insert(values, value)
1173 stack_env[name] = value
1174 end
1175 local num_locals = #var_names
1176 local info = debug.getinfo(callstack_min + stack_index - 1, "uf")
1177 for upval = 1, info.nups do
1178 local _continue_0 = false
1179 repeat
1180 local name, value = debug.getupvalue(info.func, upval)
1181 if name == "_ENV" then
1182 _continue_0 = true
1183 break
1184 end
1185 table.insert(var_names, tostring(name))
1186 table.insert(values, value)
1187 stack_env[name] = value
1188 _continue_0 = true
1189 until true
1190 if not _continue_0 then
1191 break
1192 end
1193 end
1194 local _list_0 = watch_exprs[stack_index]
1195 for _index_0 = 1, #_list_0 do
1196 local _continue_0 = false
1197 repeat
1198 local watch = _list_0[_index_0]
1199 if stack_env[watch.expr] ~= nil then
1200 _continue_0 = true
1201 break
1202 end
1203 table.insert(var_names, watch.expr)
1204 table.insert(values, watch.value)
1205 stack_env[watch.expr] = watch.value
1206 _continue_0 = true
1207 until true
1208 if not _continue_0 then
1209 break
1210 end
1211 end
1212 local var_y = pads.stack.y + pads.stack.height
1213 local var_x = 0
1214 local height = SCREEN_H - (pads.err.height + pads.stack.height)
1215 pads.vars = Pad("(V)ars", var_y, var_x, height, AUTO, var_names, function(self, i)
1216 local color
1217 if i <= num_locals then
1218 color = Color()
1219 elseif i <= num_locals + info.nups - 1 then
1220 color = Color("blue")
1221 else
1222 color = Color("green")
1223 end
1224 if i == self.selected then
1225 color = color + C.A_REVERSE
1226 end
1227 return color
1228 end)
1229 pads.vars.keypress = function(self, key)
1230 if key == ('l'):byte() or key == C.KEY_RIGHT then
1231 return select_pad(pads.data)
1232 else
1233 return Pad.keypress(self, key)
1234 end
1235 end
1236 pads.vars.on_select = function(self, var_index)
1237 if var_index == nil then
1238 return
1239 end
1240 local value_x = pads.vars.x + pads.vars.width
1241 local value_w = SCREEN_W - (value_x)
1242 local value = stack_env[var_names[var_index]]
1243 local type_str = tostring(type(value))
1244 pads.data = DataViewer(value, "(D)ata [" .. tostring(type_str) .. "]", var_y, value_x, pads.vars.height, value_w)
1245 pads.data.keypress = function(self, key)
1246 if (key == ('h'):byte() or key == C.KEY_LEFT) and self.selected == 1 then
1247 return select_pad(pads.vars)
1248 else
1249 return DataViewer.keypress(self, key)
1250 end
1251 end
1252 collectgarbage()
1253 return collectgarbage()
1254 end
1255 return pads.vars:select(1)
1256 end
1257 pads.stack.on_select = function(self, stack_index)
1258 local filename, line_no = pads.stack.columns[2][stack_index]:match("^(.*):(%d*)$")
1259 line_no = tonumber(line_no)
1260 show_src(filename, line_no, filename and file_cache[filename] or stack_sources[stack_index])
1261 return show_vars(stack_index)
1262 end
1263 pads.stack:select(1)
1264 local selected_pad = nil
1265 select_pad = function(pad)
1266 if selected_pad ~= pad then
1267 if selected_pad then
1268 selected_pad:set_active(false)
1269 selected_pad:refresh()
1270 end
1271 selected_pad = pad
1272 if selected_pad then
1273 selected_pad:set_active(true)
1274 return selected_pad:refresh()
1275 end
1276 end
1277 end
1278 select_pad(pads.stack)
1279 while true do
1280 for _, p in pairs(pads) do
1281 p:refresh()
1282 end
1283 local s = " press 'q' to quit "
1284 stdscr:mvaddstr(math.floor(SCREEN_H - 1), math.floor((SCREEN_W - #s)), s)
1285 local c = stdscr:getch()
1286 local _exp_0 = c
1287 if (':'):byte() == _exp_0 or ('>'):byte() == _exp_0 or ('?'):byte() == _exp_0 then
1288 C.echo(true)
1289 local print_nil = false
1290 local user_input
1291 local code = ''
1292 if c == ('?'):byte() then
1293 stdscr:mvaddstr(SCREEN_H - 1, 0, "? " .. (' '):rep(SCREEN_W - 1))
1294 stdscr:move(SCREEN_H - 1, 2)
1295 user_input = stdscr:getstr()
1296 code = 'return ' .. user_input
1297 print_nil = true
1298 elseif c == (':'):byte() or c == ('>'):byte() then
1299 local numlines = 1
1300 stdscr:mvaddstr(SCREEN_H - 1, 0, "> " .. (' '):rep(SCREEN_W - 1))
1301 stdscr:move(SCREEN_H - 1, 2)
1302 while true do
1303 local line = stdscr:getstr()
1304 if line == '' then
1305 break
1306 end
1307 code = code .. (line .. '\n')
1308 numlines = numlines + 1
1309 stdscr:mvaddstr(SCREEN_H - numlines, 0, "> " .. ((' '):rep(SCREEN_W) .. '\n'):rep(numlines))
1310 stdscr:mvaddstr(SCREEN_H - numlines, 2, code)
1311 stdscr:mvaddstr(SCREEN_H - 1, 0, (' '):rep(SCREEN_W))
1312 stdscr:move(SCREEN_H - 1, 0)
1313 end
1314 end
1315 C.echo(false)
1316 local output = ""
1317 if not stack_env then
1318 stack_env = setmetatable({ }, {
1319 __index = _G
1321 end
1322 stack_env.print = function(...)
1323 for i = 1, select('#', ...) do
1324 if i > 1 then
1325 output = output .. '\t'
1326 end
1327 output = output .. tostring(select(i, ...))
1328 end
1329 output = output .. "\n"
1330 end
1331 for _, p in pairs(pads) do
1332 p:refresh(true)
1333 end
1334 local run_fn
1335 run_fn, err_msg = load(code, 'user input', 't', stack_env)
1336 if not run_fn then
1337 stdscr:attrset(Color('red bold'))
1338 stdscr:addstr(err_msg)
1339 stdscr:attrset(Color())
1340 else
1341 local ret
1342 ok, ret = pcall(run_fn)
1343 if not ok then
1344 stdscr:attrset(Color('red bold'))
1345 stdscr:addstr(ret)
1346 stdscr:attrset(Color())
1347 elseif ret ~= nil or print_nil then
1348 local value_bits = {
1349 '= ',
1350 Color('yellow'),
1351 unpack(colored_repr(ret, SCREEN_W - 2, 4))
1353 local numlines = 1
1354 for i = 1, #value_bits - 1, 2 do
1355 for nl in value_bits[i]:gmatch('\n') do
1356 numlines = numlines + 1
1357 end
1358 end
1359 for nl in output:gmatch('\n') do
1360 numlines = numlines + 1
1361 end
1362 local y, x = SCREEN_H - numlines, 0
1363 if output ~= "" then
1364 stdscr:mvaddstr(SCREEN_H - numlines, 0, output)
1365 for nl in output:gmatch('\n') do
1366 y = y + 1
1367 end
1368 end
1369 for i = 1, #value_bits - 1, 2 do
1370 stdscr:attrset(value_bits[i + 1])
1371 local first_line = value_bits[i]:match('^[^\n]*')
1372 stdscr:mvaddstr(y, x, first_line)
1373 x = x + #first_line
1374 for line in value_bits[i]:gmatch('\n([^\n]*)') do
1375 stdscr:mvaddstr(y, x, (' '):rep(SCREEN_W - x))
1376 y = y + 1
1377 x = 0
1378 stdscr:mvaddstr(y, x, line)
1379 end
1380 end
1381 stdscr:attrset(Color())
1382 stdscr:mvaddstr(y, x, (' '):rep(SCREEN_W - x))
1383 if c == ("?"):byte() and ret ~= nil then
1384 local replacing = false
1385 local watch_index = nil
1386 local watches = watch_exprs[pads.stack.selected]
1387 for i, w in ipairs(watches) do
1388 if w.expr == user_input then
1389 w.value = ret
1390 watch_index = i
1391 break
1392 end
1393 end
1394 if not (watch_index) then
1395 table.insert(watches, {
1396 expr = user_input,
1397 value = ret
1399 watch_index = #watches
1400 end
1401 show_vars(pads.stack.selected)
1402 for i, s in ipairs(pads.vars.columns[1]) do
1403 if s == user_input then
1404 pads.vars:select(i)
1405 break
1406 end
1407 end
1408 select_pad(pads.data)
1409 end
1410 else
1411 local numlines = 0
1412 for nl in output:gmatch('\n') do
1413 numlines = numlines + 1
1414 end
1415 stdscr:mvaddstr(SCREEN_H - numlines, 0, output)
1416 end
1417 end
1418 elseif ('o'):byte() == _exp_0 then
1419 local file = stack_locations[pads.stack.selected]
1420 local filename, line_no = file:match("([^:]*):(.*)")
1421 line_no = tostring(pads.src.selected)
1422 C.endwin()
1423 os.execute((os.getenv("EDITOR") or "nano") .. " +" .. line_no .. " " .. filename)
1424 stdscr = C.initscr()
1425 C.cbreak()
1426 C.echo(false)
1427 C.nl(false)
1428 C.curs_set(0)
1429 C.start_color()
1430 C.use_default_colors()
1431 stdscr:clear()
1432 stdscr:refresh()
1433 for _, pad in pairs(pads) do
1434 pad:refresh(true)
1435 end
1436 elseif C.KEY_RESIZE == _exp_0 then
1437 SCREEN_H, SCREEN_W = stdscr:getmaxyx()
1438 stdscr:clear()
1439 stdscr:refresh()
1440 for _, pad in pairs(pads) do
1441 pad:refresh(true)
1442 end
1443 C.doupdate()
1444 elseif ('q'):byte() == _exp_0 or ("Q"):byte() == _exp_0 then
1445 pads = { }
1446 C.endwin()
1447 return
1448 elseif ('c'):byte() == _exp_0 then
1449 select_pad(pads.stack)
1450 elseif ('s'):byte() == _exp_0 then
1451 select_pad(pads.src)
1452 elseif ('v'):byte() == _exp_0 then
1453 select_pad(pads.vars)
1454 elseif ('d'):byte() == _exp_0 then
1455 select_pad(pads.data)
1456 elseif ('e'):byte() == _exp_0 then
1457 select_pad(pads.err)
1458 elseif C.KEY_DC == _exp_0 or C.KEY_DL == _exp_0 or C.KEY_BACKSPACE == _exp_0 then
1459 if selected_pad == pads.vars then
1460 local watches = watch_exprs[pads.stack.selected]
1461 local expr = pads.vars.columns[1][pads.vars.selected]
1462 for i, w in ipairs(watches) do
1463 if w.expr == expr then
1464 table.remove(watches, i)
1465 show_vars(pads.stack.selected)
1466 select_pad(pads.vars)
1467 break
1468 end
1469 end
1470 end
1471 else
1472 if selected_pad then
1473 selected_pad:keypress(c)
1474 end
1475 end
1476 end
1477 return C.endwin()
1478 end,
1479 guard = function(fn, ...)
1480 local handler
1481 handler = function(err_msg)
1482 print(debug.traceback(err_msg, 2))
1483 return xpcall(ldb.run_debugger, err_hand, err_msg)
1484 end
1485 return xpcall(fn, handler, ...)
1486 end,
1487 breakpoint = function()
1488 return xpcall(ldb.run_debugger, err_hand, "Breakpoint triggered!")
1489 end,
1490 hijack = function()
1491 error = function(err_msg)
1492 print(debug.traceback(err_msg, 2))
1493 xpcall(ldb.run_debugger, err_hand, err_msg)
1494 return os.exit(2)
1495 end
1496 assert = function(condition, err_msg)
1497 if not condition then
1498 err_msg = err_msg or 'Assertion failed!'
1499 print(debug.traceback(err_msg, 2))
1500 xpcall(ldb.run_debugger, err_hand, err_msg)
1501 os.exit(2)
1502 end
1503 return condition
1504 end
1505 end
1507 return ldb