Skip to content

Commit cf84e46

Browse files
committed
feat: Add multiline input for repl
1 parent 8f396b7 commit cf84e46

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

lua/dap.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ local DAP_QUICKFIX_CONTEXT = DAP_QUICKFIX_TITLE
8181
---@field id string|nil
8282
---@field options nil|AdapterOptions
8383
---@field enrich_config fun(config: Configuration, on_config: fun(config: Configuration))
84+
---@field is_multiline fun(text: string)
8485

8586
---@class AdapterOptions
8687
---@field initialize_timeout_sec nil|number

lua/dap/repl.lua

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,28 @@ local function get_session()
1313
return require('dap').session()
1414
end
1515

16+
local repl
1617
local execute -- required for forward reference
18+
local function execute_current(opts)
19+
local current_text = api.nvim_buf_get_lines(repl.buf, -2, -1, false)[1]
20+
local prompt_length = #vim.fn.prompt_getprompt(repl.buf)
21+
execute(string.sub(current_text, prompt_length + 1), opts)
22+
end
23+
24+
25+
local REGULAR_PROMPT = 1
26+
local FOLLOWUP_PROMPT = 2
27+
local PROMPT_STATE = 'PROMPT_STATE'
28+
29+
local function get_prompt(buf)
30+
buf = buf or repl.buf
31+
local prompt_state = vim.b[buf][PROMPT_STATE]
32+
if prompt_state == REGULAR_PROMPT then
33+
return 'dap> '
34+
else
35+
return '...> '
36+
end
37+
end
1738

1839

1940
local function new_buf()
@@ -31,7 +52,12 @@ local function new_buf()
3152
api.nvim_buf_set_keymap(buf, 'n', 'o', "<Cmd>lua require('dap.ui').trigger_actions()<CR>", {})
3253
api.nvim_buf_set_keymap(buf, 'i', '<up>', "<Cmd>lua require('dap.repl').on_up()<CR>", {})
3354
api.nvim_buf_set_keymap(buf, 'i', '<down>', "<Cmd>lua require('dap.repl').on_down()<CR>", {})
34-
vim.fn.prompt_setprompt(buf, 'dap> ')
55+
56+
-- CR keybindings may require additional for some terminals see https://stackoverflow.com/a/42461580
57+
vim.keymap.set('i', '<S-CR>', function() execute_current({force_followup = true}) end, {buffer = buf})
58+
vim.keymap.set('i', '<C-CR>', function() execute_current({force_finish = true}) end, {buffer = buf})
59+
vim.b[buf][PROMPT_STATE] = REGULAR_PROMPT
60+
vim.fn.prompt_setprompt(buf, get_prompt(buf))
3561
vim.fn.prompt_setcallback(buf, execute)
3662
return buf
3763
end
@@ -46,7 +72,7 @@ local function new_win(buf, winopts, wincmd)
4672
return win
4773
end
4874

49-
local repl = ui.new_view(
75+
repl = ui.new_view(
5076
new_buf,
5177
new_win, {
5278
before_open = function()
@@ -197,7 +223,48 @@ local function print_threads(threads)
197223
end
198224

199225

200-
function execute(text)
226+
---- Handle multiline inputs
227+
--
228+
-- Returns nil if input is not complete, otherwise returns the full user input
229+
local function handle_followup_input(curr_text, force_followup, force_finish)
230+
local session = get_session()
231+
if not session then
232+
return curr_text
233+
end
234+
local is_multiline = session.adapter.is_multiline
235+
if not force_followup and not is_multiline then
236+
return curr_text
237+
end
238+
local buf_state = vim.b[repl.buf]
239+
local prompt_state = buf_state[PROMPT_STATE]
240+
if prompt_state == REGULAR_PROMPT and (force_followup or is_multiline(curr_text)) then
241+
buf_state[PROMPT_STATE] = FOLLOWUP_PROMPT
242+
buf_state['curr_text'] = { curr_text }
243+
vim.fn.prompt_setprompt(repl.buf, get_prompt())
244+
return nil
245+
elseif prompt_state == FOLLOWUP_PROMPT then
246+
local all_text = buf_state['curr_text']
247+
if curr_text == '' or force_finish then
248+
buf_state[PROMPT_STATE] = REGULAR_PROMPT
249+
vim.fn.prompt_setprompt(repl.buf, get_prompt())
250+
table.insert(all_text, curr_text)
251+
return table.concat(all_text, '\n')
252+
else
253+
table.insert(all_text, curr_text)
254+
vim.b[repl.buf]['curr_text'] = all_text
255+
return nil
256+
end
257+
end
258+
return curr_text
259+
end
260+
261+
function execute(text, opts)
262+
opts = opts or {}
263+
text = handle_followup_input(text, opts.force_followup, opts.force_finish)
264+
if text == nil then
265+
return
266+
end
267+
201268
if text == '' then
202269
if history.last then
203270
text = history.last
@@ -315,7 +382,7 @@ local function select_history(delta)
315382
local text = history.entries[history.idx]
316383
if text then
317384
local lnum = vim.fn.line('$') - 1
318-
api.nvim_buf_set_lines(repl.buf, lnum, lnum + 1, true, {'dap> ' .. text })
385+
api.nvim_buf_set_lines(repl.buf, lnum, lnum + 1, true, {get_prompt() .. text })
319386
end
320387
end
321388

@@ -382,7 +449,7 @@ do
382449
local session = get_session()
383450
local col = api.nvim_win_get_cursor(0)[2]
384451
local line = api.nvim_get_current_line()
385-
local offset = vim.startswith(line, 'dap> ') and 5 or 0
452+
local offset = vim.startswith(line, get_prompt()) and 5 or 0
386453
local line_to_cursor = line:sub(offset + 1, col)
387454
local text_match = vim.fn.match(line_to_cursor, '\\k*$')
388455
if vim.startswith(line_to_cursor, '.') or base ~= '' then

0 commit comments

Comments
 (0)