mirror of
https://github.com/neovim/neovim.git
synced 2026-05-09 01:40:00 -04:00
feat(prompt): plugins can update prompt during user input #37743
Problem: Currently, if prompt gets changed during user-input with prompt_setprompt() it only gets reflected in next prompt. And that behavior is not also consistent. If user re-enters insert mode then the user input gets discarded and a new prompt gets created with the new prompt. Solution: Handle prompt_setprompt eagerly. Update the prompt display, preserve user input.
This commit is contained in:
+2
-1
@@ -2105,7 +2105,8 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
|
||||
buf->b_prompt_callback.type = kCallbackNone;
|
||||
buf->b_prompt_interrupt.type = kCallbackNone;
|
||||
buf->b_prompt_text = NULL;
|
||||
clear_fmark(&buf->b_prompt_start, 0);
|
||||
buf->b_prompt_start = (fmark_T)INIT_FMARK;
|
||||
buf->b_prompt_start.mark.col = 2; // default prompt is "% "
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "nvim/errors.h"
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/eval/encode.h"
|
||||
#include "nvim/eval/funcs.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/event/multiqueue.h"
|
||||
@@ -26,7 +25,6 @@
|
||||
#include "nvim/event/socket.h"
|
||||
#include "nvim/event/stream.h"
|
||||
#include "nvim/event/wstream.h"
|
||||
#include "nvim/ex_cmds.h"
|
||||
#include "nvim/garray.h"
|
||||
#include "nvim/gettext_defs.h"
|
||||
#include "nvim/globals.h"
|
||||
@@ -1009,65 +1007,3 @@ Array channel_all_info(Arena *arena)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// "prompt_setcallback({buffer}, {callback})" function
|
||||
void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
Callback prompt_callback = { .type = kCallbackNone };
|
||||
|
||||
if (check_secure()) {
|
||||
return;
|
||||
}
|
||||
buf_T *buf = tv_get_buf(&argvars[0], false);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
|
||||
if (!callback_from_typval(&prompt_callback, &argvars[1])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback_free(&buf->b_prompt_callback);
|
||||
buf->b_prompt_callback = prompt_callback;
|
||||
}
|
||||
|
||||
/// "prompt_setinterrupt({buffer}, {callback})" function
|
||||
void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
Callback interrupt_callback = { .type = kCallbackNone };
|
||||
|
||||
if (check_secure()) {
|
||||
return;
|
||||
}
|
||||
buf_T *buf = tv_get_buf(&argvars[0], false);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
|
||||
if (!callback_from_typval(&interrupt_callback, &argvars[1])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback_free(&buf->b_prompt_interrupt);
|
||||
buf->b_prompt_interrupt = interrupt_callback;
|
||||
}
|
||||
|
||||
/// "prompt_setprompt({buffer}, {text})" function
|
||||
void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
if (check_secure()) {
|
||||
return;
|
||||
}
|
||||
buf_T *buf = tv_get_buf(&argvars[0], false);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *text = tv_get_string(&argvars[1]);
|
||||
xfree(buf->b_prompt_text);
|
||||
buf->b_prompt_text = xstrdup(text);
|
||||
}
|
||||
|
||||
+4
-5
@@ -1596,15 +1596,14 @@ static void init_prompt(int cmdchar_todo)
|
||||
if (curwin->w_cursor.lnum < curbuf->b_prompt_start.mark.lnum) {
|
||||
curwin->w_cursor.lnum = curbuf->b_prompt_start.mark.lnum;
|
||||
}
|
||||
char *text = get_cursor_line_ptr();
|
||||
char *text = ml_get(curbuf->b_prompt_start.mark.lnum);
|
||||
if ((curbuf->b_prompt_start.mark.lnum == curwin->w_cursor.lnum
|
||||
&& (curbuf->b_prompt_start.mark.col < prompt_len
|
||||
|| strncmp(text + curbuf->b_prompt_start.mark.col - prompt_len, prompt,
|
||||
(size_t)prompt_len) != 0))
|
||||
|| curbuf->b_prompt_start.mark.lnum > curwin->w_cursor.lnum) {
|
||||
|| !strnequal(text + curbuf->b_prompt_start.mark.col - prompt_len, prompt,
|
||||
(size_t)prompt_len)))) {
|
||||
// prompt is missing, insert it or append a line with it
|
||||
if (*text == NUL) {
|
||||
ml_replace(curbuf->b_ml.ml_line_count, prompt, true);
|
||||
ml_replace(curbuf->b_prompt_start.mark.lnum, prompt, true);
|
||||
} else {
|
||||
ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false);
|
||||
curbuf->b_prompt_start.mark.lnum = curbuf->b_ml.ml_line_count;
|
||||
|
||||
@@ -6669,7 +6669,6 @@ void prompt_invoke_callback(void)
|
||||
curwin->w_cursor.lnum = lnum + 1;
|
||||
curwin->w_cursor.col = 0;
|
||||
curbuf->b_prompt_start.mark.lnum = lnum + 1;
|
||||
curbuf->b_prompt_start.mark.col = 0;
|
||||
|
||||
if (curbuf->b_prompt_callback.type == kCallbackNone) {
|
||||
xfree(user_input);
|
||||
|
||||
@@ -11,12 +11,15 @@
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/change.h"
|
||||
#include "nvim/cursor.h"
|
||||
#include "nvim/drawscreen.h"
|
||||
#include "nvim/edit.h"
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/eval/buffer.h"
|
||||
#include "nvim/eval/funcs.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/eval/typval_defs.h"
|
||||
#include "nvim/eval/window.h"
|
||||
#include "nvim/ex_cmds.h"
|
||||
#include "nvim/globals.h"
|
||||
#include "nvim/macros_defs.h"
|
||||
#include "nvim/memline.h"
|
||||
@@ -25,6 +28,7 @@
|
||||
#include "nvim/path.h"
|
||||
#include "nvim/pos_defs.h"
|
||||
#include "nvim/sign.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/vim_defs.h"
|
||||
@@ -704,3 +708,109 @@ void restore_buffer(bufref_T *save_curbuf)
|
||||
curbuf->b_nwindows++;
|
||||
}
|
||||
}
|
||||
|
||||
/// "prompt_setcallback({buffer}, {callback})" function
|
||||
void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
Callback prompt_callback = { .type = kCallbackNone };
|
||||
|
||||
if (check_secure()) {
|
||||
return;
|
||||
}
|
||||
buf_T *buf = tv_get_buf(&argvars[0], false);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
|
||||
if (!callback_from_typval(&prompt_callback, &argvars[1])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback_free(&buf->b_prompt_callback);
|
||||
buf->b_prompt_callback = prompt_callback;
|
||||
}
|
||||
|
||||
/// "prompt_setinterrupt({buffer}, {callback})" function
|
||||
void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
Callback interrupt_callback = { .type = kCallbackNone };
|
||||
|
||||
if (check_secure()) {
|
||||
return;
|
||||
}
|
||||
buf_T *buf = tv_get_buf(&argvars[0], false);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
|
||||
if (!callback_from_typval(&interrupt_callback, &argvars[1])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback_free(&buf->b_prompt_interrupt);
|
||||
buf->b_prompt_interrupt = interrupt_callback;
|
||||
}
|
||||
|
||||
/// "prompt_setprompt({buffer}, {text})" function
|
||||
void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
if (check_secure()) {
|
||||
return;
|
||||
}
|
||||
buf_T *buf = tv_get_buf(&argvars[0], false);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *new_prompt = tv_get_string(&argvars[1]);
|
||||
int new_prompt_len = (int)strlen(new_prompt);
|
||||
|
||||
// Update the prompt-text and prompt-marks if a plugin calls prompt_setprompt()
|
||||
// even while user is editing their input.
|
||||
if (bt_prompt(buf)) {
|
||||
if (buf->b_prompt_start.mark.lnum > buf->b_ml.ml_line_count) {
|
||||
// In case the mark is set to a nonexistent line.
|
||||
buf->b_prompt_start.mark.lnum = buf->b_ml.ml_line_count;
|
||||
}
|
||||
|
||||
linenr_T prompt_lno = buf->b_prompt_start.mark.lnum;
|
||||
char *old_prompt = buf_prompt_text(buf);
|
||||
char *old_line = ml_get_buf(buf, prompt_lno);
|
||||
old_line = old_line != NULL ? old_line : "";
|
||||
|
||||
int old_prompt_len = (int)strlen(old_prompt);
|
||||
colnr_T cursor_col = curwin->w_cursor.col;
|
||||
|
||||
if (buf->b_prompt_start.mark.col < old_prompt_len
|
||||
|| curbuf->b_prompt_start.mark.col < old_prompt_len
|
||||
|| !strnequal(old_prompt, old_line + curbuf->b_prompt_start.mark.col - old_prompt_len,
|
||||
(size_t)old_prompt_len)) {
|
||||
// If for some odd reason the old prompt is missing,
|
||||
// replace prompt line with new-prompt (discards user-input).
|
||||
ml_replace_buf(buf, prompt_lno, (char *)new_prompt, true, false);
|
||||
cursor_col = new_prompt_len;
|
||||
} else {
|
||||
// Replace prev-prompt + user-input with new-prompt + user-input
|
||||
char *new_line = concat_str(new_prompt, old_line + buf->b_prompt_start.mark.col);
|
||||
if (ml_replace_buf(buf, prompt_lno, new_line, false, false) != OK) {
|
||||
xfree(new_line);
|
||||
}
|
||||
cursor_col += new_prompt_len - old_prompt_len;
|
||||
}
|
||||
|
||||
if (curwin->w_buffer == buf) {
|
||||
coladvance(curwin, cursor_col);
|
||||
}
|
||||
changed_lines_redraw_buf(buf, prompt_lno, prompt_lno + 1, 0);
|
||||
redraw_buf_later(buf, UPD_INVERTED);
|
||||
}
|
||||
|
||||
// Clear old prompt text and replace with the new one
|
||||
xfree(buf->b_prompt_text);
|
||||
buf->b_prompt_text = xstrdup(new_prompt);
|
||||
buf->b_prompt_start.mark.col = (colnr_T)new_prompt_len;
|
||||
}
|
||||
|
||||
@@ -711,7 +711,8 @@ const char *did_set_buftype(optset_T *args)
|
||||
// Set default value for 'comments'
|
||||
set_option_direct(kOptComments, STATIC_CSTR_AS_OPTVAL(""), OPT_LOCAL, SID_NONE);
|
||||
// set the prompt start position to lastline.
|
||||
pos_T next_prompt = { .lnum = buf->b_ml.ml_line_count, .col = 0, .coladd = 0 };
|
||||
pos_T next_prompt = { .lnum = buf->b_ml.ml_line_count, .col = buf->b_prompt_start.mark.col,
|
||||
.coladd = 0 };
|
||||
RESET_FMARK(&buf->b_prompt_start, next_prompt, 0, ((fmarkv_T)INIT_FMARKV));
|
||||
}
|
||||
if (win->w_status_height || global_stl_height()) {
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('prompt buffer', function()
|
||||
screen:expect([[
|
||||
cmd: ^ |
|
||||
{1:~ }|*3
|
||||
{3:[Prompt] [+] }|
|
||||
{3:[Prompt] }|
|
||||
other buffer |
|
||||
{1:~ }|*3
|
||||
{5:-- INSERT --} |
|
||||
@@ -149,7 +149,7 @@ describe('prompt buffer', function()
|
||||
screen:expect([[
|
||||
cmd: |
|
||||
{1:~ }|*3
|
||||
{2:[Prompt] [+] }|
|
||||
{2:[Prompt] }|
|
||||
^other buffer |
|
||||
{1:~ }|*3
|
||||
|
|
||||
@@ -158,7 +158,7 @@ describe('prompt buffer', function()
|
||||
screen:expect([[
|
||||
cmd: ^ |
|
||||
{1:~ }|*3
|
||||
{3:[Prompt] [+] }|
|
||||
{3:[Prompt] }|
|
||||
other buffer |
|
||||
{1:~ }|*3
|
||||
{5:-- INSERT --} |
|
||||
@@ -167,7 +167,7 @@ describe('prompt buffer', function()
|
||||
screen:expect([[
|
||||
cmd:^ |
|
||||
{1:~ }|*3
|
||||
{3:[Prompt] [+] }|
|
||||
{3:[Prompt] }|
|
||||
other buffer |
|
||||
{1:~ }|*3
|
||||
|
|
||||
@@ -290,8 +290,8 @@ describe('prompt buffer', function()
|
||||
source([[
|
||||
bwipeout!
|
||||
set formatoptions+=r
|
||||
set buftype=prompt
|
||||
call prompt_setprompt(bufnr(), "% ")
|
||||
set buftype=prompt
|
||||
]])
|
||||
feed('iline1<s-cr>line2')
|
||||
screen:expect([[
|
||||
@@ -793,4 +793,58 @@ describe('prompt buffer', function()
|
||||
|
||||
eq({ 2, 0 }, api.nvim_buf_get_mark(0, ':'))
|
||||
end)
|
||||
|
||||
it('prompt can be changed without interrupting user input', function()
|
||||
api.nvim_set_option_value('buftype', 'prompt', { buf = 0 })
|
||||
local buf = api.nvim_get_current_buf()
|
||||
|
||||
local function set_prompt(prompt)
|
||||
fn('prompt_setprompt', buf, prompt)
|
||||
end
|
||||
|
||||
set_prompt('> ')
|
||||
|
||||
source('startinsert')
|
||||
|
||||
feed('user input')
|
||||
-- Move the cursor a bit to check cursor maintaining position
|
||||
feed('<esc>hhi')
|
||||
|
||||
screen:expect([[
|
||||
> user in^put |
|
||||
{1:~ }|*8
|
||||
{5:-- INSERT --} |
|
||||
]])
|
||||
|
||||
eq({ 1, 2 }, api.nvim_buf_get_mark(0, ':'))
|
||||
|
||||
set_prompt('new-prompt > ')
|
||||
|
||||
screen:expect([[
|
||||
new-prompt > user in^put |
|
||||
{1:~ }|*8
|
||||
{5:-- INSERT --} |
|
||||
]])
|
||||
|
||||
eq({ 1, 13 }, api.nvim_buf_get_mark(0, ':'))
|
||||
|
||||
set_prompt('new-prompt(status) > ')
|
||||
|
||||
screen:expect([[
|
||||
new-prompt(status) > user|
|
||||
in^put |
|
||||
{1:~ }|*7
|
||||
{5:-- INSERT --} |
|
||||
]])
|
||||
eq({ 1, 21 }, api.nvim_buf_get_mark(0, ':'))
|
||||
|
||||
set_prompt('new-prompt > ')
|
||||
|
||||
screen:expect([[
|
||||
new-prompt > user in^put |
|
||||
{1:~ }|*8
|
||||
{5:-- INSERT --} |
|
||||
]])
|
||||
eq({ 1, 13 }, api.nvim_buf_get_mark(0, ':'))
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user