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