1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
-- Class: DebugConsole
-- Displays a running log of output that is print()ed, and allows
-- interactive execution of Lua This adds a debugger.unsourcedPrint
-- function that allows output of text without the usual source
-- attribution.
DebugConsole = DebugInstrument:extend
{
-- Property: inputHistory
-- A table of previously-entered commands.
inputHistory = {},
-- Property: inputHistoryIndex
-- Which history entry, if any, we are displaying.
inputHistoryIndex = 1,
width = 'wide',
contentHeight = '*',
-- Property: log
-- The <Text> sprite showing recent lines in the log.
-- Property: input
-- The <TextInput> that the user types into to enter commands.
-- Property: prompt
-- The <Text> sprite that shows a > in front of commands.
onNew = function (self)
self.title.text = 'Console'
self.log = self:add(Text:new{ font = self.font })
self.prompt = self:add(Text:new{ font = self.font, text = '>' })
local w = self.prompt:getSize()
self.inputIndent = w
self.lineHeight = self.log._fontObj:getHeight()
self.input = self:add(TextInput:new
{
font = self.font,
onType = function (self, char)
return char ~= debugger.consoleKey
end
})
-- hijack print function
-- this is nasty to debug if it goes wrong, be careful
self._oldPrint = print
print = function (...)
local caller = debug.getinfo(2, 'Sl')
if caller.linedefined ~= 0 then
self.log.text = self.log.text .. '(' .. caller.short_src .. ':' .. (caller.currentline or caller.linedefined) .. ') '
end
for _, value in pairs{...} do
self.log.text = self.log.text .. tostring(value) .. ' '
end
self.log.text = self.log.text .. '\n'
self._updateLog = true
self._oldPrint(...)
end
debugger.unsourcedPrint = function (...)
for _, value in pairs{...} do
self.log.text = self.log.text .. tostring(value) .. ' '
end
self.log.text = self.log.text .. '\n'
self._updateLog = true
self._oldPrint(...)
end
-- This replaces the default love.errhand() method, displaying
-- a stack trace and allowing inspection of the state of things.
debugger._handleCrash = function (message)
if debugger.crashed then
debugger._originalErrhand(message)
return
end
debugger.crashed = true
local print = debugger.unsourcedPrint or print
debug.sethook()
setmetatable(_G, nil)
love.audio.stop()
love.mouse.setVisible(true)
print('\n' .. string.rep('=', 40) .. '\n')
print('Crash, ' .. message)
-- print offending source line where possible
local crashState = debug.getinfo(3, 'Sl')
if string.find(crashState.source, '^@') then
print('\n>>> ' .. debugger.sourceLine(string.gsub(crashState.source, '^@', ''), crashState.currentline) .. '\n')
end
-- print or show stack and locals
if debugger.showStack then
debugger.showStack(5)
else
print(debug.traceback('', 3))
end
if debugger.showLocals then
debugger.showLocals(5)
-- move locals into global context
-- http://www.lua.org/pil/23.1.1.html
local i = 1
while true do
local name, value = debug.getlocal(4, i)
if not name then break end
if (not string.find(name, ' ')) then
_G[name] = value
end
i = i + 1
end
else
print('\nlocal variables:')
-- http://www.lua.org/pil/23.1.1.html
local i = 1
local localVars = {}
while true do
local name, value = debug.getlocal(4, i)
if not name then break end
if (not string.find(name, ' ')) then
table.insert(localVars, name)
_G[name] = value
end
i = i + 1
end
table.sort(localVars)
for _, name in pairs(localVars) do
local val
if type(_G[name]) == 'string' then
val = "'" .. string.gsub(_G[name], "'", "\\'") .. "'"
else
val = tostring(_G[name])
end
print(name .. ': ' .. val)
end
end
print(string.rep('=', 40) .. '\n')
debugger.showConsole()
if debugger._miniEventLoop then debugger._miniEventLoop(true) end
end
end,
-- Method: execute
-- Safely executes a string of code and prints the result.
--
-- Arguments:
-- code - string code to execute
--
-- Returns:
-- string result
execute = function (self, code)
if string.sub(code, 1, 1) == '=' then
code = 'debugger.unsourcedPrint (' .. string.sub(code, 2) .. ')'
end
local func, err = loadstring(code)
if func then
local ok, result = pcall(func)
if not ok then
debugger.unsourcedPrint('Error, ' .. tostring(result) .. '\n')
else
debugger.unsourcedPrint('')
end
return tostring(result)
else
debugger.unsourcedPrint('Syntax error, ' .. string.gsub(tostring(err), '^.*:', '') .. '\n')
end
end,
onUpdate = function (self, elapsed)
-- update the log contents if output is waiting
if self._updateLog then
local _, height = self.log:getSize()
local linesToDelete = math.ceil((height - self.log.height) / self.lineHeight)
if linesToDelete > 0 then
self.log.text = string.gsub(self.log.text, '.-\n', '', linesToDelete)
end
_, height = self.log:getSize()
self.prompt.y = self.log.y + height
self.input.y = self.log.y + height
self._updateLog = false
end
-- control keys to jump to different sides and erase everything
if the.keys:pressed('ctrl') and the.keys:justPressed('a') then
self.input.caret = 0
end
if the.keys:pressed('ctrl') and the.keys:justPressed('e') then
self.input.caret = string.len(self.input.text)
end
if the.keys:pressed('ctrl') and the.keys:justPressed('k') then
self.input.caret = 0
self.input.text = ''
end
-- up and down arrows cycle through history
if the.keys:justPressed('up') and self.inputHistoryIndex > 1 then
-- save what the user was in the middle of typing
self.inputHistory[self.inputHistoryIndex] = self.input.text
self.input.text = self.inputHistory[self.inputHistoryIndex - 1]
self.input.caret = string.len(self.input.text)
self.inputHistoryIndex = self.inputHistoryIndex - 1
end
if the.keys:justPressed('down') and self.inputHistoryIndex < #self.inputHistory then
self.input.text = self.inputHistory[self.inputHistoryIndex + 1]
self.input.caret = string.len(self.input.text)
self.inputHistoryIndex = self.inputHistoryIndex + 1
end
-- return executes
if the.keys:justPressed('return') then
debugger.unsourcedPrint('>' .. self.input.text)
self:execute(self.input.text)
table.insert(self.inputHistory, self.inputHistoryIndex, self.input.text)
while #self.inputHistory > self.inputHistoryIndex do
table.remove(self.inputHistory)
end
self.inputHistoryIndex = self.inputHistoryIndex + 1
self.input.text = ''
self.input.caret = 0
end
end,
onResize = function (self, x, y, width, height)
self.log.x = x + self.spacing
self.log.y = y + self.spacing
self.log.width = width - self.spacing * 2
self.log.height = height - self.spacing * 2
self.prompt.x = self.log.x
self.input.x = self.prompt.x + self.inputIndent
self.input.width = width - self.inputIndent
self._updateLog = true
end
}
|