-
Notifications
You must be signed in to change notification settings - Fork 0
/
init.lua
382 lines (338 loc) · 9.89 KB
/
init.lua
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
-- love-repl - an interactive lua repl for love games
-- Copyright (c) 2013-2014 ioddly
-- Released under the Boost License: <http://www.boost.org/LICENSE_1_0.txt>
-- Module
local repl = {
_VERSION = 'love-repl v0.2',
_DESCRIPTION = "An interactive lua REPL for Love games",
_URL = "https://github.com/ioddly/love-repl",
_LICENSE = "Boost 1.0",
toggle_key = 'f8',
clear_key = 'escape',
padding_left = 10,
max_lines = 1000,
max_history = 1000,
font = nil,
screenshot = true,
background = false,
dark_factor = 0.6,
wrapping = false
}
-- How many pixels of padding are on either side
local PADDING = 20
-- How many pixels are required to display a row
local ROW_HEIGHT
-- Maximum amount of rows that can be displayed on the screen
local DISPLAY_ROWS
-- Width of the display available for text, in pixels
local DISPLAY_WIDTH
-- True when open, false when closed
local toggled = false
-- Console contents
-- History is just a list of strings
local history
-- List of {boolean, string} where boolean is true if the string is part of user-navigable history (a > will be prepended before rendering if true)
local lines
-- Line that is currently being edited
local editline = ""
-- Location in the editline
local cursor = 0
-- Current position in history
local histpos = 0
-- Save the game's keyboard settings
local kprepeat
-- Line display offset (in case of scrolling up and down)
local offset = 1
-- Circular buffer functionality
local buffer = {}
function buffer:new(ob)
local o = ob or {}
o.entries = #o
o.cursor = #o + 1
o.max = 10
setmetatable(o, self)
self.__index = self
return o
end
function buffer:append(entry)
if self[self.cursor] then
self[self.cursor] = entry
else
table.insert(self, entry)
end
self.cursor = self.cursor + 1
if self.cursor == self.max + 1 then
self.cursor = 1
end
if self.entries ~= self.max then
self.entries = self.entries + 1
end
end
function buffer:get(idx)
-- Allow negative indexes
if idx < 0 then
idx = (self.entries + idx) + 1
end
if self.entries == self.max then
local c = self.cursor + idx - 1
if c > self.max then
c = c - self.max
end
return self[c]
else
return self[idx]
end
end
function repl.initialize()
lines = buffer:new({"! love-repl"})
lines.max = repl.max_lines
history = buffer:new()
history.max = repl.max_history
-- Expose these in case somebody wants to use them
repl.lines = lines
repl.history = history
if not repl.font then
repl.font = love.graphics.newFont(12)
end
end
function repl.toggle()
toggled = not toggled
if toggled then
kprepeat = love.keyboard.hasKeyRepeat()
love.keyboard.setKeyRepeat(true)
if repl.screenshot then
repl.background = love.graphics.newImage(love.graphics.newScreenshot())
end
else
love.keyboard.setKeyRepeat(kprepeat)
repl.on_close()
end
end
function repl.toggled()
return toggled
end
function repl.on_close() end
function repl.append(history, value)
value = tostring(value)
lines:append(history and ('> ' .. value) or value)
end
function repl.print(text)
repl.append(false, text)
end
local function pack(...) return {...} end
function repl.eval(text, add_to_history)
-- Evaluate string
if text:sub(0,1) == '=' then
text = 'return ' .. text:sub(2)
end
local func, err = loadstring(text)
-- Compilation error
if not func then
if err then
-- Could be an expression instead of a statement -- try auto-adding return before it
local err2
func, err2 = loadstring("return " .. text)
if err2 then
repl.print('! Compilation error: ' .. err)
return false
end
else
repl.print('! Unknown compilation error')
end
end
-- Try evaluating
if func then
local result = pack(pcall(func))
local ret = result[2]
if result[1] then
repl.append(true, text)
local results, i = tostring(result[2]), 3
if add_to_history then
if text:sub(0,1) == '=' then
history:append('return ' .. text:sub(2))
else
history:append(text)
end
end
while i <= #result do
results = results .. ', ' .. tostring(result[i])
i = i + 1
end
repl.print(results)
return true
else
repl.print('! Evaluation error: ' .. ret)
end
end
return false
end
function repl.mousepressed(x, y, button)
if button == 'wu' then
if offset <= (lines.entries - DISPLAY_ROWS) then
offset = offset + 1
end
elseif button == 'wd' then
if offset - 1 ~= 0 then
offset = offset - 1
end
end
end
-- Line editing functionality and key handling
local function reset_editline()
editline = ''
cursor = 0
prompt_prefix = ''
end
local function get_history()
if histpos > 0 then
editline = history:get(-histpos)
cursor = #editline
end
end
local function ctrlp() return love.keyboard.isDown('lctrl') or love.keyboard.isDown('rctrl') end
local function shiftp() return love.keyboard.isDown('lshift') or love.keyboard.isDown('rshift') end
function repl.keypressed(k, isrepeat)
-- Line editing
if k == 'backspace' then
editline = editline:sub(0, cursor - 1) .. editline:sub(cursor + 1, #editline)
if cursor > 0 then
cursor = cursor - 1
end
elseif k == 'delete' then
editline = editline:sub(0, cursor) .. editline:sub(cursor + 2, #editline)
elseif ctrlp() and k == 'a' then
cursor = 0
elseif ctrlp() and k == 'e' then
cursor = #editline
elseif k == 'return' then
histpos = 0
offset = 1
if editline == '' then return end
if repl.eval(editline, true) then
reset_editline()
end
elseif k == 'up' then
if histpos + 1 <= history.entries then
histpos = histpos + 1
get_history()
end
-- Navigation
elseif k == 'home' then
offset = math.max(1, lines.entries - DISPLAY_ROWS + 1)
elseif k == 'end' then
offset = 1
elseif k == 'pageup' then
offset = math.min(lines.entries - DISPLAY_ROWS + 1, offset + DISPLAY_ROWS)
elseif k == 'pagedown' then
offset = math.max(1, offset - DISPLAY_ROWS)
elseif k == repl.clear_key then
reset_editline()
elseif k == 'down' then
if histpos - 1 > 0 then
histpos = histpos - 1
get_history()
else
histpos = 0
reset_editline()
end
elseif k == 'left' and cursor > 0 then
cursor = cursor - 1
elseif k == 'right' and cursor ~= #editline then
cursor = cursor + 1
elseif k == repl.toggle_key then
repl.toggle()
assert(toggled == false)
end
end
function repl.textinput(t)
editline = editline:sub(0, cursor) .. t .. editline:sub(cursor + 1)
cursor = cursor + 1
end
-- Rendering
function repl.draw()
local width, height = love.window.getMode()
local font = repl.font
ROW_HEIGHT = font:getHeight()
DISPLAY_WIDTH = width - PADDING
DISPLAY_ROWS = math.floor((height - (ROW_HEIGHT * 2)) / ROW_HEIGHT)
local saved_font = love.graphics.getFont()
love.graphics.setFont(font)
-- Draw background
if repl.screenshot then
local c = 255 * repl.dark_factor
love.graphics.setColor(c,c,c,c)
love.graphics.draw(repl.background, 0, 0)
love.graphics.setColor(255, 255, 255, 255)
elseif repl.background then
love.graphics.draw(repl.background, 0, 0)
else
love.graphics.clear()
end
-- Leave some room for text entry
local width, height = love.window.getMode()
local limit = height - (ROW_HEIGHT * 2)
-- print edit line
local prefix = "> "
local ln = prefix .. editline
love.graphics.print(ln, repl.padding_left, limit)
-- draw cursor
local cx, cy = repl.padding_left + 1 + font:getWidth(prefix .. editline:sub(0, cursor)),
limit + font:getHeight() + 2
love.graphics.line(cx, cy, cx + 5, cy)
-- draw history
-- maximum characters in a rendered line of text
local render_line = function(ln, row)
love.graphics.print(ln, repl.padding_left, limit - (ROW_HEIGHT * (row + 1)))
end
local render_lines = function(ln, row, rows)
love.graphics.printf(ln, repl.padding_left, limit - (ROW_HEIGHT * (row + rows)), DISPLAY_WIDTH)
end
if repl.wrapping then
-- max chars in a line
local line_max = (width - (repl.padding_left * 2)) / font:getWidth('a')
local pos, lines_drawn = offset, 0
while lines_drawn < DISPLAY_ROWS do
local line = lines:get(-pos)
if line == nil then break end
local lines_to_draw = math.ceil(#line / line_max)
render_lines(line, lines_drawn, lines_to_draw)
lines_drawn = lines_drawn + lines_to_draw
pos = pos + 1
end
else
for i = offset, DISPLAY_ROWS + offset do
local line = lines:get(-i)
if line == nil then break end
render_line(line, i - offset)
end
end
-- draw scroll bar
-- this only gives you an estimate since it uses the amount of lines entered rather than the lines drawn, but close
-- enough
-- height is percentage of the possible lines
local bar_height = math.min(100, (DISPLAY_ROWS * 100) / lines.entries)
-- convert to pixels (percentage of screen height, minus 10px padding)
local bar_height_pixels = (bar_height * (height - 10)) / 100
local sx = width - 5
-- Handle the case where there are less actual lines than display rows
if bar_height_pixels >= height - 10 then
love.graphics.line(sx, 5, sx, height - 5)
else
-- now determine location on the screen by taking the offset in history and converting it first to a percentage of total lines and then a pixel offset on the screen
local bar_end = (offset * 100) / lines.entries
bar_end = ((height - 10) * bar_end) / 100
bar_end = height - bar_end
local bar_begin = bar_end - bar_height_pixels
-- Handle overflows
if bar_begin < 5 then
love.graphics.line(sx, 5, sx, bar_height_pixels)
elseif bar_end > height - 5 then
love.graphics.line(sx, height - 5 - bar_height_pixels, sx, height - 5)
else
love.graphics.line(sx, bar_begin, sx, bar_end)
end
end
-- reset font
love.graphics.setFont(saved_font)
end
return repl