commit 5ae41ddde35041e0601e94d1c6b348029c3b305b
parent efd0fa55c8b1ba78692382c52a9d644fdd5488c8
Author: Shadman <shadmansaleh3@gmail.com>
Date: Wed, 25 Jun 2025 01:42:16 +0600
feat(prompt): prompt_getinput() gets current input #34491
Problem:
Not easy to get user-input in prompt-buffer before the user submits the
input. Under the current system user/plugin needs to read the buffer
contents, figure out where the prompt is, then extract the text.
Solution:
- Add prompt_getinput().
- Extract prompt text extraction logic to a separate function
Diffstat:
10 files changed, 147 insertions(+), 21 deletions(-)
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
@@ -151,6 +151,8 @@ EDITOR
• |omnicompletion| in `help` buffer. |ft-help-omni|
• Setting "'0" in 'shada' prevents storing the jumplist in the shada file.
• 'shada' now correctly respects "/0" and "f0".
+• |prompt-buffer| supports multiline input/paste, undo/redo, and o/O normal
+ commands.
EVENTS
@@ -253,6 +255,7 @@ UI
VIMSCRIPT
• |cmdcomplete_info()| gets current cmdline completion info.
+• |prompt_getinput()| gets current user-input in prompt-buffer.
==============================================================================
CHANGED FEATURES *news-changed*
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
@@ -333,6 +333,7 @@ Functions:
- |tempname()| tries to recover if the Nvim |tempdir| disappears.
- |writefile()| with "p" flag creates parent directories.
- |searchcount()|'s maximal value is raised from 99 to 999.
+- |prompt_getinput()|
Highlight groups:
- |highlight-blend| controls blend level for a highlight group
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -7548,6 +7548,19 @@ printf({fmt}, {expr1} ...) *printf()*
Return: ~
(`string`)
+prompt_getinput({buf}) *prompt_getinput()*
+ Gets the current user-input in |prompt-buffer| {buf} without invoking
+ prompt_callback. {buf} can be a buffer name or number.
+
+ If the buffer doesn't exist or isn't a prompt buffer, an empty
+ string is returned.
+
+ Parameters: ~
+ • {buf} (`integer|string`)
+
+ Return: ~
+ (`any`)
+
prompt_getprompt({buf}) *prompt_getprompt()*
Returns the effective prompt text for buffer {buf}. {buf} can
be a buffer name or number. See |prompt-buffer|.
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -6867,6 +6867,16 @@ function vim.fn.prevnonblank(lnum) end
--- @return string
function vim.fn.printf(fmt, expr1) end
+--- Gets the current user-input in |prompt-buffer| {buf} without invoking
+--- prompt_callback. {buf} can be a buffer name or number.
+---
+--- If the buffer doesn't exist or isn't a prompt buffer, an empty
+--- string is returned.
+---
+--- @param buf integer|string
+--- @return any
+function vim.fn.prompt_getinput(buf) end
+
--- Returns the effective prompt text for buffer {buf}. {buf} can
--- be a buffer name or number. See |prompt-buffer|.
---
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
@@ -1078,7 +1078,7 @@ check_pum:
return 0;
}
if ((mod_mask & MOD_MASK_SHIFT) == 0 && bt_prompt(curbuf)) {
- invoke_prompt_callback();
+ prompt_invoke_callback();
if (!bt_prompt(curbuf)) {
// buffer changed to a non-prompt buffer, get out of
// Insert mode
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
@@ -8660,24 +8660,16 @@ void eval_fmt_source_name_line(char *buf, size_t bufsize)
}
}
-void invoke_prompt_callback(void)
+/// Gets the current user-input in prompt buffer `buf`, or NULL if buffer is not a prompt buffer.
+char *prompt_get_input(buf_T *buf)
{
- typval_T rettv;
- typval_T argv[2];
- linenr_T lnum_start = curbuf->b_prompt_start.mark.lnum;
- linenr_T lnum_last = curbuf->b_ml.ml_line_count;
-
- // Add a new line for the prompt before invoking the callback, so that
- // text can always be inserted above the last line.
- ml_append(lnum_last, "", 0, false);
- appended_lines_mark(lnum_last, 1);
- curwin->w_cursor.lnum = lnum_last + 1;
- curwin->w_cursor.col = 0;
-
- if (curbuf->b_prompt_callback.type == kCallbackNone) {
- goto theend;
+ if (!bt_prompt(buf)) {
+ return NULL;
}
- char *text = ml_get(lnum_start);
+ linenr_T lnum_start = buf->b_prompt_start.mark.lnum;
+ linenr_T lnum_last = buf->b_ml.ml_line_count;
+
+ char *text = ml_get_buf(buf, lnum_start);
char *prompt = prompt_text();
if (strlen(text) >= strlen(prompt)) {
text += strlen(prompt);
@@ -8687,11 +8679,39 @@ void invoke_prompt_callback(void)
for (linenr_T i = lnum_start + 1; i <= lnum_last; i++) {
char *half_text = concat_str(full_text, "\n");
xfree(full_text);
- full_text = concat_str(half_text, ml_get(i));
+ full_text = concat_str(half_text, ml_get_buf(buf, i));
xfree(half_text);
}
+ return full_text;
+}
+
+/// Invokes the user-defined callback defined for the current prompt-buffer.
+void prompt_invoke_callback(void)
+{
+ typval_T rettv;
+ typval_T argv[2];
+ linenr_T lnum = curbuf->b_ml.ml_line_count;
+
+ char *user_input = prompt_get_input(curbuf);
+
+ if (!user_input) {
+ return;
+ }
+
+ // Add a new line for the prompt before invoking the callback, so that
+ // text can always be inserted above the last line.
+ ml_append(lnum, "", 0, false);
+ appended_lines_mark(lnum, 1);
+ curwin->w_cursor.lnum = lnum + 1;
+ curwin->w_cursor.col = 0;
+
+ if (curbuf->b_prompt_callback.type == kCallbackNone) {
+ xfree(user_input);
+ goto theend;
+ }
+
argv[0].v_type = VAR_STRING;
- argv[0].vval.v_string = full_text;
+ argv[0].vval.v_string = user_input;
argv[1].v_type = VAR_UNKNOWN;
callback_call(&curbuf->b_prompt_callback, 1, argv, &rettv);
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -8332,6 +8332,21 @@ M.funcs = {
signature = 'printf({fmt}, {expr1} ...)',
returns = 'string',
},
+ prompt_getinput = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Gets the current user-input in |prompt-buffer| {buf} without invoking
+ prompt_callback. {buf} can be a buffer name or number.
+
+ If the buffer doesn't exist or isn't a prompt buffer, an empty
+ string is returned.
+
+ ]=],
+ name = 'prompt_getinput',
+ params = { { 'buf', 'integer|string' } },
+ signature = 'prompt_getinput({buf})',
+ },
prompt_getprompt = {
args = 1,
base = 1,
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
@@ -5364,6 +5364,26 @@ static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData
buf->b_prompt_text = xstrdup(text);
}
+/// "prompt_getinput({buffer})" function
+static void f_prompt_getinput(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // return an empty string by default, e.g. it's not a prompt buffer
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (!bt_prompt(buf)) {
+ return;
+ }
+
+ rettv->vval.v_string = prompt_get_input(buf);
+}
+
/// "pum_getpos()" function
static void f_pum_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
@@ -3853,7 +3853,7 @@ static void nv_down(cmdarg_T *cap)
} else if (bt_prompt(curbuf) && cap->cmdchar == CAR
&& curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
// In a prompt buffer a <CR> in the last line invokes the callback.
- invoke_prompt_callback();
+ prompt_invoke_callback();
if (restart_edit == 0) {
restart_edit = 'a';
}
diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua
@@ -249,6 +249,8 @@ describe('prompt buffer', function()
it('can insert multiline text', function()
source_script()
+ local buf = api.nvim_get_current_buf()
+
feed('line 1<s-cr>line 2<s-cr>line 3')
screen:expect([[
cmd: line 1 |
@@ -261,6 +263,9 @@ describe('prompt buffer', function()
{5:-- INSERT --} |
]])
+ -- prompt_getinput works with multiline input
+ eq('line 1\nline 2\nline 3', fn('prompt_getinput', buf))
+
feed('<cr>')
-- submiting multiline text works
screen:expect([[
@@ -274,6 +279,8 @@ describe('prompt buffer', function()
{5:-- INSERT --} |
]])
+ eq('', fn('prompt_getinput', buf))
+
-- % prompt is not repeated with formatoptions+=r
source([[
bwipeout!
@@ -293,6 +300,8 @@ describe('prompt buffer', function()
it('can put (p) multiline text', function()
source_script()
+ local buf = api.nvim_get_current_buf()
+
fn('setreg', 'a', 'line 1\nline 2\nline 3')
feed('<esc>"ap')
screen:expect([[
@@ -305,6 +314,10 @@ describe('prompt buffer', function()
{1:~ }|*3
|
]])
+
+ -- prompt_getinput works with pasted input
+ eq('line 1\nline 2\nline 3', fn('prompt_getinput', buf))
+
feed('i<cr>')
screen:expect([[
Result: "line 1 |
@@ -335,6 +348,8 @@ describe('prompt buffer', function()
it('can undo current prompt', function()
source_script()
+ local buf = api.nvim_get_current_buf()
+
-- text editiing alowed in current prompt
feed('tests-initial<esc>')
feed('bimiddle-<esc>')
@@ -368,6 +383,9 @@ describe('prompt buffer', function()
1 change; {MATCH:.*} |
]])
+ -- undo is reflected in prompt_getinput
+ eq('tests-middle-initial', fn('prompt_getinput', buf))
+
feed('u')
screen:expect([[
cmd: tests-^initial |
@@ -406,6 +424,8 @@ describe('prompt buffer', function()
it('o/O can create new lines', function()
source_script()
+ local buf = api.nvim_get_current_buf()
+
feed('line 1<s-cr>line 2<s-cr>line 3')
screen:expect([[
cmd: line 1 |
@@ -419,7 +439,6 @@ describe('prompt buffer', function()
]])
feed('<esc>koafter')
-
screen:expect([[
cmd: line 1 |
line 2 |
@@ -431,6 +450,9 @@ describe('prompt buffer', function()
{5:-- INSERT --} |
]])
+ -- newline created with o is reflected in prompt_getinput
+ eq('line 1\nline 2\nafter\nline 3', fn('prompt_getinput', buf))
+
feed('<esc>kObefore')
screen:expect([[
@@ -444,6 +466,9 @@ describe('prompt buffer', function()
{5:-- INSERT --} |
]])
+ -- newline created with O is reflected in prompt_getinput
+ eq('line 1\nbefore\nline 2\nafter\nline 3', fn('prompt_getinput', buf))
+
feed('<cr>')
screen:expect([[
line 2 |
@@ -552,4 +577,23 @@ describe('prompt buffer', function()
source('set buftype=')
eq("Invalid mark name: ':'", t.pcall_err(api.nvim_buf_get_mark, 0, ':'))
end)
+
+ describe('prompt_getinput', function()
+ it('returns current prompts text', function()
+ command('new')
+ local bufnr = fn('bufnr')
+ api.nvim_set_option_value('buftype', 'prompt', { buf = 0 })
+ eq('', fn('prompt_getinput', bufnr))
+ feed('iasdf')
+ eq('asdf', fn('prompt_getinput', bufnr))
+ feed('<esc>dd')
+ eq('', fn('prompt_getinput', bufnr))
+ feed('iasdf2')
+ eq('asdf2', fn('prompt_getinput', bufnr))
+
+ -- returns empty string when called from non prompt buffer
+ api.nvim_set_option_value('buftype', '', { buf = 0 })
+ eq('', fn('prompt_getinput', bufnr))
+ end)
+ end)
end)