1 -- environment for immutable logs
2 -- optionally reads extensions for rendering some types from the source codebase that generated them
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}
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
27 function log_browser
.parse(State
)
28 for _
,line
in ipairs(State
.lines
) do
29 if line
.data
~= '' then
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*(.*)')
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
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
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
58 line
.section_end
= true
59 line
.section_name
= section_name
62 line
.section_stack
= table.shallowcopy(Section_stack
)
65 line
.section_stack
= table.shallowcopy(Section_stack
)
68 line
.section_stack
= {}
73 function table.shallowcopy(x
)
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())
81 for line_index
= State
.screen_top1
.line
,#State
.lines
do
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
)
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
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
)
125 function render_stack_left_margin(State
, line_index
, line
, y
)
126 if line
.section_stack
== nil then
128 for k
,v
in pairs(line
) do
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
))
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
))
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
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
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
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
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
224 -- open appropriate file in source side
225 if line
.filename
~= Editor_state
.filename
then
226 source
.switch_to_file(line
.filename
)
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)
237 function log_browser
.line_index(State
, mx
,my
)
238 -- duplicate some logic from log_browser.draw
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
)
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
)
257 for i
=1,math
.floor(dy
) do
258 log_browser
.up(State
)
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
)
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
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
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
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
313 function log_browser
.height(State
, line_index
)
314 local line
= State
.lines
[line_index
]
315 if line
.data
== nil then
317 return State
.line_height
318 elseif type(line
.data
) == 'string' then
319 return State
.line_height
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
)
329 function log_browser
.key_release(State
, key
, scancode
)