/ld27

To get this branch, use:
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
}