text.love/log_browser.lua
Kartik K. Agaram 007b965b11 audit all asserts
Each one should provide a message that will show up within LÖVE. Stop
relying on nearby prints to the terminal.

I also found some unnecessary ones.

There is some potential here for performance regressions: the format()
calls will trigger whether or not the assertion fails, and cause
allocations. So far Lua's GC seems good enough to manage the load even
with Moby Dick, even in some situations that caused issues in the past
like undo.
2023-11-18 11:32:01 -08:00

331 lines
12 KiB
Lua

-- environment for immutable logs
-- optionally reads extensions for rendering some types from the source codebase that generated them
--
-- We won't care too much about long, wrapped lines. If they lines get too
-- long to manage, you need a better, graphical rendering for them. Load
-- functions to render them into the log_render namespace.
function source.initialize_log_browser_side()
Log_browser_state = edit.initialize_state(Margin_top, Editor_state.right + Margin_right + Margin_left, (Editor_state.right+Margin_right)*2, Editor_state.font_height, Editor_state.line_height)
Log_browser_state.filename = 'log'
load_from_disk(Log_browser_state) -- TODO: pay no attention to Fold
log_browser.parse(Log_browser_state)
Text.redraw_all(Log_browser_state)
Log_browser_state.screen_top1 = {line=1, pos=1}
Log_browser_state.cursor1 = {line=1, pos=1}
end
Section_stack = {}
Section_border_color = {r=0.7, g=0.7, b=0.7}
Cursor_line_background_color = {r=0.7, g=0.7, b=0, a=0.1}
Section_border_padding_horizontal = 30 -- TODO: adjust this based on font height (because we draw text vertically along the borders
Section_border_padding_vertical = 15 -- TODO: adjust this based on font height
log_browser = {}
function log_browser.parse(State)
for _,line in ipairs(State.lines) do
if line.data ~= '' then
local rest
line.filename, line.line_number, rest = line.data:match('%[string "([^:]*)"%]:([^:]*):%s*(.*)')
if line.filename == nil then
line.filename, line.line_number, rest = line.data:match('([^:]*):([^:]*):%s*(.*)')
end
if rest then
line.data = rest
end
line.line_number = tonumber(line.line_number)
if line.data:sub(1,1) == '{' then
local data = json.decode(line.data)
if log_render[data.name] then
line.data = data
end
line.section_stack = table.shallowcopy(Section_stack)
elseif line.data:match('%[ u250c') then
line.section_stack = table.shallowcopy(Section_stack) -- as it is at the beginning
local section_name = line.data:match('u250c%s*(.*)')
table.insert(Section_stack, {name=section_name})
line.section_begin = true
line.section_name = section_name
line.data = nil
elseif line.data:match('%] u2518') then
local section_name = line.data:match('] u2518%s*(.*)')
if array.find(Section_stack, function(x) return x.name == section_name end) then
while table.remove(Section_stack).name ~= section_name do
--
end
line.section_end = true
line.section_name = section_name
line.data = nil
end
line.section_stack = table.shallowcopy(Section_stack)
else
-- string
line.section_stack = table.shallowcopy(Section_stack)
end
else
line.section_stack = {}
end
end
end
function table.shallowcopy(x)
return {unpack(x)}
end
function log_browser.draw(State, hide_cursor)
assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines))
local mouse_line_index = log_browser.line_index(State, App.mouse_x(), App.mouse_y())
local y = State.top
for line_index = State.screen_top1.line,#State.lines do
App.color(Text_color)
local line = State.lines[line_index]
if y + State.line_height > App.screen.height then break end
local height = State.line_height
if should_show(line) then
local xleft = render_stack_left_margin(State, line_index, line, y)
local xright = render_stack_right_margin(State, line_index, line, y)
if line.section_name then
App.color(Section_border_color)
if line.section_begin then
local sectiony = y+Section_border_padding_vertical
love.graphics.line(xleft,sectiony, xleft,y+State.line_height)
love.graphics.line(xright,sectiony, xright,y+State.line_height)
love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
love.graphics.print(line.section_name, xleft+50,y)
love.graphics.line(xleft+50+App.width(line.section_name)+2,sectiony, xright,sectiony)
else assert(line.section_end, "log line has a section name, but it's neither the start nor end of a section")
local sectiony = y+State.line_height-Section_border_padding_vertical
love.graphics.line(xleft,y, xleft,sectiony)
love.graphics.line(xright,y, xright,sectiony)
love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
love.graphics.print(line.section_name, xleft+50,y)
love.graphics.line(xleft+50+App.width(line.section_name)+2,sectiony, xright,sectiony)
end
else
if type(line.data) == 'string' then
local old_left, old_right = State.left,State.right
State.left,State.right = xleft,xright
Text.draw(State, line_index, y, --[[startpos]] 1, hide_cursor)
State.left,State.right = old_left,old_right
else
height = log_render[line.data.name](line.data, xleft, y, xright-xleft)
end
end
if App.mouse_x() > Log_browser_state.left and line_index == mouse_line_index then
App.color(Cursor_line_background_color)
love.graphics.rectangle('fill', xleft,y, xright-xleft, height)
end
y = y + height
end
end
end
function render_stack_left_margin(State, line_index, line, y)
if line.section_stack == nil then
-- assertion message
for k,v in pairs(line) do
print(k)
end
end
App.color(Section_border_color)
for i=1,#line.section_stack do
local x = State.left + (i-1)*Section_border_padding_horizontal
love.graphics.line(x,y, x,y+log_browser.height(State, line_index))
if y < 30 then
love.graphics.print(line.section_stack[i].name, x+State.font_height+5, y+5, --[[vertically]] math.pi/2)
end
if y > App.screen.height-log_browser.height(State, line_index) then
love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-App.width(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
end
end
return log_browser.left_margin(State, line)
end
function render_stack_right_margin(State, line_index, line, y)
App.color(Section_border_color)
for i=1,#line.section_stack do
local x = State.right - (i-1)*Section_border_padding_horizontal
love.graphics.line(x,y, x,y+log_browser.height(State, line_index))
if y < 30 then
love.graphics.print(line.section_stack[i].name, x, y+5, --[[vertically]] math.pi/2)
end
if y > App.screen.height-log_browser.height(State, line_index) then
love.graphics.print(line.section_stack[i].name, x, App.screen.height-App.width(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
end
end
return log_browser.right_margin(State, line)
end
function should_show(line)
-- Show a line if every single section it's in is expanded.
for i=1,#line.section_stack do
local section = line.section_stack[i]
if not section.expanded then
return false
end
end
return true
end
function log_browser.left_margin(State, line)
return State.left + #line.section_stack*Section_border_padding_horizontal
end
function log_browser.right_margin(State, line)
return State.right - #line.section_stack*Section_border_padding_horizontal
end
function log_browser.update(State, dt)
end
function log_browser.quit(State)
end
function log_browser.mouse_press(State, x,y, mouse_button)
local line_index = log_browser.line_index(State, x,y)
if line_index == nil then
-- below lower margin
return
end
-- leave some space to click without focusing
local line = State.lines[line_index]
local xleft = log_browser.left_margin(State, line)
local xright = log_browser.right_margin(State, line)
if x < xleft or x > xright then
return
end
-- if it's a section begin/end and the section is collapsed, expand it
-- TODO: how to collapse?
if line.section_begin or line.section_end then
-- HACK: get section reference from next/previous line
local new_section
if line.section_begin then
if line_index < #State.lines then
local next_section_stack = State.lines[line_index+1].section_stack
if next_section_stack then
new_section = next_section_stack[#next_section_stack]
end
end
elseif line.section_end then
if line_index > 1 then
local previous_section_stack = State.lines[line_index-1].section_stack
if previous_section_stack then
new_section = previous_section_stack[#previous_section_stack]
end
end
end
if new_section and new_section.expanded == nil then
new_section.expanded = true
return
end
end
-- open appropriate file in source side
if line.filename ~= Editor_state.filename then
source.switch_to_file(line.filename)
end
-- set cursor
Editor_state.cursor1 = {line=line.line_number, pos=1}
-- make sure it's visible
-- TODO: handle extremely long lines
Editor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)
-- show cursor
Focus = 'edit'
end
function log_browser.line_index(State, mx,my)
-- duplicate some logic from log_browser.draw
local y = State.top
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
if should_show(line) then
y = y + log_browser.height(State, line_index)
if my < y then
return line_index
end
if y > App.screen.height then break end
end
end
end
function log_browser.mouse_release(State, x,y, mouse_button)
end
function log_browser.mouse_wheel_move(State, dx,dy)
if dy > 0 then
for i=1,math.floor(dy) do
log_browser.up(State)
end
elseif dy < 0 then
for i=1,math.floor(-dy) do
log_browser.down(State)
end
end
end
function log_browser.text_input(State, t)
end
function log_browser.keychord_press(State, chord, key)
-- move
if chord == 'up' then
log_browser.up(State)
elseif chord == 'down' then
log_browser.down(State)
elseif chord == 'pageup' then
local y = 0
while State.screen_top1.line > 1 and y < App.screen.height - 100 do
State.screen_top1.line = State.screen_top1.line - 1
if should_show(State.lines[State.screen_top1.line]) then
y = y + log_browser.height(State, State.screen_top1.line)
end
end
elseif chord == 'pagedown' then
local y = 0
while State.screen_top1.line < #State.lines and y < App.screen.height - 100 do
if should_show(State.lines[State.screen_top1.line]) then
y = y + log_browser.height(State, State.screen_top1.line)
end
State.screen_top1.line = State.screen_top1.line + 1
end
end
end
function log_browser.up(State)
while State.screen_top1.line > 1 do
State.screen_top1.line = State.screen_top1.line-1
if should_show(State.lines[State.screen_top1.line]) then
break
end
end
end
function log_browser.down(State)
while State.screen_top1.line < #State.lines do
State.screen_top1.line = State.screen_top1.line+1
if should_show(State.lines[State.screen_top1.line]) then
break
end
end
end
function log_browser.height(State, line_index)
local line = State.lines[line_index]
if line.data == nil then
-- section header
return State.line_height
elseif type(line.data) == 'string' then
return State.line_height
else
if line.height == nil then
--? print('nil line height! rendering off screen to calculate')
line.height = log_render[line.data.name](line.data, State.left, App.screen.height, State.right-State.left)
end
return line.height
end
end
function log_browser.key_release(State, key, scancode)
end