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_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
.filename
= guess_source(line
.filename
)
39 line
.line_number
= tonumber(line
.line_number
)
40 if line
.data
:sub(1,1) == '{' then
41 local data
= json
.decode(line
.data
)
42 if log_render
[data
.name
] then
45 line
.section_stack
= table.shallowcopy(Section_stack
)
46 elseif line
.data
:match('\u{250c}') then
47 line
.section_stack
= table.shallowcopy(Section_stack
) -- as it is at the beginning
48 local section_name
= line
.data
:match('\u{250c}%s*(.*)')
49 table.insert(Section_stack
, {name
=section_name
})
50 line
.section_begin
= true
51 line
.section_name
= section_name
53 elseif line
.data
:match('\u{2518}') then
54 local section_name
= line
.data
:match('\u{2518}%s*(.*)')
55 if array
.find(Section_stack
, function(x
) return x
.name
== section_name
end) then
56 while table.remove(Section_stack
).name
~= section_name
do
59 line
.section_end
= true
60 line
.section_name
= section_name
63 line
.section_stack
= table.shallowcopy(Section_stack
)
66 line
.section_stack
= table.shallowcopy(Section_stack
)
69 line
.section_stack
= {}
74 function table.shallowcopy(x
)
78 function guess_source(filename
)
79 local possible_source
= filename
:gsub('%.lua$', '%.splua')
80 if file_exists(possible_source
) then
81 return possible_source
87 function log_browser
.draw(State
, hide_cursor
)
88 assert(#State
.lines
== #State
.line_cache
)
89 local mouse_line_index
= log_browser
.line_index(State
, App
.mouse_x(), App
.mouse_y())
91 for line_index
= State
.screen_top1
.line
,#State
.lines
do
93 local line
= State
.lines
[line_index
]
94 if y
+ State
.line_height
> App
.screen
.height
then break end
95 local height
= State
.line_height
96 if should_show(line
) then
97 local xleft
= render_stack_left_margin(State
, line_index
, line
, y
)
98 local xright
= render_stack_right_margin(State
, line_index
, line
, y
)
99 if line
.section_name
then
100 App
.color(Section_border_color
)
101 if line
.section_begin
then
102 local sectiony
= y
+Section_border_padding_vertical
103 love
.graphics
.line(xleft
,sectiony
, xleft
,y
+State
.line_height
)
104 love
.graphics
.line(xright
,sectiony
, xright
,y
+State
.line_height
)
105 love
.graphics
.line(xleft
,sectiony
, xleft
+50-2,sectiony
)
106 love
.graphics
.print(line
.section_name
, xleft
+50,y
)
107 love
.graphics
.line(xleft
+50+App
.width(line
.section_name
)+2,sectiony
, xright
,sectiony
)
108 else assert(line
.section_end
)
109 local sectiony
= y
+State
.line_height
-Section_border_padding_vertical
110 love
.graphics
.line(xleft
,y
, xleft
,sectiony
)
111 love
.graphics
.line(xright
,y
, xright
,sectiony
)
112 love
.graphics
.line(xleft
,sectiony
, xleft
+50-2,sectiony
)
113 love
.graphics
.print(line
.section_name
, xleft
+50,y
)
114 love
.graphics
.line(xleft
+50+App
.width(line
.section_name
)+2,sectiony
, xright
,sectiony
)
117 if type(line
.data
) == 'string' then
118 local old_left
, old_right
= State
.left
,State
.right
119 State
.left
,State
.right
= xleft
,xright
120 Text
.draw(State
, line_index
, y
, --[[startpos]] 1, hide_cursor
)
121 State
.left
,State
.right
= old_left
,old_right
123 height
= log_render
[line
.data
.name
](line
.data
, xleft
, y
, xright
-xleft
)
126 if App
.mouse_x() > Log_browser_state
.left
and line_index
== mouse_line_index
then
127 App
.color(Cursor_line_background_color
)
128 love
.graphics
.rectangle('fill', xleft
,y
, xright
-xleft
, height
)
135 function render_stack_left_margin(State
, line_index
, line
, y
)
136 if line
.section_stack
== nil then
138 for k
,v
in pairs(line
) do
142 App
.color(Section_border_color
)
143 for i
=1,#line
.section_stack
do
144 local x
= State
.left
+ (i
-1)*Section_border_padding_horizontal
145 love
.graphics
.line(x
,y
, x
,y
+log_browser
.height(State
, line_index
))
147 love
.graphics
.print(line
.section_stack
[i
].name
, x
+State
.font_height
+5, y
+5, --[[vertically]] math
.pi
/2)
149 if y
> App
.screen
.height
-log_browser
.height(State
, line_index
) then
150 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)
153 return log_browser
.left_margin(State
, line
)
156 function render_stack_right_margin(State
, line_index
, line
, y
)
157 App
.color(Section_border_color
)
158 for i
=1,#line
.section_stack
do
159 local x
= State
.right
- (i
-1)*Section_border_padding_horizontal
160 love
.graphics
.line(x
,y
, x
,y
+log_browser
.height(State
, line_index
))
162 love
.graphics
.print(line
.section_stack
[i
].name
, x
, y
+5, --[[vertically]] math
.pi
/2)
164 if y
> App
.screen
.height
-log_browser
.height(State
, line_index
) then
165 love
.graphics
.print(line
.section_stack
[i
].name
, x
, App
.screen
.height
-App
.width(line
.section_stack
[i
].name
)-5, --[[vertically]] math
.pi
/2)
168 return log_browser
.right_margin(State
, line
)
171 function should_show(line
)
172 -- Show a line if every single section it's in is expanded.
173 for i
=1,#line
.section_stack
do
174 local section
= line
.section_stack
[i
]
175 if not section
.expanded
then
182 function log_browser
.left_margin(State
, line
)
183 return State
.left
+ #line
.section_stack
*Section_border_padding_horizontal
186 function log_browser
.right_margin(State
, line
)
187 return State
.right
- #line
.section_stack
*Section_border_padding_horizontal
190 function log_browser
.update(State
, dt
)
193 function log_browser
.quit(State
)
196 function log_browser
.mouse_press(State
, x
,y
, mouse_button
)
197 local line_index
= log_browser
.line_index(State
, x
,y
)
198 if line_index
== nil then
199 -- below lower margin
202 -- leave some space to click without focusing
203 local line
= State
.lines
[line_index
]
204 local xleft
= log_browser
.left_margin(State
, line
)
205 local xright
= log_browser
.right_margin(State
, line
)
206 if x
< xleft
or x
> xright
then
209 -- if it's a section begin/end and the section is collapsed, expand it
210 -- TODO: how to collapse?
211 if line
.section_begin
or line
.section_end
then
212 -- HACK: get section reference from next/previous line
214 if line
.section_begin
then
215 if line_index
< #State
.lines
then
216 local next_section_stack
= State
.lines
[line_index
+1].section_stack
217 if next_section_stack
then
218 new_section
= next_section_stack
[#next_section_stack
]
221 elseif line
.section_end
then
222 if line_index
> 1 then
223 local previous_section_stack
= State
.lines
[line_index
-1].section_stack
224 if previous_section_stack
then
225 new_section
= previous_section_stack
[#previous_section_stack
]
229 if new_section
and new_section
.expanded
== nil then
230 new_section
.expanded
= true
234 -- open appropriate file in source side
235 if line
.filename
~= Editor_state
.filename
then
236 source
.switch_to_file(line
.filename
)
239 Editor_state
.cursor1
= {line
=line
.line_number
, pos
=1}
240 -- make sure it's visible
241 -- TODO: handle extremely long lines
242 Editor_state
.screen_top1
.line
= math
.max(0, Editor_state
.cursor1
.line
-5)
247 function log_browser
.line_index(State
, mx
,my
)
248 -- duplicate some logic from log_browser.draw
250 for line_index
= State
.screen_top1
.line
,#State
.lines
do
251 local line
= State
.lines
[line_index
]
252 if should_show(line
) then
253 y
= y
+ log_browser
.height(State
, line_index
)
257 if y
> App
.screen
.height
then break end
262 function log_browser
.mouse_release(State
, x
,y
, mouse_button
)
265 function log_browser
.mouse_wheel_move(State
, dx
,dy
)
267 for i
=1,math
.floor(dy
) do
268 log_browser
.up(State
)
271 for i
=1,math
.floor(-dy
) do
272 log_browser
.down(State
)
277 function log_browser
.text_input(State
, t
)
280 function log_browser
.keychord_press(State
, chord
, key
)
282 if chord
== 'up' then
283 log_browser
.up(State
)
284 elseif chord
== 'down' then
285 log_browser
.down(State
)
286 elseif chord
== 'pageup' then
288 while State
.screen_top1
.line
> 1 and y
< App
.screen
.height
- 100 do
289 State
.screen_top1
.line
= State
.screen_top1
.line
- 1
290 if should_show(State
.lines
[State
.screen_top1
.line
]) then
291 y
= y
+ log_browser
.height(State
, State
.screen_top1
.line
)
294 elseif chord
== 'pagedown' then
296 while State
.screen_top1
.line
< #State
.lines
and y
< App
.screen
.height
- 100 do
297 if should_show(State
.lines
[State
.screen_top1
.line
]) then
298 y
= y
+ log_browser
.height(State
, State
.screen_top1
.line
)
300 State
.screen_top1
.line
= State
.screen_top1
.line
+ 1
305 function log_browser
.up(State
)
306 while State
.screen_top1
.line
> 1 do
307 State
.screen_top1
.line
= State
.screen_top1
.line
-1
308 if should_show(State
.lines
[State
.screen_top1
.line
]) then
314 function log_browser
.down(State
)
315 while State
.screen_top1
.line
< #State
.lines
do
316 State
.screen_top1
.line
= State
.screen_top1
.line
+1
317 if should_show(State
.lines
[State
.screen_top1
.line
]) then
323 function log_browser
.height(State
, line_index
)
324 local line
= State
.lines
[line_index
]
325 if line
.data
== nil then
327 return State
.line_height
328 elseif type(line
.data
) == 'string' then
329 return State
.line_height
331 if line
.height
== nil then
332 --? print('nil line height! rendering off screen to calculate')
333 line
.height
= log_render
[line
.data
.name
](line
.data
, State
.left
, App
.screen
.height
, State
.right
-State
.left
)
339 function log_browser
.key_release(State
, key
, scancode
)