From d4bb091d9d71098b70ce8d34095f29311be2c63e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 19 Mar 2018 20:32:09 -0700 Subject: [PATCH] Partially working overhaul. --- cursed.lua | 392 ++++++++++++++++++++++++++++++++++------------------ cursed.moon | 261 +++++++++++++++++++--------------- 2 files changed, 410 insertions(+), 243 deletions(-) diff --git a/cursed.lua b/cursed.lua index 85b069b..33f3e7d 100644 --- a/cursed.lua +++ b/cursed.lua @@ -41,74 +41,147 @@ wrap_text = function(text, width) end return lines end -local default_colors = { } local Pad do local _class_0 local _base_0 = { + configure_size = function(self, height, width) + self.height, self.width = height, width + self._height = #self.columns[1] + if self.height == AUTO then + self.height = self._height + 2 + end + self._width = 0 + for i = 1, #self.columns[1] do + local row_len = #self.columns - 1 + local _list_0 = self.columns + for _index_0 = 1, #_list_0 do + local col = _list_0[_index_0] + row_len = row_len + #col[i] + end + self._width = math.max(self._width, row_len) + end + if self.width == AUTO then + self.width = self._width + 2 + end + end, + setup_chstr = function(self, i) + local chstr = self.chstrs[i] + local x = 0 + for c = 1, #self.columns do + local attr = self.colors[c](self, i) + local chunk = self.columns[c][i] + chunk = chunk .. (" "):rep(self.column_widths[c] - #chunk) + chstr:set_str(x, chunk, attr) + x = x + #chunk + if c < #self.columns then + chstr:set_ch(x, C.ACS_VLINE, self.active and self.active_frame or self.inactive_frame) + x = x + 1 + end + end + self._pad:mvaddchstr(i - 1, 0, chstr) + self.dirty = true + end, set_active = function(self, active) if active == self.active then return end self.active = active - return self._frame:attrset(active and self.colors.active_frame or self.colors.inactive_frame) + self._frame:attrset(active and self.active_frame or self.inactive_frame) + self.dirty = true end, select = function(self, i) - if #self.lines == 0 then + if #self.columns[1] == 0 then i = nil end if i == self.selected then return self.selected end + local old_y, old_x = self.scroll_y, self.scroll_x if i ~= nil then - i = math.max(1, math.min(#self.lines, i)) + i = math.max(1, math.min(#self.columns[1], i)) + end + local old_selected + old_selected, self.selected = self.selected, i + if old_selected then + self:setup_chstr(old_selected) end if self.selected then - local j = self.selected - local attr = self.colors.line_colors[j] - 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, 0, self.chstrs[j]) - end - if i then - local attr = self.active and self.colors.active or self.colors.highlight - self.chstrs[i]:set_str(0, self.lines[i], attr) - self.chstrs[i]:set_str(#self.lines[i], ' ', attr, self.chstrs[i]:len() - #self.lines[i]) - self._pad:mvaddchstr(i - 1, 0, self.chstrs[i]) + self:setup_chstr(self.selected) local scrolloff = 3 - if i > self.scroll_y + (self.height - 2) - scrolloff then - self.scroll_y = i - (self.height - 2) + scrolloff - elseif i < self.scroll_y + scrolloff then - self.scroll_y = i - scrolloff + if self.selected > self.scroll_y + (self.height - 2) - scrolloff then + self.scroll_y = self.selected - (self.height - 2) + scrolloff + elseif self.selected < self.scroll_y + scrolloff then + self.scroll_y = self.selected - scrolloff end - self.scroll_y = math.max(1, math.min(#self.lines, self.scroll_y)) + self.scroll_y = math.max(1, math.min(#self.columns[1], self.scroll_y)) + end + if self.scroll_y == old_y then + local w = math.min(self.width - 2, self._width) + if old_selected and self.scroll_y <= old_selected and old_selected <= self.scroll_y + self.height - 2 then + 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) + end + if self.selected and self.scroll_y <= self.selected and self.selected <= self.scroll_y + self.height - 2 then + 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) + end + else + self.dirty = true end - self.selected = i - self:refresh() if self.on_select then self:on_select(self.selected) end return self.selected end, scroll = function(self, dy, dx) + local old_y, old_x = self.scroll_y, self.scroll_x if self.selected ~= nil then self:select(self.selected + (dy or 0)) else self.scroll_y = math.max(1, math.min(self._height - self.height, self.scroll_y + (dy or 0))) end self.scroll_x = math.max(1, math.min(self._width - self.width, self.scroll_x + (dx or 0))) - return self:refresh() + if self.scroll_y ~= old_y or self.scroll_x ~= old_x then + self.dirty = true + end end, - refresh = function(self) - 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) + refresh = function(self, force) + if force == nil then + force = false + end + if not force and not self.dirty then + return + end + self._frame:mvaddch(0, 0, C.ACS_ULCORNER) + for y = 1, self.height - 2 do + self._frame:mvaddch(y, 0, C.ACS_VLINE) + end + self._frame:mvaddch(self.height - 1, 0, C.ACS_LLCORNER) + for x = 1, self.width - 2 do + self._frame:mvaddch(self.height - 1, x, C.ACS_HLINE) + end + self._frame:mvaddch(self.height - 1, self.width - 1, C.ACS_LRCORNER) + for y = 1, self.height - 2 do + self._frame:mvaddch(y, self.width - 1, C.ACS_VLINE) + end + self._frame:mvaddch(0, self.width - 1, C.ACS_URCORNER) + for x = 1, self.width - 2 do + self._frame:mvaddch(0, x, C.ACS_HLINE) + end + local _ = [[ @_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) + ]] if self.label then self._frame:mvaddstr(0, math.floor((self.width - #self.label - 2) / 2), " " .. tostring(self.label) .. " ") end self._frame:refresh() local h, w = math.min(self.height - 2, self._height), math.min(self.width - 2, self._width) - return self._pad:pnoutrefresh(self.scroll_y - 1, self.scroll_x - 1, self.y + 1, self.x + 1, self.y + h, self.x + w) + self._pad:pnoutrefresh(self.scroll_y - 1, self.scroll_x - 1, self.y + 1, self.x + 1, self.y + h, self.x + w) + self.dirty = false end, erase = function(self) + self.dirty = true self._frame:erase() return self._frame:refresh() end, @@ -119,49 +192,42 @@ do } _base_0.__index = _base_0 _class_0 = setmetatable({ - __init = function(self, y, x, height, width, lines, label, colors) - if colors == nil then - colors = default_colors - end - self.y, self.x, self.height, self.width, self.lines, self.label, self.colors = y, x, height, width, lines, label, colors - if self.colors and self.colors ~= default_colors then - setmetatable(self.colors, { - __index = default_colors - }) - end + __init = function(self, label, y, x, height, width, ...) + self.label, self.y, self.x = label, y, x self.scroll_y, self.scroll_x = 1, 1 self.selected = nil - self._height = #self.lines - if self.height == AUTO then - self.height = self._height + 2 - end - self._width = 0 - local _list_0 = self.lines - for _index_0 = 1, #_list_0 do - local x = _list_0[_index_0] - self._width = math.max(self._width, #x + 2) - end - if self.width == AUTO then - self.width = self._width + 2 + self.columns = { } + self.column_widths = { } + self.active_frame = color("blue bold") + self.inactive_frame = color("blue dim") + self.colors = { } + for i = 1, select('#', ...) - 1, 2 do + local col = select(i, ...) + table.insert(self.columns, col) + local w = 0 + for _index_0 = 1, #col do + local chunk = col[_index_0] + w = math.max(w, #chunk) + end + table.insert(self.column_widths, w) + local color_fn = select(i + 1, ...) or (function(self, i) + return color() + end) + assert(type(color_fn) == 'function', "Invalid color function type: " .. tostring(type(color_fn))) + table.insert(self.colors, color_fn) end + self:configure_size(height, width) self._frame = C.newwin(self.height, self.width, self.y, self.x) + self._frame:immedok(true) self._pad = C.newpad(self._height, self._width) self._pad:scrollok(true) self:set_active(false) self.chstrs = { } - for i, line in ipairs(self.lines) do - local attr = self.colors.line_colors[i] - local chstr = C.new_chstr(self._width) - self.chstrs[i] = chstr - if #line >= chstr:len() then - line = line:sub(1, chstr:len()) - else - line = line .. (" "):rep(chstr:len() - #line) - end - chstr:set_str(0, line, attr) - self._pad:mvaddchstr(i - 1, 0, chstr) + for i = 1, #self.columns[1] do + self.chstrs[i] = C.new_chstr(self._width) + self:setup_chstr(i) end - return self:refresh() + self.dirty = true end, __base = _base_0, __name = "Pad" @@ -176,6 +242,64 @@ do _base_0.__class = _class_0 Pad = _class_0 end +local NumberedPad +do + local _class_0 + local _parent_0 = Pad + local _base_0 = { } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, label, y, x, height, width, ...) + self.label, self.y, self.x = label, y, x + local col1 = select(1, ...) + local fmt = "%" .. tostring(#tostring(#col1)) .. "d" + local line_nums + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, #col1 do + _accum_0[_len_0] = fmt:format(i) + _len_0 = _len_0 + 1 + end + line_nums = _accum_0 + end + local cols = { + line_nums, + (function(self, i) + return color("yellow") + end), + ... + } + return _class_0.__parent.__init(self, self.label, self.y, self.x, height, width, unpack(cols)) + end, + __base = _base_0, + __name = "NumberedPad", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + NumberedPad = _class_0 +end local ok, to_lua = pcall(function() return require('moonscript.base').to_lua end) @@ -238,13 +362,14 @@ run_debugger = function(err_msg) end local color_lang = re.compile([[ x <- {| {:attrs: {| {attr} (" " {attr})* |} :} - / ((({:fg: color :} (" on " {:bg: color :})?) / {:bg: "on " color :}) {:attrs: {| (" " {attr})* |} :}) + / ( + ({:bg: "on " {color} :} / ({:fg: color :} (" on " {:bg: color :})?)) + {:attrs: {| (" " {attr})* |} :}) |} attr <- "blink" / "bold" / "dim" / "invis" / "normal" / "protect" / "reverse" / "standout" / "underline" color <- "black" / "blue" / "cyan" / "green" / "magenta" / "red" / "white" / "yellow" / "default" ]]) C.COLOR_DEFAULT = -1 - local color color = function(s) if s == nil then s = "default" @@ -264,17 +389,6 @@ run_debugger = function(err_msg) end return c end - default_colors = { - active_frame = color("blue"), - inactive_frame = color("bold black"), - line_colors = setmetatable({ }, { - __index = function(self, i) - return (i % 2 == 0 and color("on black") or color()) - end - }), - highlight = color("black on white"), - active = color("black on yellow") - } do stdscr:wbkgd(color("yellow on red bold")) stdscr:clear() @@ -305,14 +419,9 @@ run_debugger = function(err_msg) for i, line in ipairs(err_msg_lines) do err_msg_lines[i] = (" "):rep(2) .. line end - pads.err = Pad(0, 0, AUTO, SCREEN_W, err_msg_lines, "Error Message", { - line_colors = setmetatable({ }, { - __index = function() - return color("red bold") - end - }), - inactive_frame = color("red dim") - }) + pads.err = Pad("Error Message", 0, 0, AUTO, SCREEN_W, err_msg_lines, function(self, i) + return color("red bold") + end) end local stack_locations = { } do @@ -320,19 +429,15 @@ run_debugger = function(err_msg) local max_filename, max_fn_name = 0, 0 local stack_min, stack_max = callstack_range() for i = stack_min, stack_max do - local _continue_0 = false - repeat - local info = debug.getinfo(i) - if not info then - break - end - table.insert(stack_names, info.name or "") - if not info.short_src then - _continue_0 = true - break - end + local info = debug.getinfo(i) + if not info then + break + end + local fn_name = info.name or "" + table.insert(stack_names, fn_name) + local line + if info.short_src then local line_table = line_tables[info.short_src] - local line if line_table then local char = line_table[info.currentline] local line_num = 1 @@ -344,56 +449,62 @@ run_debugger = function(err_msg) else line = info.short_src .. ":" .. info.currentline end - table.insert(stack_locations, line) - max_filename = math.max(max_filename, #line) - max_fn_name = math.max(max_fn_name, #stack_names[#stack_names]) - _continue_0 = true - until true - if not _continue_0 then - break + else + line = "???" end + table.insert(stack_locations, line) + max_filename = math.max(max_filename, #line) + max_fn_name = math.max(max_fn_name, #fn_name) end local callstack = { } local max_line = 0 for i = 1, #stack_names do - callstack[i] = ("%-" .. max_fn_name .. "s | %s"):format(stack_names[i], stack_locations[i]) - max_line = math.max(max_line, #callstack[i]) + local fn_name = stack_names[i] + callstack[i] = { + fn_name, + stack_locations[i] + } + max_line = math.max(max_line, #fn_name + #stack_locations[i] + 3) end - pads.stack = Pad(pads.err.height, SCREEN_W - (max_line + 2), math.max(#callstack + 2, 20), max_line + 2, callstack, "(C)allstack") + local stack_h = math.max(#callstack + 2, math.floor(2 / 3 * SCREEN_H)) + pads.stack = Pad("(C)allstack", pads.err.height, SCREEN_W - (max_line + 2), stack_h, max_line + 2, stack_names, (function(self, i) + return (i == self.selected) and color("black on green") or color("green") + end), stack_locations, (function(self, i) + return (i == self.selected) and color("black on cyan") or color("cyan") + end)) end local show_src show_src = function(filename, line_no) - local file = file_cache[filename] - local src_lines = { } - local err_line = nil - if file then - local i = 0 - for line in file:gmatch("[^\n]*") do - i = i + 1 - table.insert(src_lines, line) - if i == line_no then - err_line = #src_lines - end - end - while #src_lines < pads.stack.height do - table.insert(src_lines, "") - end - else - table.insert(src_lines, "") - end if pads.src then pads.src:erase() end - pads.src = Pad(pads.err.height, 0, pads.stack.height, pads.stack.x, src_lines, "(S)ource Code", { - line_colors = setmetatable({ - [err_line or -1] = color("yellow on red bold") - }, { - __index = function(self, i) - return (i % 2 == 0) and color("on black") or color() + local file = file_cache[filename] + if file then + local src_lines = { } + for line in (file .. '\n'):gmatch("([^\n]*)\n") do + table.insert(src_lines, line) + end + pads.src = NumberedPad("(S)ource Code", pads.err.height, 0, pads.stack.height, pads.stack.x, src_lines, function(self, i) + if i == self.selected then + return color("black on white") + elseif i == line_no then + return color("yellow on red bold") end - }) - }) - return pads.src:select(err_line) + return color("white bold") + end) + return pads.src:select(line_no) + else + local lines = { } + for i = 1, math.floor(pads.stack.height / 2) - 1 do + table.insert(lines, "") + end + local s = "" + s = (" "):rep(math.floor((pads.stack.x - 2 - #s) / 2)) .. s + table.insert(lines, s) + pads.src = Pad("(S)ource Code", pads.err.height, 0, pads.stack.height, pads.stack.x, lines, function() + return color("red") + end) + end end local show_vars show_vars = function(stack_index) @@ -421,14 +532,20 @@ run_debugger = function(err_msg) local var_y = pads.stack.y + pads.stack.height local var_x = 0 local height = SCREEN_H - (pads.err.height + pads.stack.height) - pads.vars = Pad(var_y, var_x, height, AUTO, var_names, "(V)ars") + pads.vars = Pad("(V)ars", var_y, var_x, height, AUTO, var_names, function(self, i) + return color() + end) pads.vars.on_select = function(self, var_index) local value_x = pads.vars.x + pads.vars.width local value_w = SCREEN_W - (value_x) if var_index then - pads.values = Pad(var_y, value_x, pads.vars.height, value_w, wrap_text(values[var_index], value_w - 2), "V(a)lue") + pads.values = Pad("V(a)lue", var_y, value_x, pads.vars.height, value_w, wrap_text(values[var_index], value_w - 2), function(self, i) + return color() + end) else - pads.values = Pad(var_y, value_x, pads.vars.height, value_w, values, "Values") + pads.values = Pad("V(a)lue", var_y, value_x, pads.vars.height, value_w, values, function(self, i) + return color() + end) end collectgarbage() return collectgarbage() @@ -436,8 +553,8 @@ run_debugger = function(err_msg) return pads.vars:select(1) end pads.stack.on_select = function(self, stack_index) - local filename, line_no = pads.stack.lines[stack_index]:match("[^|]*| ([^:]*):(%d*).*") - line_no = tonumber(line_no) + local filename = pads.stack.columns[2][stack_index]:match("([^:]*):.*") + local line_no = tonumber(line_no) show_src(filename, line_no) return show_vars(stack_index) end @@ -457,6 +574,11 @@ run_debugger = function(err_msg) end select_pad(pads.src) while true do + for _, p in pairs(pads) do + if p.dirty then + p:refresh() + end + end C.doupdate() local c = stdscr:getch() local _exp_0 = c diff --git a/cursed.moon b/cursed.moon index d5e7ecc..150ffea 100644 --- a/cursed.moon +++ b/cursed.moon @@ -34,99 +34,144 @@ wrap_text = (text, width)-> table.insert(lines, line) return lines -default_colors = { -} class Pad - new: (@y,@x,@height,@width,@lines,@label,@colors=default_colors)=> - if @colors and @colors != default_colors - setmetatable(@colors, __index:default_colors) + new: (@label,@y,@x,height,width,...)=> @scroll_y, @scroll_x = 1, 1 @selected = nil - @_height = #@lines + @columns = {} + @column_widths = {} + @active_frame = color("blue bold") + @inactive_frame = color("blue dim") + @colors = {} + for i=1,select('#',...)-1,2 + col = select(i, ...) + table.insert(@columns, col) + w = 0 + for chunk in *col do w = math.max(w, #chunk) + table.insert(@column_widths, w) + color_fn = select(i+1,...) or ((i)=>color()) + assert(type(color_fn) == 'function', "Invalid color function type: #{type color_fn}") + table.insert(@colors, color_fn) + + @configure_size height, width + @_frame = C.newwin(@height, @width, @y, @x) + @_frame\immedok(true) + @_pad = C.newpad(@_height, @_width) + @_pad\scrollok(true) + @set_active false + @chstrs = {} + for i=1,#@columns[1] + @chstrs[i] = C.new_chstr(@_width) + @setup_chstr(i) + @dirty = true + + configure_size: (@height, @width)=> + @_height = #@columns[1] if @height == AUTO @height = @_height + 2 @_width = 0 - for x in *@lines do @_width = math.max(@_width, #x+2) + for i=1,#@columns[1] + row_len = #@columns - 1 + for col in *@columns + row_len += #col[i] + @_width = math.max(@_width, row_len) if @width == AUTO @width = @_width + 2 - @_frame = C.newwin(@height, @width, @y, @x) - - @_pad = C.newpad(@_height, @_width) - @_pad\scrollok(true) - @set_active false - - @chstrs = {} - for i, line in ipairs(@lines) - attr = @colors.line_colors[i] - chstr = C.new_chstr(@_width) - @chstrs[i] = chstr - if #line >= chstr\len! - line = line\sub(1, chstr\len!) - else - line ..= (" ")\rep(chstr\len!-#line) - chstr\set_str(0, line, attr) - @_pad\mvaddchstr(i-1,0,chstr) - @refresh! + setup_chstr: (i)=> + chstr = @chstrs[i] + x = 0 + for c=1,#@columns + attr = @colors[c](@, i) + chunk = @columns[c][i] + chunk ..= (" ")\rep(@column_widths[c]-#chunk) + chstr\set_str(x, chunk, attr) + x += #chunk + if c < #@columns + chstr\set_ch(x, C.ACS_VLINE, @active and @active_frame or @inactive_frame) + x += 1 + @_pad\mvaddchstr(i-1,0,chstr) + @dirty = true set_active: (active)=> return if active == @active @active = active - @_frame\attrset(active and @colors.active_frame or @colors.inactive_frame) + @_frame\attrset(active and @active_frame or @inactive_frame) + @dirty = true select: (i)=> - if #@lines == 0 then i = nil + if #@columns[1] == 0 then i = nil if i == @selected then return @selected + old_y, old_x = @scroll_y, @scroll_x if i != nil - i = math.max(1, math.min(#@lines, i)) - if @selected - j = @selected - attr = @colors.line_colors[j] - @chstrs[j]\set_str(0, @lines[j], attr) - @chstrs[j]\set_str(#@lines[j], ' ', attr, @chstrs[j]\len!-#@lines[j]) - @_pad\mvaddchstr(j-1,0,@chstrs[j]) + i = math.max(1, math.min(#@columns[1], i)) + + old_selected,@selected = @selected,i - if i - attr = @active and @colors.active or @colors.highlight - @chstrs[i]\set_str(0, @lines[i], attr) - @chstrs[i]\set_str(#@lines[i], ' ', attr, @chstrs[i]\len!-#@lines[i]) - @_pad\mvaddchstr(i-1,0,@chstrs[i]) + if old_selected + @setup_chstr(old_selected) + + if @selected + @setup_chstr(@selected) scrolloff = 3 - if i > @scroll_y + (@height-2) - scrolloff - @scroll_y = i - (@height-2) + scrolloff - elseif i < @scroll_y + scrolloff - @scroll_y = i - scrolloff - @scroll_y = math.max(1, math.min(#@lines, @scroll_y)) + if @selected > @scroll_y + (@height-2) - scrolloff + @scroll_y = @selected - (@height-2) + scrolloff + elseif @selected < @scroll_y + scrolloff + @scroll_y = @selected - scrolloff + @scroll_y = math.max(1, math.min(#@columns[1], @scroll_y)) + + if @scroll_y == old_y + w = math.min(@width-2,@_width) + if old_selected and @scroll_y <= old_selected and old_selected <= @scroll_y + @height-2 + @_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) + if @selected and @scroll_y <= @selected and @selected <= @scroll_y + @height-2 + @_pad\pnoutrefresh(@selected-1,@scroll_x-1,@y+1+(@selected-@scroll_y),@x+1,@y+1+(@selected-@scroll_y)+1,@x+w) + else + @dirty = true - @selected = i - @refresh! if @on_select then @on_select(@selected) return @selected scroll: (dy,dx)=> + old_y, old_x = @scroll_y, @scroll_x if @selected != nil @select(@selected + (dy or 0)) else @scroll_y = math.max(1, math.min(@_height-@height, @scroll_y+(dy or 0))) @scroll_x = math.max(1, math.min(@_width-@width, @scroll_x+(dx or 0))) - @refresh! + if @scroll_y != old_y or @scroll_x != old_x + @dirty = true - refresh: => + refresh: (force=false)=> + return if not force and not @dirty + @_frame\mvaddch(0,0,C.ACS_ULCORNER) + for y=1,@height-2 do @_frame\mvaddch(y,0,C.ACS_VLINE) + @_frame\mvaddch(@height-1,0,C.ACS_LLCORNER) + for x=1,@width-2 do @_frame\mvaddch(@height-1,x,C.ACS_HLINE) + @_frame\mvaddch(@height-1,@width-1,C.ACS_LRCORNER) + for y=1,@height-2 do @_frame\mvaddch(y,@width-1,C.ACS_VLINE) + @_frame\mvaddch(0,@width-1,C.ACS_URCORNER) + for x=1,@width-2 do @_frame\mvaddch(0,x,C.ACS_HLINE) + [[ @_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) + ]] if @label @_frame\mvaddstr(0, math.floor((@width-#@label-2)/2), " #{@label} ") @_frame\refresh! + --@_frame\prefresh(0,0,@y,@x,@y+@height-1,@x+@width-1) h,w = math.min(@height-2,@_height),math.min(@width-2,@_width) @_pad\pnoutrefresh(@scroll_y-1,@scroll_x-1,@y+1,@x+1,@y+h,@x+w) + @dirty = false erase: => + @dirty = true @_frame\erase! @_frame\refresh! @@ -134,6 +179,14 @@ class Pad @_frame\close! @_pad\close! +class NumberedPad extends Pad + new: (@label,@y,@x,height,width,...)=> + col1 = select(1, ...) + fmt = "%#{#tostring(#col1)}d" + line_nums = [fmt\format(i) for i=1,#col1] + cols = {line_nums, ((i)=>color("yellow")), ...} + super @label, @y, @x, height, width, unpack(cols) + ok, to_lua = pcall -> require('moonscript.base').to_lua if not ok then to_lua = -> nil file_cache = setmetatable({}, {__index:(filename)=> @@ -177,12 +230,15 @@ run_debugger = (err_msg)-> color_lang = re.compile[[ x <- {| {:attrs: {| {attr} (" " {attr})* |} :} - / ((({:fg: color :} (" on " {:bg: color :})?) / {:bg: "on " color :}) {:attrs: {| (" " {attr})* |} :}) + / ( + ({:bg: "on " {color} :} / ({:fg: color :} (" on " {:bg: color :})?)) + {:attrs: {| (" " {attr})* |} :}) |} attr <- "blink" / "bold" / "dim" / "invis" / "normal" / "protect" / "reverse" / "standout" / "underline" color <- "black" / "blue" / "cyan" / "green" / "magenta" / "red" / "white" / "yellow" / "default" ]] C.COLOR_DEFAULT = -1 + export color color = (s="default")-> t = assert(color_lang\match(s), "Invalid color: #{s}") if t.fg then t.fg = C["COLOR_"..t.fg\upper!] @@ -192,15 +248,6 @@ run_debugger = (err_msg)-> c |= C["A_"..a\upper!] return c - export default_colors - default_colors = { - active_frame: color"blue", - inactive_frame: color"bold black", - line_colors: setmetatable({}, __index:(i)=> (i % 2 == 0 and color("on black") or color())) - highlight: color"black on white", - active: color"black on yellow", - } - do -- Fullscreen flash stdscr\wbkgd(color"yellow on red bold") stdscr\clear! @@ -227,10 +274,7 @@ run_debugger = (err_msg)-> err_msg_lines = wrap_text(err_msg, SCREEN_W - 4) for i,line in ipairs(err_msg_lines) err_msg_lines[i] = (" ")\rep(2)..line - pads.err = Pad(0,0,AUTO,SCREEN_W, err_msg_lines, "Error Message", { - line_colors: setmetatable({}, __index:-> color"red bold") - inactive_frame: color"red dim" - }) + pads.err = Pad("Error Message", 0,0,AUTO,SCREEN_W, err_msg_lines, (i)=> color("red bold")) stack_locations = {} do -- Stack pad @@ -240,59 +284,56 @@ run_debugger = (err_msg)-> for i=stack_min,stack_max info = debug.getinfo(i) if not info then break - table.insert(stack_names, info.name or "") - if not info.short_src - continue - line_table = line_tables[info.short_src] - line = if line_table - char = line_table[info.currentline] - line_num = 1 - file = file_cache[info.short_src] - for _ in file\sub(1,char)\gmatch("\n") do line_num += 1 - "#{info.short_src}:#{line_num}" + fn_name = info.name or "" + table.insert(stack_names, fn_name) + line = if info.short_src + line_table = line_tables[info.short_src] + if line_table + char = line_table[info.currentline] + line_num = 1 + file = file_cache[info.short_src] + for _ in file\sub(1,char)\gmatch("\n") do line_num += 1 + "#{info.short_src}:#{line_num}" + else + info.short_src..":"..info.currentline else - info.short_src..":"..info.currentline + "???" table.insert(stack_locations, line) max_filename = math.max(max_filename, #line) - max_fn_name = math.max(max_fn_name, #stack_names[#stack_names]) + max_fn_name = math.max(max_fn_name, #fn_name) callstack = {} max_line = 0 for i=1,#stack_names do - --callstack[i] = stack_locations[i]..(" ")\rep(max_filename-#stack_locations[i]).." | "..stack_names[i].." " - callstack[i] = ("%-"..max_fn_name.."s | %s")\format(stack_names[i], stack_locations[i]) - --callstack[i] = stack_locations[i]..(" ")\rep(max_filename-#stack_locations[i]).." | "..stack_names[i].." " - max_line = math.max(max_line, #callstack[i]) + fn_name = stack_names[i] + callstack[i] = {fn_name, stack_locations[i]} + max_line = math.max(max_line, #fn_name+#stack_locations[i]+3) - pads.stack = Pad(pads.err.height,SCREEN_W-(max_line+2),math.max(#callstack+2, 20),max_line+2, callstack, "(C)allstack") + stack_h = math.max(#callstack+2, math.floor(2/3*SCREEN_H)) + pads.stack = Pad "(C)allstack",pads.err.height,SCREEN_W-(max_line+2),stack_h,max_line+2, + stack_names, ((i)=> (i == @selected) and color("black on green") or color("green")), + stack_locations, ((i)=> (i == @selected) and color("black on cyan") or color("cyan")) show_src = (filename, line_no)-> - file = file_cache[filename] - src_lines = {} - err_line = nil - if file - i = 0 - for line in file\gmatch("[^\n]*") - i += 1 - --if i < line_no-(pads.stack.height-2)/2 - -- continue - table.insert src_lines, line - if i == line_no - err_line = #src_lines - --if #src_lines >= pads.stack.height-2 - -- break - while #src_lines < pads.stack.height - table.insert(src_lines, "") - else - table.insert(src_lines, "") - if pads.src pads.src\erase! - pads.src = Pad(pads.err.height,0, - pads.stack.height,pads.stack.x, src_lines, "(S)ource Code", { - line_colors:setmetatable({[err_line or -1]: color"yellow on red bold"}, - {__index:(i)=> (i % 2 == 0) and color"on black" or color!}) - }) - pads.src\select(err_line) + file = file_cache[filename] + if file + src_lines = {} + for line in (file..'\n')\gmatch("([^\n]*)\n") + table.insert src_lines, line + pads.src = NumberedPad "(S)ource Code", pads.err.height,0, + pads.stack.height,pads.stack.x, src_lines, (i)=> + if i == @selected then return color("black on white") + elseif i == line_no then return color("yellow on red bold") + return color("white bold") + pads.src\select(line_no) + else + lines = {} + for i=1,math.floor(pads.stack.height/2)-1 do table.insert(lines, "") + s = "" + s = (" ")\rep(math.floor((pads.stack.x-2-#s)/2))..s + table.insert(lines, s) + pads.src = Pad "(S)ource Code", pads.err.height,0,pads.stack.height,pads.stack.x,lines, ->color("red") show_vars = (stack_index)-> if pads.vars @@ -316,23 +357,24 @@ run_debugger = (err_msg)-> var_x = 0 --height = math.min(2+#var_names, SCREEN_H-pads.err.height-pads.stack.height) height = SCREEN_H-(pads.err.height+pads.stack.height) - pads.vars = Pad(var_y,var_x,height,AUTO,var_names,"(V)ars") + pads.vars = Pad "(V)ars", var_y,var_x,height,AUTO,var_names, (i)=> color() pads.vars.on_select = (var_index)=> value_x = pads.vars.x+pads.vars.width value_w = SCREEN_W-(value_x) -- Show single value: if var_index - pads.values = Pad(var_y,value_x,pads.vars.height,value_w,wrap_text(values[var_index], value_w-2), "V(a)lue") + pads.values = Pad "V(a)lue",var_y,value_x,pads.vars.height,value_w,wrap_text(values[var_index], value_w-2), (i)=>color() else - pads.values = Pad(var_y,value_x,pads.vars.height,value_w,values, "Values") + pads.values = Pad "V(a)lue",var_y,value_x,pads.vars.height,value_w,values, (i)=>color() collectgarbage() collectgarbage() pads.vars\select(1) pads.stack.on_select = (stack_index)=> - filename, line_no = pads.stack.lines[stack_index]\match("[^|]*| ([^:]*):(%d*).*") + filename = pads.stack.columns[2][stack_index]\match("([^:]*):.*") + --filename, line_no = pads.stack.lines[stack_index]\match("[^|]*| ([^:]*):(%d*).*") line_no = tonumber(line_no) show_src(filename, line_no) show_vars(stack_index) @@ -352,6 +394,9 @@ run_debugger = (err_msg)-> select_pad(pads.src) while true + for _,p in pairs(pads) + if p.dirty + p\refresh! C.doupdate! c = stdscr\getch! switch c