about summary refs log tree commit diff stats
path: root/log_browser.lua
diff options
context:
space:
mode:
Diffstat (limited to 'log_browser.lua')
-rw-r--r--log_browser.lua316
1 files changed, 316 insertions, 0 deletions
diff --git a/log_browser.lua b/log_browser.lua
new file mode 100644
index 0000000..f65117f
--- /dev/null
+++ b/log_browser.lua
@@ -0,0 +1,316 @@
+-- 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=nil}
+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
+      line.filename, line.line_number, line.data = line.data:match('%[string "([^:]*)"%]:([^:]*):%s*(.*)')
+      line.filename = guess_source(line.filename)
+      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('\u{250c}') then
+        line.section_stack = table.shallowcopy(Section_stack)  -- as it is at the beginning
+        local section_name = line.data:match('\u{250c}%s*(.*)')
+        table.insert(Section_stack, {name=section_name})
+        line.section_begin = true
+        line.section_name = section_name
+        line.data = nil
+      elseif line.data:match('\u{2518}') then
+        local section_name = line.data:match('\u{2518}%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 guess_source(filename)
+  local possible_source = filename:gsub('%.lua$', '%.splua')
+  if file_exists(possible_source) then
+    return possible_source
+  else
+    return filename
+  end
+end
+
+function log_browser.draw(State)
+  assert(#State.lines == #State.line_cache)
+  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)
+        local section_text = to_text(line.section_name)
+        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.draw(section_text, xleft+50,y)
+          love.graphics.line(xleft+50+App.width(section_text)+2,sectiony, xright,sectiony)
+        else assert(line.section_end)
+          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.draw(section_text, xleft+50,y)
+          love.graphics.line(xleft+50+App.width(section_text)+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
+          y = Text.draw(State, line_index, y, --[[startpos]] 1)
+          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(to_text(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(to_text(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_pressed(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, posB=nil}
+  -- 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'
+  -- expand B side
+  Editor_state.expanded = true
+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_released(State, x,y, mouse_button)
+end
+
+function log_browser.textinput(State, t)
+end
+
+function log_browser.keychord_pressed(State, chord, key)
+  -- move
+  if chord == 'up' then
+    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
+  elseif chord == 'down' then
+    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
+  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.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.keyreleased(State, key, scancode)
+end