feat(prompt): prompt_appendbuf() appends to prompt buffer #37763

Problem:
Currently, we recommend always inserting text above prompt-line in
prompt-buffer. This can be done using the `:` mark. However, although
we recommend it this way it can sometimes get confusing how to do it
best.

Solution:
Provide an api to append text to prompt buffer. This is a common
use-case for things using prompt-buffer.
This commit is contained in:
Shadman
2026-03-27 17:39:09 +06:00
committed by GitHub
parent 0fa96585dc
commit a940b77cb2
12 changed files with 167 additions and 8 deletions

View File

@@ -235,8 +235,8 @@ command, displaying shell output above the prompt: >lua
-- Handles output from the shell.
local function on_output(_, msg, _)
-- Add shell output above the prompt.
local input_start = vim.api.nvim_buf_get_mark(0, ":")[1]
vim.fn.append(input_start - 1, msg)
local buf = vim.api.nvim_get_current_buf()
vim.fn.prompt_appendbuf(buf, msg)
end
-- Handles the shell exit.

View File

@@ -488,6 +488,7 @@ VIMSCRIPT
• |prompt_getinput()| gets current user-input in prompt-buffer.
• |wildtrigger()| triggers command-line expansion.
• |v:vim_did_init| is set after sourcing |init.vim| but before |load-plugins|.
• |prompt_appendbuf()| appends text to prompt-buffer.
==============================================================================
CHANGED FEATURES *news-changed*

View File

@@ -355,6 +355,7 @@ Functions:
- |tempname()| tries to recover if the Nvim |tempdir| disappears.
- |writefile()| with "p" flag creates parent directories.
- |prompt_getinput()|
- |prompt_appendbuf()|
Highlight groups:
- |highlight-blend| controls blend level for a highlight group

View File

@@ -7575,6 +7575,31 @@ printf({fmt}, {expr1} ...) *printf()*
Return: ~
(`string`)
prompt_appendbuf({buf}, {text}) *prompt_appendbuf()*
Appends text to prompt buffer before current prompt. When {text} is
a |List|: Append each item of the |List| as a text line above
prompt-line in the buffer. Any type of item is accepted and converted
to a String. Returns 1 for failure ({buf} not a prmopt buffer),
0 for success. When {text} is an empty list zero is returned.
Example: >vim
func TextEntered(text)
call prompt_appendbuf(bufnr(''), split('Entered: "' . a:text . '"', '\n'))
endfunc
set buftype=prompt
call prompt_setcallback(bufnr(''), function("TextEntered"))
eval bufnr("")->prompt_setprompt("cmd: ")
startinsert
<
Parameters: ~
• {buf} (`integer|string`)
• {text} (`string|string[]`)
Return: ~
(`0|1`)
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.

View File

@@ -6893,6 +6893,28 @@ function vim.fn.prevnonblank(lnum) end
--- @return string
function vim.fn.printf(fmt, expr1) end
--- Appends text to prompt buffer before current prompt. When {text} is
--- a |List|: Append each item of the |List| as a text line above
--- prompt-line in the buffer. Any type of item is accepted and converted
--- to a String. Returns 1 for failure ({buf} not a prmopt buffer),
--- 0 for success. When {text} is an empty list zero is returned.
---
--- Example: >vim
--- func TextEntered(text)
--- call prompt_appendbuf(bufnr(''), split('Entered: "' . a:text . '"', '\n'))
--- endfunc
---
--- set buftype=prompt
--- call prompt_setcallback(bufnr(''), function("TextEntered"))
--- eval bufnr("")->prompt_setprompt("cmd: ")
--- startinsert
--- <
---
--- @param buf integer|string
--- @param text string|string[]
--- @return 0|1
function vim.fn.prompt_appendbuf(buf, text) end
--- Gets the current user-input in |prompt-buffer| {buf} without invoking
--- prompt_callback. {buf} can be a buffer name or number.
---

View File

@@ -2107,6 +2107,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
buf->b_prompt_text = NULL;
buf->b_prompt_start = (fmark_T)INIT_FMARK;
buf->b_prompt_start.mark.col = 2; // default prompt is "% "
buf->b_prompt_append_new_line = true;
return buf;
}

View File

@@ -715,6 +715,7 @@ struct file_buffer {
char *b_prompt_text; // set by prompt_setprompt()
Callback b_prompt_callback; // set by prompt_setcallback()
Callback b_prompt_interrupt; // set by prompt_setinterrupt()
bool b_prompt_append_new_line; // prompt_appendlines() should start a newline
int b_prompt_insert; // value for restart_edit when entering
// a prompt buffer window.
fmark_T b_prompt_start; // Start of the editable area of a prompt buffer.

View File

@@ -1594,8 +1594,12 @@ static void init_prompt(int cmdchar_todo)
int prompt_len = (int)strlen(prompt);
// In case the mark is set to a nonexistent line.
curbuf->b_prompt_start.mark.lnum = MAX(1, MIN(curbuf->b_prompt_start.mark.lnum,
curbuf->b_ml.ml_line_count));
if (curbuf->b_prompt_start.mark.lnum < 1
|| curbuf->b_prompt_start.mark.lnum > curbuf->b_ml.ml_line_count) {
curbuf->b_prompt_start.mark.lnum = MAX(1, MIN(curbuf->b_prompt_start.mark.lnum,
curbuf->b_ml.ml_line_count));
curbuf->b_prompt_append_new_line = true;
}
curwin->w_cursor.lnum = MAX(curwin->w_cursor.lnum, curbuf->b_prompt_start.mark.lnum);
char *text = ml_get(curbuf->b_prompt_start.mark.lnum);
@@ -1615,6 +1619,7 @@ static void init_prompt(int cmdchar_todo)
ml_append(lnum, prompt, 0, false);
appended_lines_mark(lnum, 1);
curbuf->b_prompt_start.mark.lnum = curbuf->b_ml.ml_line_count;
curbuf->b_prompt_append_new_line = true;
// Like submitting, undo history was relevant to the old prompt.
u_clearallandblockfree(curbuf);
}

