bugfix: don't clear selection on M-arrow
[view.love.git] / log_browser.lua
blob6e7e6db66d4962e352f8afadf1a47a2596384714
1 -- environment for immutable logs
2 -- optionally reads extensions for rendering some types from the source codebase that generated them
3 --
4 -- We won't care too much about long, wrapped lines. If they lines get too
5 -- long to manage, you need a better, graphical rendering for them. Load
6 -- functions to render them into the log_render namespace.
8 function source.initialize_log_browser_side()
9 Log_browser_state = edit.initialize_state(Margin_top, Editor_state.right + Margin_right + Margin_left, (Editor_state.right+Margin_right)*2, Editor_state.font, Editor_state.font_height, Editor_state.line_height)
10 Log_browser_state.filename = 'log'
11 load_from_disk(Log_browser_state) -- TODO: pay no attention to Fold
12 log_browser.parse(Log_browser_state)
13 Text.redraw_all(Log_browser_state)
14 Log_browser_state.screen_top1 = {line=1, pos=1}
15 Log_browser_state.cursor1 = {line=1, pos=1}
16 end
18 Section_stack = {}
19 Section_border_color = {r=0.7, g=0.7, b=0.7}
20 Cursor_line_background_color = {r=0.7, g=0.7, b=0, a=0.1}
22 Section_border_padding_horizontal = 30 -- TODO: adjust this based on font height (because we draw text vertically along the borders
23 Section_border_padding_vertical = 15 -- TODO: adjust this based on font height
25 log_browser = {}
27 function log_browser.parse(State)
28 for _,line in ipairs(State.lines) do
29 if line.data ~= '' then
30 local rest
31 line.filename, line.line_number, rest = line.data:match('%[string "([^:]*)"%]:([^:]*):%s*(.*)')
32 if line.filename == nil then
33 line.filename, line.line_number, rest = line.data:match('([^:]*):([^:]*):%s*(.*)')
34 end
35 if rest then
36 line.data = rest
37 end
38 line.line_number = tonumber(line.line_number)
39 if line.data:sub(1,1) == '{' then
40 local data = json.decode(line.data)
41 if log_render[data.name] then
42 line.data = data
43 end
44 line.section_stack = table.shallowcopy(Section_stack)
45 elseif line.data:match('%[ u250c') then
46 line.section_stack = table.shallowcopy(Section_stack) -- as it is at the beginning
47 local section_name = line.data:match('u250c%s*(.*)')
48 table.insert(Section_stack, {name=section_name})
49 line.section_begin = true
50 line.section_name = section_name
51 line.data = nil
52 elseif line.data:match('%] u2518') then
53 local section_name = line.data:match('] u2518%s*(.*)')
54 if array.find(Section_stack, function(x) return x.name == section_name end) then
55 while table.remove(Section_stack).name ~= section_name do
57 end
58 line.section_end = true
59 line.section_name = section_name
60 line.data = nil
61 end
62 line.section_stack = table.shallowcopy(Section_stack)
63 else
64 -- string
65 line.section_stack = table.shallowcopy(Section_stack)
66 end
67 else
68 line.section_stack = {}
69 end
70 end
71 end
73 function table.shallowcopy(x)
74 return {unpack(x)}
75 end
77 function log_browser.draw(State, hide_cursor)
78 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))
79 local mouse_line_index = log_browser.line_index(State, App.mouse_x(), App.mouse_y())
80 local y = State.top
81 for line_index = State.screen_top1.line,#State.lines do
82 App.color(Text_color)
83 local line = State.lines[line_index]
84 if y + State.line_height > App.screen.height then break end
85 local height = State.line_height
86 if should_show(line) then
87 local xleft = render_stack_left_margin(State, line_index, line, y)
88 local xright = render_stack_right_margin(State, line_index, line, y)
89 if line.section_name then
90 App.color(Section_border_color)
91 if line.section_begin then
92 local sectiony = y+Section_border_padding_vertical
93 love.graphics.line(xleft,sectiony, xleft,y+State.line_height)
94 love.graphics.line(xright,sectiony, xright,y+State.line_height)
95 love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
96 love.graphics.print(line.section_name, xleft+50,y)
97 love.graphics.line(xleft+50+State.font:getWidth(line.section_name)+2,sectiony, xright,sectiony)
98 else assert(line.section_end, "log line has a section name, but it's neither the start nor end of a section")
99 local sectiony = y+State.line_height-Section_border_padding_vertical
100 love.graphics.line(xleft,y, xleft,sectiony)
101 love.graphics.line(xright,y, xright,sectiony)
102 love.graphics.line(xleft,sectiony, xleft+50-2,sectiony)
103 love.graphics.print(line.section_name, xleft+50,y)
104 love.graphics.line(xleft+50+State.font:getWidth(line.section_name)+2,sectiony, xright,sectiony)
106 else
107 if type(line.data) == 'string' then
108 local old_left, old_right = State.left,State.right
109 State.left,State.right = xleft,xright
110 Text.draw(State, line_index, y, --[[startpos]] 1, hide_cursor)
111 State.left,State.right = old_left,old_right
112 else
113 height = log_render[line.data.name](line.data, xleft, y, xright-xleft)
116 if App.mouse_x() > Log_browser_state.left and line_index == mouse_line_index then
117 App.color(Cursor_line_background_color)
118 love.graphics.rectangle('fill', xleft,y, xright-xleft, height)
120 y = y + height
125 function render_stack_left_margin(State, line_index, line, y)
126 if line.section_stack == nil then
127 -- assertion message
128 for k,v in pairs(line) do
129 print(k)
132 App.color(Section_border_color)
133 for i=1,#line.section_stack do
134 local x = State.left + (i-1)*Section_border_padding_horizontal
135 love.graphics.line(x,y, x,y+log_browser.height(State, line_index))
136 if y < 30 then
137 love.graphics.print(line.section_stack[i].name, x+State.font_height+5, y+5, --[[vertically]] math.pi/2)
139 if y > App.screen.height-log_browser.height(State, line_index) then
140 love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
143 return log_browser.left_margin(State, line)
146 function render_stack_right_margin(State, line_index, line, y)
147 App.color(Section_border_color)
148 for i=1,#line.section_stack do
149 local x = State.right - (i-1)*Section_border_padding_horizontal
150 love.graphics.line(x,y, x,y+log_browser.height(State, line_index))
151 if y < 30 then
152 love.graphics.print(line.section_stack[i].name, x, y+5, --[[vertically]] math.pi/2)
154 if y > App.screen.height-log_browser.height(State, line_index) then
155 love.graphics.print(line.section_stack[i].name, x, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2)
158 return log_browser.right_margin(State, line)
161 function should_show(line)
162 -- Show a line if every single section it's in is expanded.
163 for i=1,#line.section_stack do
164 local section = line.section_stack[i]
165 if not section.expanded then
166 return false
169 return true
172 function log_browser.left_margin(State, line)
173 return State.left + #line.section_stack*Section_border_padding_horizontal
176 function log_browser.right_margin(State, line)
177 return State.right - #line.section_stack*Section_border_padding_horizontal
180 function log_browser.update(State, dt)
183 function log_browser.quit(State)
186 function log_browser.mouse_press(State, x,y, mouse_button)
187 local line_index = log_browser.line_index(State, x,y)
188 if line_index == nil then
189 -- below lower margin
190 return
192 -- leave some space to click without focusing
193 local line = State.lines[line_index]
194 local xleft = log_browser.left_margin(State, line)
195 local xright = log_browser.right_margin(State, line)
196 if x < xleft or x > xright then
197 return
199 -- if it's a section begin/end and the section is collapsed, expand it
200 -- TODO: how to collapse?
201 if line.section_begin or line.section_end then
202 -- HACK: get section reference from next/previous line
203 local new_section
204 if line.section_begin then
205 if line_index < #State.lines then
206 local next_section_stack = State.lines[line_index+1].section_stack
207 if next_section_stack then
208 new_section = next_section_stack[#next_section_stack]
211 elseif line.section_end then
212 if line_index > 1 then
213 local previous_section_stack = State.lines[line_index-1].section_stack
214 if previous_section_stack then
215 new_section = previous_section_stack[#previous_section_stack]
219 if new_section and new_section.expanded == nil then
220 new_section.expanded = true
221 return
224 -- open appropriate file in source side
225 if line.filename ~= Editor_state.filename then
226 source.switch_to_file(line.filename)
228 -- set cursor
229 Editor_state.cursor1 = {line=line.line_number, pos=1}
230 -- make sure it's visible
231 -- TODO: handle extremely long lines
232 Editor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)
233 -- show cursor
234 Focus = 'edit'
237 function log_browser.line_index(State, mx,my)
238 -- duplicate some logic from log_browser.draw
239 local y = State.top
240 for line_index = State.screen_top1.line,#State.lines do
241 local line = State.lines[line_index]
242 if should_show(line) then
243 y = y + log_browser.height(State, line_index)
244 if my < y then
245 return line_index
247 if y > App.screen.height then break end
252 function log_browser.mouse_release(State, x,y, mouse_button)
255 function log_browser.mouse_wheel_move(State, dx,dy)
256 if dy > 0 then
257 for i=1,math.floor(dy) do
258 log_browser.up(State)
260 elseif dy < 0 then
261 for i=1,math.floor(-dy) do
262 log_browser.down(State)
267 function log_browser.text_input(State, t)
270 function log_browser.keychord_press(State, chord, key)
271 -- move
272 if chord == 'up' then
273 log_browser.up(State)
274 elseif chord == 'down' then
275 log_browser.down(State)
276 elseif chord == 'pageup' then
277 local y = 0
278 while State.screen_top1.line > 1 and y < App.screen.height - 100 do
279 State.screen_top1.line = State.screen_top1.line - 1
280 if should_show(State.lines[State.screen_top1.line]) then
281 y = y + log_browser.height(State, State.screen_top1.line)
284 elseif chord == 'pagedown' then
285 local y = 0
286 while State.screen_top1.line < #State.lines and y < App.screen.height - 100 do
287 if should_show(State.lines[State.screen_top1.line]) then
288 y = y + log_browser.height(State, State.screen_top1.line)
290 State.screen_top1.line = State.screen_top1.line + 1
295 function log_browser.up(State)
296 while State.screen_top1.line > 1 do
297 State.screen_top1.line = State.screen_top1.line-1
298 if should_show(State.lines[State.screen_top1.line]) then
299 break
304 function log_browser.down(State)
305 while State.screen_top1.line < #State.lines do
306 State.screen_top1.line = State.screen_top1.line+1
307 if should_show(State.lines[State.screen_top1.line]) then
308 break
313 function log_browser.height(State, line_index)
314 local line = State.lines[line_index]
315 if line.data == nil then
316 -- section header
317 return State.line_height
318 elseif type(line.data) == 'string' then
319 return State.line_height
320 else
321 if line.height == nil then
322 --? print('nil line height! rendering off screen to calculate')
323 line.height = log_render[line.data.name](line.data, State.left, App.screen.height, State.right-State.left)
325 return line.height
329 function log_browser.key_release(State, key, scancode)