Skip to content

Commit e1e2bb7

Browse files
committed
Code cleanup and added unit tests.
1 parent effdbc5 commit e1e2bb7

File tree

3 files changed

+241
-42
lines changed

3 files changed

+241
-42
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ Select "Tools > Lua REPL" to open the REPL. Typing the Enter key on any line eva
1616
line, unless that line is a continuation line. In that case, when finished, select the lines
1717
to evaluate and type Enter to evaluate the entire chunk.
1818

19-
Lines may be optionally prefixed with '=' (similar to the Lua prompt) to print a result.
19+
Tab completion is available, as is cycling through history with the `Ctrl+Up`/`Ctrl+Down`
20+
and `Ctrl+P` and `Ctrl+N` keys.
2021

21-
<a id="lua_repl.complete_lua"></a>
22-
## `lua_repl.complete_lua`()
22+
**Note:** if the Language Server Protocol (LSP) module is enabled, any completions coming
23+
from that module are separate from this module's completions.
2324

24-
Shows a set of Lua code completions for the current position.
25+
Lines may be optionally prefixed with '=' (similar to the Lua prompt) to print a result.
2526

2627
<a id="lua_repl.cycle_history_next"></a>
2728
## `lua_repl.cycle_history_next`()
@@ -48,7 +49,6 @@ Lua command history.
4849
It has a numeric `pos` field that indicates where in the history the user currently is.
4950

5051
Fields:
51-
5252
- `pos`:
5353

5454
<a id="lua_repl.keys"></a>

init.lua

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
-- line, unless that line is a continuation line. In that case, when finished, select the lines
1616
-- to evaluate and type Enter to evaluate the entire chunk.
1717
--
18+
-- Tab completion is available, as is cycling through history with the `Ctrl+Up`/`Ctrl+Down`
19+
-- and `Ctrl+P` and `Ctrl+N` keys.
20+
--
21+
-- **Note:** if the Language Server Protocol (LSP) module is enabled, any completions coming
22+
-- from that module are separate from this module's completions.
23+
--
1824
-- Lines may be optionally prefixed with '=' (similar to the Lua prompt) to print a result.
1925
-- @module lua_repl
2026
local M = {}
@@ -87,8 +93,9 @@ function M.evaluate_repl()
8793
buffer:set_save_point()
8894
end
8995

