2
-- It can be used to keep track of fps, the position of a sprite,
3
-- and so on. It only updates when visible.
5
-- This also allows debugging hotkeys -- e.g. you could set it so that
6
-- pressing Control-Alt-I toggles invincibility of the player sprite.
8
-- - Control-Alt-F toggles fullscreen
9
-- - Control-Alt-Q quits the app.
10
-- - Control-Alt-P deactivates the view.
11
-- - Control-Alt-R reloads all app code from on disk.
12
-- - Control-Alt-S saves a screenshot to the app's directory --
13
-- see https://love2d.org/wiki/love.filesystem for where this is.
15
DebugConsole = Group:extend
17
-- Property: toggleKey
18
-- What key toggles visibility. By default, this is the tab key.
21
-- Property: hotkeyModifiers
22
-- A table of modifier keys that must be held in order to activate
23
-- a debugging hotkey (set via <addHotkey()>). If you want hotkeys to
24
-- activate without having to hold any keys down, set this to nil.
25
hotkeyModifiers = {'ctrl', 'alt'},
27
-- Property: watchBasics
28
-- If true, the console will automatically start watching the frames
29
-- per second and memory usage. Changing this value after the object has
30
-- been created has no effect.
33
-- Property: watchWidth
34
-- How wide the sidebar, where watch values are displayed, should be.
37
-- Property: inputHistory
38
-- A table of previously-entered commands.
41
-- Property: inputHistoryIndex
42
-- Which history entry, if any, we are displaying.
43
inputHistoryIndex = 1,
46
-- The background <Fill> used to darken the view.
49
-- The <Text> sprite showing recent lines in the log.
51
-- Property: watchList
52
-- The <Text> sprite showing the state of all watched variables.
55
-- The <TextInput> that the user types into to enter commands.
58
-- The <Text> sprite that shows a > in front of commands.
60
-- internal property: _bindings
61
-- Keeps track of debugging hotkeys.
63
new = function (self, obj)
64
local width = the.app.width
65
local height = the.app.height
67
obj = self:extend(obj)
73
obj.fill = Fill:new{ x = 0, y = 0, width = width, height = height, fill = {0, 0, 0, 200} }
76
obj.log = Text:new{ x = 4, y = 4, width = width - self.watchWidth - 8, height = height - 8, text = '' }
79
obj.watchList = Text:new{ x = width - self.watchWidth - 4, y = 4,
80
width = self.watchWidth - 8, height = height - 8, text = '', wordWrap = false }
81
obj:add(obj.watchList)
83
obj.prompt = Text:new{ x = 4, y = 0, width = 100, text = '>' }
86
local inputIndent = obj.log._fontObj:getWidth('>') + 4
87
obj.input = TextInput:new
89
x = inputIndent, y = 0, width = the.app.width,
91
onType = function (self, char)
92
return char ~= the.console.toggleKey
97
-- some default behavior
99
obj:addHotkey('f', function() the.app:toggleFullscreen() end)
100
obj:addHotkey('p', function()
101
the.view.active = not the.view.active
102
if the.view.active then
105
the.view:tint(0, 0, 0, 200)
108
obj:addHotkey('q', love.event.quit)
109
if debugger then obj:addHotkey('r', debugger.reload) end
110
obj:addHotkey('s', function() the.app:saveScreenshot('screenshot.png') end)
112
if obj.watchBasics then
113
obj:watch('FPS', 'love.timer.getFPS()')
114
obj:watch('Memory', 'math.floor(collectgarbage("count") / 1024) .. "M"')
117
-- hijack print function
118
-- this is nasty to debug if it goes wrong, be careful
120
obj._oldPrint = print
121
print = function (...)
122
local caller = debug.getinfo(2)
124
if caller.linedefined ~= 0 then
125
obj.log.text = obj.log.text .. '(' .. caller.short_src .. ':' .. caller.linedefined .. ') '
128
for _, value in pairs{...} do
129
obj.log.text = obj.log.text .. tostring(value) .. ' '
132
obj.log.text = obj.log.text .. '\n'
133
obj._updateLog = true
137
debugger._unsourcedPrint = function (...)
138
for _, value in pairs{...} do
139
obj.log.text = obj.log.text .. tostring(value) .. ' '
142
obj.log.text = obj.log.text .. '\n'
143
obj._updateLog = true
148
if obj.onNew then obj.onNew() end
153
-- Adds an expression to be watched.
156
-- label - string label
157
-- expression - expression to evaluate as a string
159
watch = function (self, label, expression)
160
table.insert(self._watches, { label = label,
161
func = loadstring('return ' .. expression) })
165
-- Adds a hotkey to execute a function. This hotkey will require
166
-- holding down whatever modifiers are set in <hotkeyModifiers>.
169
-- key - key to trigger the hotkey
170
-- func - function to run. This will receive the key that
171
-- was pressed, so you can re-use functions (i.e.
172
-- the 1 key loads level 1, the 2 key loads level 2).
177
addHotkey = function (self, key, func)
178
table.insert(self._hotkeys, { key = key, func = func })
182
-- Safely executes a string of code and prints the result.
185
-- code - string code to execute
190
execute = function (self, code)
191
if string.sub(code, 1, 1) == '=' then
192
code = 'debugger._unsourcedPrint (' .. string.sub(code, 2) .. ')'
195
local func, err = loadstring(code)
198
local ok, result = pcall(func)
201
debugger._unsourcedPrint('Error, ' .. tostring(result) .. '\n')
203
debugger._unsourcedPrint('')
206
return tostring(result)
208
debugger._unsourcedPrint('Syntax error, ' .. string.gsub(tostring(err), '^.*:', '') .. '\n')
213
-- Shows the debug console.
221
show = function (self)
223
self.input.active = true
227
-- Hides the debug console.
235
hide = function (self)
237
self.input.active = false
240
update = function (self, elapsed)
241
-- listen for visibility key
243
if the.keys:justPressed(self.toggleKey) then
244
self.visible = not self.visible
245
self.input.active = self.visible
248
-- listen for hotkeys
250
local modifiers = (self.hotkeyModifiers == nil)
252
if not modifiers then
255
for _, key in pairs(self.hotkeyModifiers) do
256
if not the.keys:pressed(key) then
264
for _, hotkey in pairs(self._hotkeys) do
265
if the.keys:justPressed(hotkey.key) then
266
hotkey.func(hotkey.key)
274
self.watchList.text = ''
276
for _, watch in pairs(self._watches) do
277
local ok, value = pcall(watch.func)
278
if not ok then value = nil end
280
self.watchList.text = self.watchList.text .. watch.label .. ': ' .. tostring(value) .. '\n'
285
if self._updateLog then
286
local lineHeight = self.log._fontObj:getHeight()
287
local _, height = self.log:getSize()
288
local linesToDelete = math.ceil((height - the.app.height - 20) / lineHeight)
290
if linesToDelete > 0 then
291
self.log.text = string.gsub(self.log.text, '.-\n', '', linesToDelete)
292
height = height - linesToDelete * lineHeight
295
self.prompt.y = height + 4
296
self.input.y = height + 4
297
self._updateLog = false
300
-- handle special keys at the console
302
if the.keys:pressed('ctrl') and the.keys:justPressed('a') then
306
if the.keys:pressed('ctrl') and the.keys:justPressed('e') then
307
self.input.caret = string.len(self.input.text)
310
if the.keys:pressed('ctrl') and the.keys:justPressed('k') then
315
if the.keys:justPressed('up') and self.inputHistoryIndex > 1 then
316
-- save what the user was in the middle of typing
318
self.inputHistory[self.inputHistoryIndex] = self.input.text
320
self.input.text = self.inputHistory[self.inputHistoryIndex - 1]
321
self.input.caret = string.len(self.input.text)
322
self.inputHistoryIndex = self.inputHistoryIndex - 1
325
if the.keys:justPressed('down') and self.inputHistoryIndex < #self.inputHistory then
326
self.input.text = self.inputHistory[self.inputHistoryIndex + 1]
327
self.input.caret = string.len(self.input.text)
328
self.inputHistoryIndex = self.inputHistoryIndex + 1
331
if the.keys:justPressed('return') then
332
debugger._unsourcedPrint('>' .. self.input.text)
333
self:execute(self.input.text)
334
table.insert(self.inputHistory, self.inputHistoryIndex, self.input.text)
336
while #self.inputHistory > self.inputHistoryIndex do
337
table.remove(self.inputHistory)
340
self.inputHistoryIndex = self.inputHistoryIndex + 1
346
Group.update(self, elapsed)
350
-- Function: debugger.reload
351
-- Resets the entire app and forces all code to be reloaded from
352
-- on disk. via https://love2d.org/forums/viewtopic.php?f=3&t=7965
361
debugger.reload = function()
365
-- create local references to needed variables
366
-- because we're about to blow the global scope away
368
local initialGlobals = debugger._initialGlobals
369
local initialPackages = debugger._initialPackages
371
-- reset global scope
373
for key, _ in pairs(_G) do
374
_G[key] = initialGlobals[key]
377
-- reload main file and restart
379
for key, _ in pairs(package.loaded) do
380
if not initialPackages[key] then
381
package.loaded[key] = nil