neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/doc/news.txt | 3+++
Mruntime/doc/vim_diff.txt | 1+
Mruntime/doc/vimfn.txt | 13+++++++++++++
Mruntime/lua/vim/_meta/vimfn.lua | 10++++++++++
Msrc/nvim/edit.c | 2+-
Msrc/nvim/eval.c | 56++++++++++++++++++++++++++++++++++++++------------------
Msrc/nvim/eval.lua | 15+++++++++++++++
Msrc/nvim/eval/funcs.c | 20++++++++++++++++++++
Msrc/nvim/normal.c | 2+-
Mtest/functional/legacy/prompt_buffer_spec.lua | 46+++++++++++++++++++++++++++++++++++++++++++++-
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)