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{
16
-- Property: toggleKey
17
-- What key toggles visibility. By default, this is the tab key.
20
-- Property: hotkeyModifiers
21
-- A table of modifier keys that must be held in order to activate
22
-- a debugging hotkey (set via <addHotkey()>). If you want hotkeys to
23
-- activate without having to hold any keys down, set this to nil.
24
hotkeyModifiers = {'ctrl', 'alt'},
26
-- Property: watchBasics
27
-- If true, the console will automatically start watching the frames
28
-- per second and memory usage. Changing this value after the object has
29
-- been created has no effect.
32
-- Property: watchWidth
33
-- How wide the sidebar, where watch values are displayed, should be.
36
-- Property: inputHistory
37
-- A table of previously-entered commands.
40
-- Property: inputHistoryIndex
41
-- Which history entry, if any, we are displaying.
42
inputHistoryIndex = 1,
45
-- The background <Fill> used to darken the view.
48
-- The <Text> sprite showing recent lines in the log.
50
-- Property: watchList
51
-- The <Text> sprite showing the state of all watched variables.
54
-- The <TextInput> that the user types into to enter commands.
57
-- The <Text> sprite that shows a > in front of commands.
59
-- internal property: _bindings
60
-- Keeps track of debugging hotkeys.
62
new = function (self, obj)
63
local width = the.app.width
64
local height = the.app.height
66
obj = self:extend(obj)
72
obj.fill = Fill:new{ x = 0, y = 0, width = width, height = height, fill = {0, 0, 0, 200} }
75
obj.log = Text:new{ x = 4, y = 4, width = width - self.watchWidth - 8, height = height - 8, text = '' }
78
obj.watchList = Text:new{ x = width - self.watchWidth - 4, y = 4,
79
width = self.watchWidth - 8, height = height - 8, text = '', wordWrap = false }
80
obj:add(obj.watchList)
82
obj.prompt = Text:new{ x = 4, y = 0, width = 100, text = '>' }
85
local inputIndent = obj.log._fontObj:getWidth('>') + 4
86
obj.input = TextInput:new{
87
x = inputIndent, y = 0, width = the.app.width,
89
onType = function (self, char)
90
return char ~= the.console.toggleKey
95
-- some default behavior
97
obj:addHotkey('f', function() the.app:toggleFullscreen() end)
98
obj:addHotkey('p', function()
99
the.view.active = not the.view.active
100
if the.view.active then
103
the.view:tint(0, 0, 0, 200)
106
obj:addHotkey('q', love.event.quit)
107
if debugger then obj:addHotkey('r', debugger.reload) end
108
obj:addHotkey('s', function() the.app:saveScreenshot('screenshot.png') end)
110
if obj.watchBasics then
111
obj:watch('FPS', 'love.timer.getFPS()')
112
obj:watch('Memory', 'math.floor(collectgarbage("count") / 1024) .. "M"')
115
-- hijack print function
116
-- this is nasty to debug if it goes wrong, be careful
118
obj._oldPrint = print
119
print = function (...)
120
for _, value in pairs{...} do
121
obj.log.text = obj.log.text .. tostring(value) .. ' '
124
obj.log.text = obj.log.text .. '\n'
125
obj._updateLog = true
130
if obj.onNew then obj.onNew() end
135
-- Adds an expression to be watched.
138
-- label - string label
139
-- expression - expression to evaluate as a string
141
watch = function (self, label, expression)
142
table.insert(self._watches, { label = label,
143
func = loadstring('return ' .. expression) })
147
-- Adds a hotkey to execute a function. This hotkey will require
148
-- holding down whatever modifiers are set in <hotkeyModifiers>.
151
-- key - key to trigger the hotkey
152
-- func - function to run. This will receive the key that
153
-- was pressed, so you can re-use functions (i.e.
154
-- the 1 key loads level 1, the 2 key loads level 2).
159
addHotkey = function (self, key, func)
160
table.insert(self._hotkeys, { key = key, func = func })
164
-- Safely executes a string of code and prints the result.
167
-- code - string code to execute
172
execute = function (self, code)
173
if string.sub(code, 1, 1) == '=' then
174
code = 'print (' .. string.sub(code, 2) .. ')'
177
local func, err = loadstring(code)
180
local ok, result = pcall(func)
183
print('Error, ' .. tostring(result) .. '\n')
188
return tostring(result)
190
print('Syntax error, ' .. string.gsub(tostring(err), '^.*:', '') .. '\n')
195
-- Shows the debug console.
203
show = function (self)
205
self.input.active = true
209
-- Hides the debug console.
217
hide = function (self)
219
self.input.active = false
222
update = function (self, elapsed)
223
-- listen for visibility key
225
if the.keys:justPressed(self.toggleKey) then
226
self.visible = not self.visible
227
self.input.active = self.visible
230
-- listen for hotkeys
232
local modifiers = (self.hotkeyModifiers == nil)
234
if not modifiers then
237
for _, key in pairs(self.hotkeyModifiers) do
238
if not the.keys:pressed(key) then
246
for _, hotkey in pairs(self._hotkeys) do
247
if the.keys:justPressed(hotkey.key) then
248
hotkey.func(hotkey.key)
256
self.watchList.text = ''
258
for _, watch in pairs(self._watches) do
259
local ok, value = pcall(watch.func)
260
if not ok then value = nil end
262
self.watchList.text = self.watchList.text .. watch.label .. ': ' .. tostring(value) .. '\n'
267
if self._updateLog then
268
local maxHeight = the.app.height - 20
269
local _, height = self.log:getSize()
271
while height > maxHeight do
272
self.log.text = string.gsub(self.log.text, '^.-\n', '')
273
_, height = self.log:getSize()
276
self.prompt.y = height + 4
277
self.input.y = height + 4
278
self._updateLog = false
281
-- handle special keys at the console
283
if the.keys:pressed('ctrl') and the.keys:justPressed('a') then
287
if the.keys:pressed('ctrl') and the.keys:justPressed('e') then
288
self.input.caret = string.len(self.input.text)
291
if the.keys:pressed('ctrl') and the.keys:justPressed('k') then
296
if the.keys:justPressed('up') and self.inputHistoryIndex > 1 then
297
-- save what the user was in the middle of typing
299
self.inputHistory[self.inputHistoryIndex] = self.input.text
301
self.input.text = self.inputHistory[self.inputHistoryIndex - 1]
302
self.input.caret = string.len(self.input.text)
303
self.inputHistoryIndex = self.inputHistoryIndex - 1
306
if the.keys:justPressed('down') and self.inputHistoryIndex < #self.inputHistory then
307
self.input.text = self.inputHistory[self.inputHistoryIndex + 1]
308
self.input.caret = string.len(self.input.text)
309
self.inputHistoryIndex = self.inputHistoryIndex + 1
312
if the.keys:justPressed('return') then
313
print('>' .. self.input.text)
314
self:execute(self.input.text)
315
table.insert(self.inputHistory, self.inputHistoryIndex, self.input.text)
317
while #self.inputHistory > self.inputHistoryIndex do
318
table.remove(self.inputHistory)
321
self.inputHistoryIndex = self.inputHistoryIndex + 1
327
Group.update(self, elapsed)
331
-- Function: debugger.reload
332
-- Resets the entire app and forces all code to be reloaded from
333
-- on disk. via https://love2d.org/forums/viewtopic.php?f=3&t=7965
342
debugger.reload = function()
344
-- create local references to needed variables
345
-- because we're about to blow the global scope away
347
local initialGlobals = debugger._initialGlobals
348
local initialPackages = debugger._initialPackages
350
-- reset global scope
352
for key, _ in pairs(_G) do
353
_G[key] = initialGlobals[key]
356
-- reload main file and restart
358
for key, _ in pairs(package.loaded) do
359
if not initialPackages[key] then
360
package.loaded[key] = nil