/ld27

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