2
-- - Control-Alt-F toggles fullscreen
3
-- - Control-Alt-Q quits the app.
4
-- - Control-Alt-P deactivates the view.
5
-- - Control-Alt-S saves a screenshot to the app's directory --
6
-- see https://love2d.org/wiki/love.filesystem for where this is.
8
DebugConsole = Group:extend
10
-- Property: toggleKey
11
-- What key toggles visibility. By default, this is the tab key.
14
-- Property: hotkeyModifiers
15
-- A table of modifier keys that must be held in order to activate
16
-- a debugging hotkey (set via <addHotkey()>). If you want hotkeys to
17
-- activate without having to hold any keys down, set this to nil.
18
hotkeyModifiers = {'ctrl', 'alt'},
20
-- Property: watchBasics
21
-- If true, the console will automatically start watching the frames
22
-- per second and memory usage. Changing this value after the object has
23
-- been created has no effect.
26
-- Property: watchWidth
27
-- How wide the sidebar, where watch values are displayed, should be.
30
-- Property: inputHistory
31
-- A table of previously-entered commands.
34
-- Property: inputHistoryIndex
35
-- Which history entry, if any, we are displaying.
36
inputHistoryIndex = 1,
39
-- The background <Fill> used to darken the view.
42
-- The <Text> sprite showing recent lines in the log.
44
-- Property: watchList
45
-- The <Text> sprite showing the state of all watched variables.
48
-- The <TextInput> that the user types into to enter commands.
51
-- The <Text> sprite that shows a > in front of commands.
53
-- internal property: _bindings
54
-- Keeps track of debugging hotkeys.
56
new = function (self, obj)
57
local width = the.app.width
58
local height = the.app.height
60
obj = self:extend(obj)
66
obj.fill = Fill:new{ x = 0, y = 0, width = width, height = height, fill = {0, 0, 0, 200} }
69
obj.log = Text:new{ x = 4, y = 4, width = width - self.watchWidth - 8, height = height - 8, text = '' }
72
obj.watchList = Text:new{ x = width - self.watchWidth - 4, y = 4,
73
width = self.watchWidth - 8, height = height - 8, text = '', wordWrap = false }
74
obj:add(obj.watchList)
76
obj.prompt = Text:new{ x = 4, y = 0, width = 100, text = '>' }
79
local inputIndent = obj.log._fontObj:getWidth('>') + 4
80
obj.input = TextInput:new
82
x = inputIndent, y = 0, width = the.app.width,
84
onType = function (self, char)
85
return char ~= the.console.toggleKey
90
-- some default behavior
92
obj:addHotkey('f', function() the.app:toggleFullscreen() end)
93
obj:addHotkey('p', function()
94
the.view.active = not the.view.active
95
if the.view.active then
98
the.view:tint(0, 0, 0, 200)
101
obj:addHotkey('q', love.event.quit)
102
if debugger then obj:addHotkey('r', debugger.reload) end
103
obj:addHotkey('s', function() the.app:saveScreenshot('screenshot.png') end)
105
if obj.watchBasics then
106
obj:watch('FPS', 'love.timer.getFPS()')
107
obj:watch('Memory', 'math.floor(collectgarbage("count") / 1024) .. "M"')
110
-- hijack print function
111
-- this is nasty to debug if it goes wrong, be careful
113
obj._oldPrint = print
114
print = function (...)
115
local caller = debug.getinfo(2)
117
if caller.linedefined ~= 0 then
118
obj.log.text = obj.log.text .. '(' .. caller.short_src .. ':' .. caller.linedefined .. ') '
121
for _, value in pairs{...} do
122
obj.log.text = obj.log.text .. tostring(value) .. ' '
125
obj.log.text = obj.log.text .. '\n'
126
obj._updateLog = true
130
debugger._unsourcedPrint = function (...)
131
for _, value in pairs{...} do
132
obj.log.text = obj.log.text .. tostring(value) .. ' '
135
obj.log.text = obj.log.text .. '\n'
136
obj._updateLog = true
141
if obj.onNew then obj.onNew() end
146
-- Adds an expression to be watched.
149
-- label - string label
150
-- expression - expression to evaluate as a string
152
watch = function (self, label, expression)
153
table.insert(self._watches, { label = label,
154
func = loadstring('return ' .. expression) })
158
-- Adds a hotkey to execute a function. This hotkey will require
159
-- holding down whatever modifiers are set in <hotkeyModifiers>.
162
-- key - key to trigger the hotkey
163
-- func - function to run. This will receive the key that
164
-- was pressed, so you can re-use functions (i.e.
165
-- the 1 key loads level 1, the 2 key loads level 2).
170
addHotkey = function (self, key, func)
171
table.insert(self._hotkeys, { key = key, func = func })
175
-- Safely executes a string of code and prints the result.
178
-- code - string code to execute
183
execute = function (self, code)
184
if string.sub(code, 1, 1) == '=' then
185
code = 'debugger._unsourcedPrint (' .. string.sub(code, 2) .. ')'
188
local func, err = loadstring(code)
191
local ok, result = pcall(func)
194
debugger._unsourcedPrint('Error, ' .. tostring(result) .. '\n')
196
debugger._unsourcedPrint('')
199
return tostring(result)
201
debugger._unsourcedPrint('Syntax error, ' .. string.gsub(tostring(err), '^.*:', '') .. '\n')
206
-- Shows the debug console.
214
show = function (self)
216
self.input.active = true
220
-- Hides the debug console.
228
hide = function (self)
230
self.input.active = false
233
update = function (self, elapsed)
234
-- listen for visibility key
236
if the.keys:justPressed(self.toggleKey) then
237
self.visible = not self.visible
238
self.input.active = self.visible
241
-- listen for hotkeys
243
local modifiers = (self.hotkeyModifiers == nil)
245
if not modifiers then
248
for _, key in pairs(self.hotkeyModifiers) do
249
if not the.keys:pressed(key) then
257
for _, hotkey in pairs(self._hotkeys) do
258
if the.keys:justPressed(hotkey.key) then
259
hotkey.func(hotkey.key)
267
self.watchList.text = ''
269
for _, watch in pairs(self._watches) do
270
local ok, value = pcall(watch.func)
271
if not ok then value = nil end
273
self.watchList.text = self.watchList.text .. watch.label .. ': ' .. tostring(value) .. '\n'
278
if self._updateLog then
279
local lineHeight = self.log._fontObj:getHeight()
280
local _, height = self.log:getSize()
281
local linesToDelete = math.ceil((height - the.app.height - 20) / lineHeight)
283
if linesToDelete > 0 then
284
self.log.text = string.gsub(self.log.text, '.-\n', '', linesToDelete)
285
height = height - linesToDelete * lineHeight
288
self.prompt.y = height + 4
289
self.input.y = height + 4
290
self._updateLog = false
293
-- handle special keys at the console
295
if the.keys:pressed('ctrl') and the.keys:justPressed('a') then
299
if the.keys:pressed('ctrl') and the.keys:justPressed('e') then
300
self.input.caret = string.len(self.input.text)
303
if the.keys:pressed('ctrl') and the.keys:justPressed('k') then
308
if the.keys:justPressed('up') and self.inputHistoryIndex > 1 then
309
-- save what the user was in the middle of typing
311
self.inputHistory[self.inputHistoryIndex] = self.input.text
313
self.input.text = self.inputHistory[self.inputHistoryIndex - 1]
314
self.input.caret = string.len(self.input.text)
315
self.inputHistoryIndex = self.inputHistoryIndex - 1
318
if the.keys:justPressed('down') and self.inputHistoryIndex < #self.inputHistory then
319
self.input.text = self.inputHistory[self.inputHistoryIndex + 1]
320
self.input.caret = string.len(self.input.text)
321
self.inputHistoryIndex = self.inputHistoryIndex + 1
324
if the.keys:justPressed('return') then
325
debugger._unsourcedPrint('>' .. self.input.text)
326
self:execute(self.input.text)
327
table.insert(self.inputHistory, self.inputHistoryIndex, self.input.text)
329
while #self.inputHistory > self.inputHistoryIndex do
330
table.remove(self.inputHistory)
333
self.inputHistoryIndex = self.inputHistoryIndex + 1
339
Group.update(self, elapsed)
348
debugger.reload = function()
352
-- create local references to needed variables
353
-- because we're about to blow the global scope away
355
local initialGlobals = debugger._initialGlobals
356
local initialPackages = debugger._initialPackages
358
-- reset global scope
360
for key, _ in pairs(_G) do
361
_G[key] = initialGlobals[key]
364
-- reload main file and restart
366
for key, _ in pairs(package.loaded) do
367
if not initialPackages[key] then
368
package.loaded[key] = nil