View File

@@ -6725,6 +6725,7 @@ theend:
u_clearallandblockfree(curbuf);
curbuf->b_prompt_start.mark.lnum = curbuf->b_ml.ml_line_count;
curbuf->b_prompt_append_new_line = true;
}
/// @return true when the interrupt callback was invoked.

View File

@@ -8371,6 +8371,32 @@ M.funcs = {
signature = 'printf({fmt}, {expr1} ...)',
returns = 'string',
},
prompt_appendbuf = {
args = 2,
base = 2,
desc = [=[
Appends text to prompt buffer before current prompt. When {text} is
a |List|: Append each item of the |List| as a text line above
prompt-line in the buffer. Any type of item is accepted and converted
to a String. Returns 1 for failure ({buf} not a prmopt buffer),
0 for success. When {text} is an empty list zero is returned.
Example: >vim
func TextEntered(text)
call prompt_appendbuf(bufnr(''), split('Entered: "' . a:text . '"', '\n'))
endfunc
set buftype=prompt
call prompt_setcallback(bufnr(''), function("TextEntered"))
eval bufnr("")->prompt_setprompt("cmd: ")
startinsert
<
]=],
name = 'prompt_appendbuf',
params = { { 'buf', 'integer|string' }, { 'text', 'string|string[]' } },
returns = '0|1',
signature = 'prompt_appendbuf({buf}, {text})',
},
prompt_getinput = {
args = 1,
base = 1,

View File

@@ -274,6 +274,74 @@ void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
buf_set_append_line(argvars, rettv, true);
}
/// "prompt_appendbuf({buffer}, string/list)" function
void f_prompt_appendbuf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
FUNC_ATTR_NONNULL_ALL
{
const int did_emsg_before = did_emsg;
// Return an 1 by default, e.g. append failed or not a prompt buffer
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 1;
buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
if (buf == NULL || !bt_prompt(buf)) {
return;
}
linenr_T lnum = MAX(0, buf->b_prompt_start.mark.lnum - 1);
typval_T *lines = &argvars[1];
if (!buf->b_prompt_append_new_line) {
// Since we are not creating a new line we need to append input to current line
const char *text = (lnum > 0) ? (const char *)ml_get_buf(buf, lnum) : "";
if (lines->v_type == VAR_LIST) {
list_T *l = lines->vval.v_list;
if (l != NULL && tv_list_len(l) > 0) {
listitem_T *li = tv_list_first(l);
const char *str = tv_get_string(&li->li_tv);
char *new_str = concat_str(text, str);
tv_clear(&li->li_tv);
li->li_tv.v_type = VAR_STRING;
li->li_tv.vval.v_string = new_str;
}
} else if (lines->v_type == VAR_STRING) {
const char *str = tv_get_string(lines);
char *new_str = concat_str(text, str);
tv_clear(lines);
lines->v_type = VAR_STRING;
lines->vval.v_string = new_str;
}
}
if (did_emsg == did_emsg_before) {
set_buffer_lines(buf, lnum, buf->b_prompt_append_new_line, lines, rettv);
}
if (rettv->vval.v_number == 0) {
// Ok we've inserted the lines successfully now check if last string ended with '\n'
// to determine if we need to insert a new line before next append
buf->b_prompt_append_new_line = false;
if (lines->v_type == VAR_LIST) {
list_T *l = lines->vval.v_list;
if (l != NULL && tv_list_len(l) > 0) {
listitem_T *li = tv_list_last(l);
const char *str = tv_get_string(&li->li_tv);
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
buf->b_prompt_append_new_line = true;
}
}
} else if (lines->v_type == VAR_STRING) {
const char *str = tv_get_string(lines);
size_t len = strlen(str);
if (len > 0 && str[len - 1] == '\n') {
buf->b_prompt_append_new_line = true;
}
}
}
}
/// "bufadd(expr)" function
void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -778,8 +846,12 @@ void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// even while user is editing their input.
if (bt_prompt(buf) && buf->b_ml.ml_mfp != NULL) {
// In case the mark is set to a nonexistent line.
buf->b_prompt_start.mark.lnum = MAX(1, MIN(buf->b_prompt_start.mark.lnum,
buf->b_ml.ml_line_count));
if (buf->b_prompt_start.mark.lnum < 1
|| buf->b_prompt_start.mark.lnum > curbuf->b_ml.ml_line_count) {
buf->b_prompt_start.mark.lnum = MAX(1, MIN(buf->b_prompt_start.mark.lnum,
buf->b_ml.ml_line_count));
curbuf->b_prompt_append_new_line = true;
}
linenr_T prompt_lno = buf->b_prompt_start.mark.lnum;
char *old_prompt = buf_prompt_text(buf);

View File

@@ -31,14 +31,18 @@ describe('prompt buffer', function()
close
else
" Add the output above the current prompt.
call append(line("$") - 1, split('Command: "' . a:text . '"', '\n'))
call prompt_appendbuf(bufnr(''), split('Command: "' . a:text . '"', '\n'))
" Reset &modified to allow the buffer to be closed.
set nomodified
call timer_start(20, {id -> TimerFunc(a:text)})
endif
endfunc
func TimerFunc(text)
" Add the output above the current prompt.
call append(line("$") - 1, split('Result: "' . a:text .'"', '\n'))
call prompt_appendbuf(bufnr(''), split('Result: "' . a:text .'"', '\n'))
" Reset &modified to allow the buffer to be closed.
set nomodified
endfunc
func SwitchWindows()