feat(prompt): multiline prompt input #33371

Problem:
Cannot enter multiline prompts in a buftype=prompt buffer.

Solution:
- Support shift+enter (`<s-enter>`) to start a new line in the prompt.
- Pasting multiline text via OS paste, clipboard, "xp, etc.
- A/I in editable region works as usual.
- i/a/A/I outside of editable region moves cursor to end of current
  prompt.
- Support undo/redo in prompt buffer.
- Support o/O in prompt buffer.
- Expose prompt location as `':` mark.
This commit is contained in:
Shadman
2025-06-17 21:46:57 +06:00
committed by GitHub
parent 496691f985
commit 286371b4d2
13 changed files with 409 additions and 39 deletions

View File

@@ -3,6 +3,7 @@ local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local feed = n.feed
local fn = n.call
local source = n.source
local clear = n.clear
local command = n.command
@@ -31,7 +32,7 @@ describe('prompt buffer', function()
close
else
" Add the output above the current prompt.
call append(line("$") - 1, 'Command: "' . a:text . '"')
call append(line("$") - 1, split('Command: "' . a:text . '"', '\n'))
" Reset &modified to allow the buffer to be closed.
set nomodified
call timer_start(20, {id -> TimerFunc(a:text)})
@@ -40,7 +41,7 @@ describe('prompt buffer', function()
func TimerFunc(text)
" Add the output above the current prompt.
call append(line("$") - 1, 'Result: "' . a:text .'"')
call append(line("$") - 1, split('Result: "' . a:text .'"', '\n'))
" Reset &modified to allow the buffer to be closed.
set nomodified
endfunc
@@ -245,4 +246,280 @@ describe('prompt buffer', function()
Leave
Close]])
end)
it('can insert mutli line text', function()
source_script()
feed('line 1<s-cr>line 2<s-cr>line 3')
screen:expect([[
cmd: line 1 |
line 2 |
line 3^ |
{1:~ }|
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<cr>')
-- submiting multiline text works
screen:expect([[
Result: "line 1 |
line 2 |
line 3" |
cmd: ^ |
{3:[Prompt] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
end)
it('can paste multiline text', function()
source_script()
fn('setreg', 'a', 'line 1\nline 2\nline 3')
feed('<esc>"ap')
screen:expect([[
cmd: ^line 1 |
line 2 |
line 3 |
{1:~ }|
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
|
]])
feed('i<cr>')
screen:expect([[
Result: "line 1 |
line 2 |
line 3" |
cmd: ^ |
{3:[Prompt] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
end)
it('undo works for current prompt', function()
source_script()
-- text editiing alowed in current prompt
feed('tests-initial<esc>')
feed('bimiddle-<esc>')
screen:expect([[
cmd: tests-middle^-initial|
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
|
]])
feed('Fdx')
screen:expect([[
cmd: tests-mid^le-initial |
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
|
]])
-- can undo edits until prompt has been submitted
feed('u')
screen:expect([[
cmd: tests-mid^dle-initial|
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
1 change; {MATCH:.*} |
]])
feed('u')
screen:expect([[
cmd: tests-^initial |
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
1 change; {MATCH:.*} |
]])
feed('i<cr><esc>')
screen:expect([[
cmd: tests-initial |
Command: "tests-initial" |
Result: "tests-initial" |
cmd:^ |
{3:[Prompt] }|
other buffer |
{1:~ }|*3
|
]])
-- after submit undo does nothing
feed('u')
screen:expect([[
cmd: tests-initial |
Command: "tests-initial" |
cmd:^ |
{1:~ }|
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
1 line {MATCH:.*} |
]])
end)
it('o/O can create new lines', function()
source_script()
feed('line 1<s-cr>line 2<s-cr>line 3')
screen:expect([[
cmd: line 1 |
line 2 |
line 3^ |
{1:~ }|
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<esc>koafter')
screen:expect([[
cmd: line 1 |
line 2 |
after^ |
line 3 |
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<esc>kObefore')
screen:expect([[
cmd: line 1 |
before^ |
line 2 |
after |
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<cr>')
screen:expect([[
line 2 |
after |
line 3" |
cmd: ^ |
{3:[Prompt] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('line 4<s-cr>line 5')
feed('<esc>k0oafter prompt')
screen:expect([[
after |
line 3" |
cmd: line 4 |
after prompt^ |
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<esc>k0Oat prompt')
screen:expect([[
after |
line 3" |
cmd: at prompt^ |
line 4 |
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<cr>')
screen:expect([[
line 4 |
after prompt |
line 5" |
cmd: ^ |
{3:[Prompt] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
end)
it('deleting prompt adds it back on insert', function()
source_script()
feed('asdf')
screen:expect([[
cmd: asdf^ |
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<esc>ddi')
screen:expect([[
cmd: ^ |
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('asdf')
screen:expect([[
cmd: asdf^ |
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
feed('<esc>cc')
screen:expect([[
cmd: ^ |
{1:~ }|*3
{3:[Prompt] [+] }|
other buffer |
{1:~ }|*3
{5:-- INSERT --} |
]])
end)
it(': mark follows current prompt', function()
source_script()
feed('asdf')
eq({ 1, 1 }, api.nvim_buf_get_mark(0, ':'))
feed('<cr>')
eq({ 3, 1 }, api.nvim_buf_get_mark(0, ':'))
end)
it(': mark only available in prompt buffer', function()
source_script()
feed('asdf')
eq({ 1, 1 }, api.nvim_buf_get_mark(0, ':'))
source('set buftype=')
eq(false, pcall(api.nvim_buf_get_mark, 0, ':'))
end)
end)