90-
--- Shows a set of Lua code completions for the current position.
91-
function M.complete_lua()
96+
-- Autocompleter function for the Lua REPL.
97+
-- @function _G.textadept.editing.autocompleters.lua_repl
98+
textadept.editing.autocompleters.lua_repl = function()
9299
local line, pos = buffer:get_cur_line()
93100
local symbol, op, part = line:sub(1, pos - 1):match('([%w_.]-)([%.:]?)([%w_]*)$')
94101
local ok, result = pcall((load(string.format('return (%s)', symbol), nil, 't', env)))
@@ -109,17 +116,12 @@ function M.complete_lua()
109116
end
110117
end
111118
end
112-
table.sort(cmpls)
113-
buffer.auto_c_separator, buffer.auto_c_order = string.byte(' '), buffer.ORDER_PRESORTED
114-
buffer:auto_c_show(#part - 1, table.concat(cmpls, ' '))
119+
return #part - 1, cmpls
115120
end
116121

117122
--- Cycle backward through command history, taking into account commands with multiple lines.
118123
function M.cycle_history_prev()
119-
if buffer:auto_c_active() then
120-
buffer:line_up()
121-
return
122-
end
124+
if buffer:auto_c_active() then return false end -- propagate
123125
if M.history.pos <= 1 then return end
124126
for _ in (M.history[M.history.pos] or ''):gmatch('\n') do
125127
buffer:line_delete()
@@ -132,10 +134,7 @@ end
132134

133135
--- Cycle forward through command history, taking into account commands with multiple lines.
134136
function M.cycle_history_next()
135-
if buffer:auto_c_active() then
136-
buffer:line_down()
137-
return
138-
end
137+
if buffer:auto_c_active() then return false end -- propagate
139138
if M.history.pos >= #M.history then return end
140139
for _ in (M.history[M.history.pos] or ''):gmatch('\n') do
141140
buffer:line_delete()
@@ -153,21 +152,30 @@ end
153152

154153
M.keys = {
155154
['\n'] = M.evaluate_repl, --
156-
['ctrl+ '] = M.complete_lua, --
157-
['ctrl+up'] = M.cycle_history_prev, --
158-
['ctrl+down'] = M.cycle_history_next, --
159-
['ctrl+p'] = M.cycle_history_prev, --
160-
['ctrl+n'] = M.cycle_history_next
155+
['\t'] = function() textadept.editing.autocomplete('lua_repl') end,
156+
['ctrl+up'] = M.cycle_history_prev, ['ctrl+p'] = M.cycle_history_prev, --
157+
['ctrl+down'] = M.cycle_history_next, ['ctrl+n'] = M.cycle_history_next
161158
}
162159

160+
--- Returns whether or not a buffer is the REPL buffer.
161+
local function is_repl_buf(buf) return buf._type == _L['[Lua REPL]'] end
162+
163+
--- Helper function for getting the REPL view.
164+
local function get_repl_view()
165+
for _, view in ipairs(_VIEWS) do if is_repl_buf(view.buffer) then return view end end
166+
end
167+
--- Helper function for getting the REPL buffer.
168+
local function get_repl_buffer()
169+
for _, buffer in ipairs(_BUFFERS) do if is_repl_buf(buffer) then return buffer end end
170+
end
171+
163172
--- Register REPL keys.
164173
local function register_keys()
165-
if not keys.lua[next(M.keys)] then
166-
for key, f in pairs(M.keys) do
167-
keys.lua[key] = function()
168-
if buffer._type ~= '[Lua REPL]' then return false end -- propagate
169-
f()
170-
end
174+
if keys.lua[next(M.keys)] then return end -- already registered
175+
for key, f in pairs(M.keys) do
176+
keys.lua[key] = function()
177+
if buffer._type == '[Lua REPL]' then return f() end
178+
return false -- propagate
171179
end
172180
end
173181
end
@@ -176,19 +184,7 @@ events.connect(events.RESET_AFTER, register_keys)
176184
--- Creates or switches to a Lua REPL.
177185
-- @param[opt=false] new Create a new REPL even if one already exists.
178186
function M.open(new)
179-
local repl_view, repl_buf = nil, nil
180-
for i = 1, #_VIEWS do
181-
if _VIEWS[i].buffer._type == '[Lua REPL]' then
182-
repl_view = _VIEWS[i]
183-
break
184-
end
185-
end
186-
for i = 1, #_BUFFERS do
187-
if _BUFFERS[i]._type == '[Lua REPL]' then
188-
repl_buf = _BUFFERS[i]
189-
break
190-
end
191-
end
187+
local repl_view, repl_buf = get_repl_view(), get_repl_buffer()
192188
if new or not (repl_view or repl_buf) then
193189
buffer.new()._type = '[Lua REPL]'
194190
buffer:set_lexer('lua')

init_test.lua

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
-- Copyright 2020-2025 Mitchell. See LICENSE.
2+
3+
local lua_repl = require('lua_repl')
4+
5+
test('lua_repl.open should open a REPL', function()
6+
lua_repl.open()
7+
8+
test.assert_equal(buffer._type, _L['[Lua REPL]'])
9+
test.assert_equal(buffer.lexer_language, 'lua')
10+
end)
11+
12+
test('lua_repl.open should switch to an active REPL', function()
13+
lua_repl.open()
14+
view:goto_buffer(-1)
15+
16+
lua_repl.open()
17+
18+
test.assert_equal(buffer._type, _L['[Lua REPL]'])
19+
test.assert_equal(#_BUFFERS, 2)
20+
end)
21+
22+
test('lua_repl.open should switch to a REPL active in another view', function()
23+
lua_repl.open()
24+
view:split()
25+
view:goto_buffer(-1)
26+
27+
lua_repl.open()
28+
29+
test.assert_equal(buffer._type, _L['[Lua REPL]'])
30+
test.assert_equal(_VIEWS[view], 1)
31+
test.assert_equal(_BUFFERS[_VIEWS[2].buffer], 1)
32+
end)
33+
34+
local function get_previous_line() return buffer:get_line(buffer.line_count - 1):gsub('\r?\n', '') end
35+
local function get_current_line() return buffer:get_line(buffer.line_count) end
36+
37+
test('lua_repl.open should always open a new REPL if told to do so', function()
38+
lua_repl.open()
39+
lua_repl.open(true)
40+
41+
test.assert_equal(#_BUFFERS, 3)
42+
end)
43+
44+
test('Enter should evaluate the current line', function()
45+
lua_repl.open()
46+
47+
test.type('1+2\n')
48+
49+
test.assert_equal(get_previous_line(), '--> 3')
50+
end)
51+
52+
test('Enter should evaluate selected lines', function()
53+
lua_repl.open()
54+
55+
test.type('1+\n')
56+
test.type('2')
57+
buffer:line_up_extend()
58+
test.type('\n')
59+
60+
test.assert_equal(get_previous_line(), '--> 3')
61+
end)
62+
63+
test('lua_repl.evaluate_repl should pretty-print tables', function()
64+
lua_repl.open()
65+
66+
test.type('{1,2,3}\n')
67+
68+
test.assert_equal(get_previous_line(), '--> {1 = 1, 2 = 2, 3 = 3}')
69+
end)
70+
71+
test('lua_repl.evaluate_repl should allow wrapping pretty-printed tables', function()
72+
lua_repl.open()
73+
local _<close> = test.mock(view, 'edge_column', 80)
74+
75+
test.type('buffer\n')
76+
77+
test.assert_equal(get_previous_line(), '--> }') -- result over multiple lines
78+
end)
79+
80+
test('lua_repl.evaluate_repl should use its own print function', function()
81+
lua_repl.open()
82+
83+
test.type('print(1,2,3)\n')
84+
85+
test.assert_equal(get_previous_line(), '--> 1\t2\t3')
86+
end)
87+
88+
test('Tab should show completions', function()
89+
lua_repl.open()
90+
buffer:add_text('string.') -- avoid triggering LSP
91+
local auto_c_show = test.stub()
92+
local _<close> = test.mock(buffer, 'auto_c_show', auto_c_show)
93+
94+
test.type('\t')
95+
96+
test.assert_equal(auto_c_show.called, true)
97+
test.assert_contains(auto_c_show.args[3], 'byte')
98+
end)
99+
100+
test('Tab completions should include methods', function()
101+
lua_repl.open()
102+
buffer:add_text('buffer:get') -- avoid triggering LSP
103+
local auto_c_show = test.stub()
104+
local _<close> = test.mock(buffer, 'auto_c_show', auto_c_show)
105+
106+
test.type('\t')
107+
108+
test.assert_equal(auto_c_show.called, true)
109+
test.assert_contains(auto_c_show.args[3], 'get_cur_line')
110+
end)
111+
112+
test('ctrl+p should cycle backwards through history', function()
113+
lua_repl.open()
114+
test.type('1+2\n')
115+
116+
test.type('{1,2,3}')
117+
test.type('ctrl+p')
118+
119+
test.assert_equal(get_current_line(), '1+2')
120+
end)
121+
122+
test('ctrl+p history should support multi-line input', function()
123+
lua_repl.open()
124+
test.type('1+\n')
125+
test.type('2')
126+
buffer:line_up_extend()
127+
test.type('\n')
128+
129+
test.type('ctrl+p')
130+
131+
test.assert_equal(get_previous_line(), '1+')
132+
test.assert_equal(get_current_line(), '2')
133+
end)
134+
135+
test('ctrl+p should stop cycling when there is no prior history', function()
136+
lua_repl.open()
137+
test.type('1+2\n')
138+
test.type('ctrl+p')
139+
140+
test.type('ctrl+p')
141+
142+
test.assert_equal(get_current_line(), '1+2')
143+
end)
144+
145+
test('ctrl+n should cycle forward through history', function()
146+
lua_repl.open()
147+
test.type('1+2\n')
148+
test.type('{1,2,3}\n')
149+
test.type('ctrl+p')
150+
test.type('ctrl+p')
151+
152+
test.type('ctrl+n')
153+
154+
test.assert_equal(get_current_line(), '{1,2,3}')
155+
end)
156+
157+
test('ctrl+n history should support multi-line input', function()
158+
lua_repl.open()
159+
test.type('1+\n')
160+
test.type('2')
161+
buffer:line_up_extend()
162+
test.type('\n')
163+
test.type('{1,2,3}\n')
164+
test.type('ctrl+p')
165+
test.type('ctrl+p')
166+
167+
test.type('ctrl+n')
168+
169+
test.assert_equal(get_previous_line(), '--> {1 = 1, 2 = 2, 3 = 3}')
170+
test.assert_equal(get_current_line(), '{1,2,3}')
171+
end)
172+
173+
test('ctrl+n should stop cycling when there is no more history', function()
174+
lua_repl.open()
175+
test.type('1+2\n')
176+
test.type('{1,2,3}\n')
177+
test.type('ctrl+p')
178+
test.type('ctrl+p')
179+
test.type('ctrl+n')
180+
181+
test.type('ctrl+n')
182+
183+
test.assert_equal(get_current_line(), '{1,2,3}')
184+
end)
185+
186+
test('lua_repl functionality should survive a reset #skip', function()
187+
lua_repl.open()
188+
189+
reset()
190+
test.type('1+2')
191+
192+
test.assert_equal(get_previous_line(), '--> 3')
193+
end)
194+
195+
-- Coverage tests.
196+
197+
test('Enter should not do anything special in a Lua buffer', function()
198+
buffer:set_lexer('lua')
199+
200+
test.type('\n')
201+
202+
test.assert_equal(buffer.line_count, 2)
203+
end)

0 commit comments

Comments
 (0)