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
=nil}
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
)
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 local section_text
= to_text(line
.section_name
)
102 if line
.section_begin
then
103 local sectiony
= y
+Section_border_padding_vertical
104 love
.graphics
.line(xleft
,sectiony
, xleft
,y
+State
.line_height
)
105 love
.graphics
.line(xright
,sectiony
, xright
,y
+State
.line_height
)
106 love
.graphics
.line(xleft
,sectiony
, xleft
+50-2,sectiony
)
107 love
.graphics
.draw(section_text
, xleft
+50,y
)
108 love
.graphics
.line(xleft
+50+App
.width(section_text
)+2,sectiony
, xright
,sectiony
)
109 else assert(line
.section_end
)
110 local sectiony
= y
+State
.line_height
-Section_border_padding_vertical
111 love
.graphics
.line(xleft
,y
, xleft
,sectiony
)
112 love
.graphics
.line(xright
,y
, xright
,sectiony
)
113 love
.graphics
.line(xleft
,sectiony
, xleft
+50-2,sectiony
)
114 love
.graphics
.draw(section_text
, xleft
+50,y
)
115 love
.graphics
.line(xleft
+50+App
.width(section_text
)+2,sectiony
, xright
,sectiony
)
118 if type(line
.data
) == 'string' then
119 local old_left
, old_right
= State
.left
,State
.right
120 State
.left
,State
.right
= xleft
,xright
121 y
= Text
.draw(State
, line_index
, y
, --[[startpos]] 1)
122 State
.left
,State
.right
= old_left
,old_right
124 height
= log_render
[line
.data
.name
](line
.data
, xleft
, y
, xright
-xleft
)
127 if App
.mouse_x() > Log_browser_state
.left
and line_index
== mouse_line_index
then
128 App
.color(Cursor_line_background_color
)
129 love
.graphics
.rectangle('fill', xleft
,y
, xright
-xleft
, height
)
136 function render_stack_left_margin(State
, line_index
, line
, y
)
137 if line
.section_stack
== nil then
139 for k
,v
in pairs(line
) do
143 App
.color(Section_border_color
)
144 for i
=1,#line
.section_stack
do
145 local x
= State
.left
+ (i
-1)*Section_border_padding_horizontal
146 love
.graphics
.line(x
,y
, x
,y
+log_browser
.height(State
, line_index
))
148 love
.graphics
.print(line
.section_stack
[i
].name
, x
+State
.font_height
+5, y
+5, --[[vertically]] math
.pi
/2)
150 if y
> App
.screen
.height
-log_browser
.height(State
, line_index
) then
151 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)
154 return log_browser
.left_margin(State
, line
)
157 function render_stack_right_margin(State
, line_index
, line
, y
)
158 App
.color(Section_border_color
)
159 for i
=1,#line
.section_stack
do
160 local x
= State
.right
- (i
-1)*Section_border_padding_horizontal
161 love
.graphics
.line(x
,y
, x
,y
+log_browser
.height(State
, line_index
))
163 love
.graphics
.print(line
.section_stack
[i
].name
, x
, y
+5, --[[vertically]] math
.pi
/2)
165 if y
> App
.screen
.height
-log_browser
.height(State
, line_index
) then
166 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)
169 return log_browser
.right_margin(State
, line
)
172 function should_show(line
)
173 -- Show a line if every single section it's in is expanded.
174 for i
=1,#line
.section_stack
do
175 local section
= line
.section_stack
[i
]
176 if not section
.expanded
then
183 function log_browser
.left_margin(State
, line
)
184 return State
.left
+ #line
.section_stack
*Section_border_padding_horizontal
187 function log_browser
.right_margin(State
, line
)
188 return State
.right
- #line
.section_stack
*Section_border_padding_horizontal
191 function log_browser
.update(State
, dt
)
194 function log_browser
.quit(State
)
197 function log_browser
.mouse_pressed(State
, x
,y
, mouse_button
)
198 local line_index
= log_browser
.line_index(State
, x
,y
)
199 if line_index
== nil then
200 -- below lower margin
203 -- leave some space to click without focusing
204 local line
= State
.lines
[line_index
]
205 local xleft
= log_browser
.left_margin(State
, line
)
206 local xright
= log_browser
.right_margin(State
, line
)
207 if x
< xleft
or x
> xright
then
210 -- if it's a section begin/end and the section is collapsed, expand it
211 -- TODO: how to collapse?
212 if line
.section_begin
or line
.section_end
then
213 -- HACK: get section reference from next/previous line
215 if line
.section_begin
then
216 if line_index
< #State
.lines
then
217 local next_section_stack
= State
.lines
[line_index
+1].section_stack
218 if next_section_stack
then
219 new_section
= next_section_stack
[#next_section_stack
]
222 elseif line
.section_end
then
223 if line_index
> 1 then
224 local previous_section_stack
= State
.lines
[line_index
-1].section_stack
225 if previous_section_stack
then
226 new_section
= previous_section_stack
[#previous_section_stack
]
230 if new_section
and new_section
.expanded
== nil then
231 new_section
.expanded
= true
235 -- open appropriate file in source side
236 if line
.filename
~= Editor_state
.filename
then
237 source
.switch_to_file(line
.filename
)
240 Editor_state
.cursor1
= {line
=line
.line_number
, pos
=1, posB
=nil}
241 -- make sure it's visible
242 -- TODO: handle extremely long lines
243 Editor_state
.screen_top1
.line
= math
.max(0, Editor_state
.cursor1
.line
-5)
247 Editor_state
.expanded
= true
250 function log_browser
.line_index(State
, mx
,my
)
251 -- duplicate some logic from log_browser.draw
253 for line_index
= State
.screen_top1
.line
,#State
.lines
do
254 local line
= State
.lines
[line_index
]
255 if should_show(line
) then
256 y
= y
+ log_browser
.height(State
, line_index
)
260 if y
> App
.screen
.height
then break end
265 function log_browser
.mouse_released(State
, x
,y
, mouse_button
)
268 function log_browser
.textinput(State
, t
)
271 function log_browser
.keychord_pressed(State
, chord
, key
)
273 if chord
== 'up' then
274 while State
.screen_top1
.line
> 1 do
275 State
.screen_top1
.line
= State
.screen_top1
.line
-1
276 if should_show(State
.lines
[State
.screen_top1
.line
]) then
280 elseif chord
== 'down' then
281 while State
.screen_top1
.line
< #State
.lines
do
282 State
.screen_top1
.line
= State
.screen_top1
.line
+1
283 if should_show(State
.lines
[State
.screen_top1
.line
]) then
287 elseif chord
== 'pageup' then
289 while State
.screen_top1
.line
> 1 and y
< App
.screen
.height
- 100 do
290 State
.screen_top1
.line
= State
.screen_top1
.line
- 1
291 if should_show(State
.lines
[State
.screen_top1
.line
]) then
292 y
= y
+ log_browser
.height(State
, State
.screen_top1
.line
)
295 elseif chord
== 'pagedown' then
297 while State
.screen_top1
.line
< #State
.lines
and y
< App
.screen
.height
- 100 do
298 if should_show(State
.lines
[State
.screen_top1
.line
]) then
299 y
= y
+ log_browser
.height(State
, State
.screen_top1
.line
)
301 State
.screen_top1
.line
= State
.screen_top1
.line
+ 1
306 function log_browser
.height(State
, line_index
)
307 local line
= State
.lines
[line_index
]
308 if line
.data
== nil then
310 return State
.line_height
311 elseif type(line
.data
) == 'string' then
312 return State
.line_height
314 if line
.height
== nil then
315 --? print('nil line height! rendering off screen to calculate')
316 line
.height
= log_render
[line
.data
.name
](line
.data
, State
.left
, App
.screen
.height
, State
.right
-State
.left
)
322 function log_browser
.keyreleased(State
, key
, scancode
)