Merge #10433 from erw7/vim-8.1.0027

vim-patch:8.1.{27,32,36,69,70,71,91,92}
This commit is contained in:
Justin M. Keyes 2020-02-12 00:24:50 -08:00 committed by GitHub
commit 68de6b17b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 816 additions and 74 deletions

View File

@ -2084,6 +2084,7 @@ ctxsize() Number return |context-stack| size
cursor({lnum}, {col} [, {off}])
Number move cursor to {lnum}, {col}, {off}
cursor({list}) Number move cursor to position in {list}
debugbreak({pid}) Number interrupt process being debugged
deepcopy({expr} [, {noref}]) any make a full copy of {expr}
delete({fname} [, {flags}]) Number delete the file or directory {fname}
deletebufline({expr}, {first}[, {last}])
@ -2280,6 +2281,10 @@ pathshorten({expr}) String shorten directory names in a path
pow({x}, {y}) Float {x} to the power of {y}
prevnonblank({lnum}) Number line nr of non-blank line <= {lnum}
printf({fmt}, {expr1}...) String format text
prompt_addtext({buf}, {expr}) none add text to a prompt buffer
prompt_setcallback({buf}, {expr}) none set prompt callback function
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
prompt_setprompt({buf}, {text}) none set prompt text
pum_getpos() Dict position and size of pum if visible
pumvisible() Number whether popup menu is visible
pyeval({expr}) any evaluate |Python| expression
@ -2290,7 +2295,7 @@ range({expr} [, {max} [, {stride}]])
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {binary} [, {max}]])
List get list of lines from file {fname}
reg_executing() Number get the executing register name
reg_executing() String get the executing register name
reg_recording() String get the recording register name
reltime([{start} [, {end}]]) List get time value
reltimefloat({time}) Float turn the time value into a Float
@ -3637,6 +3642,11 @@ exp({expr}) *exp()*
:echo exp(-1)
< 0.367879
debugbreak({pid}) *debugbreak()*
Specifically used to interrupt a program being debugged. It
will cause process {pid} to get a SIGTRAP. Behavior for other
processes is undefined. See |terminal-debugger|.
{Sends a SIGINT to a process {pid} other than MS-Windows}
expand({expr} [, {nosuf} [, {list}]]) *expand()*
Expand wildcards and the following special keywords in {expr}.
@ -4541,7 +4551,7 @@ getline({lnum} [, {end}])
from the current buffer. Example: >
getline(1)
< When {lnum} is a String that doesn't start with a
digit, line() is called to translate the String into a Number.
digit, |line()| is called to translate the String into a Number.
To get the line under the cursor: >
getline(".")
< When {lnum} is smaller than 1 or bigger than the number of
@ -6541,6 +6551,50 @@ printf({fmt}, {expr1} ...) *printf()*
of "%" items. If there are not sufficient or too many
arguments an error is given. Up to 18 arguments can be used.
prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
Set prompt callback for buffer {buf} to {expr}. When {expr}
is an empty string the callback is removed. This has only
effect if {buf} has 'buftype' set to "prompt".
The callback is invoked when pressing Enter. The current
buffer will always be the prompt buffer. A new line for a
prompt is added before invoking the callback, thus the prompt
for which the callback was invoked will be in the last but one
line.
If the callback wants to add text to the buffer, it must
insert it above the last line, since that is where the current
prompt is. This can also be done asynchronously.
The callback is invoked with one argument, which is the text
that was entered at the prompt. This can be an empty string
if the user only typed Enter.
Example: >
call prompt_setcallback(bufnr(''), function('s:TextEntered'))
func s:TextEntered(text)
if a:text == 'exit' || a:text == 'quit'
stopinsert
close
else
call append(line('$') - 1, 'Entered: "' . a:text . '"')
" Reset 'modified' to allow the buffer to be closed.
set nomodified
endif
endfunc
prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()*
Set a callback for buffer {buf} to {expr}. When {expr} is an
empty string the callback is removed. This has only effect if
{buf} has 'buftype' set to "prompt".
This callback will be invoked when pressing CTRL-C in Insert
mode. Without setting a callback Vim will exit Insert mode,
as in any buffer.
prompt_setprompt({buf}, {text}) *prompt_setprompt()*
Set prompt for buffer {buf} to {text}. You most likely want
{text} to end in a space.
The result is only visible if {buf} has 'buftype' set to
"prompt". Example: >
call prompt_setprompt(bufnr(''), 'command: ')
pum_getpos() *pum_getpos()*
If the popup menu (see |ins-completion-menu|) is not visible,

View File

@ -307,6 +307,23 @@ Other commands ~
isn't one
Prompt mode ~
*termdebug-prompt*
When on MS-Windows, gdb will run in a buffer with 'buftype' set to "prompt".
This works slightly differently:
- The gdb window will be in Insert mode while typing commands. Go to Normal
mode with <Esc>, then you can move around in the buffer, copy/paste, etc.
Go back to editing the gdb command with any command that starts Insert mode,
such as `a` or `i`.
- The program being debugged will run in a separate window. On MS-Windows
this is a new console window. On Unix, if the |+terminal| feature is
available a Terminal window will be opened to run the debugged program in.
*termdebug_use_prompt*
Prompt mode can be used even when the |+terminal| feature is present with: >
let g:termdebug_use_prompt = 1
Communication ~
*termdebug-communication*
There is another, hidden, buffer, which is used for Vim to communicate with

View File

@ -1093,6 +1093,8 @@ A jump table for the options with a short description can be found at |Q_op|.
nowrite buffer will not be written
quickfix list of errors |:cwindow| or locations |:lwindow|
terminal |terminal-emulator| buffer
prompt buffer where only the last line can be edited, meant
to be used by a plugin, see |prompt-buffer|
This option is used together with 'bufhidden' and 'swapfile' to
specify special kinds of buffers. See |special-buffers|.

View File

@ -37,7 +37,7 @@
" For neovim compatibility, the vim specific calls were replaced with neovim
" specific calls:
" term_start -> term_open
" term_sendkeys -> jobsend
" term_sendkeys -> chansend
" term_getline -> getbufline
" job_info && term_getjob -> using linux command ps to get the tty
" balloon -> nvim floating window
@ -47,8 +47,6 @@
" https://github.com/autozimu/LanguageClient-neovim/blob/0ed9b69dca49c415390a8317b19149f97ae093fa/autoload/LanguageClient.vim#L304
"
" Neovim terminal also works seamlessly on windows, which is why the ability
" to use the prompt buffer was removed.
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
@ -57,6 +55,12 @@ if exists(':Termdebug')
finish
endif
" The terminal feature does not work with gdb on win32.
if !has('win32')
let s:way = 'terminal'
else
let s:way = 'prompt'
endif
let s:keepcpo = &cpo
set cpo&vim
@ -138,7 +142,19 @@ func s:StartDebug_internal(dict)
let s:vertical = 0
endif
call s:StartDebug_term(a:dict)
" Override using a terminal window by setting g:termdebug_use_prompt to 1.
let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt
if !has('win32') && !use_prompt
let s:way = 'terminal'
else
let s:way = 'prompt'
endif
if s:way == 'prompt'
call s:StartDebug_prompt(a:dict)
else
call s:StartDebug_term(a:dict)
endif
endfunc
" Use when debugger didn't start or ended.
@ -214,11 +230,11 @@ func s:StartDebug_term(dict)
" Set arguments to be run
if len(proc_args)
call jobsend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
endif
" Connect gdb to the communication pty, using the GDB/MI interface
call jobsend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r")
call chansend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r")
" Wait for the response to show up, users may not notice the error and wonder
" why the debugger doesn't work.
@ -275,6 +291,100 @@ func s:StartDebug_term(dict)
call s:StartDebugCommon(a:dict)
endfunc
func s:StartDebug_prompt(dict)
" Open a window with a prompt buffer to run gdb in.
if s:vertical
vertical new
else
new
endif
let s:gdbwin = win_getid(winnr())
let s:promptbuf = bufnr('')
call prompt_setprompt(s:promptbuf, 'gdb> ')
set buftype=prompt
file gdb
call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
if s:vertical
" Assuming the source code window will get a signcolumn, use two more
" columns for that, thus one less for the terminal window.
exe (&columns / 2 - 1) . "wincmd |"
endif
" Add -quiet to avoid the intro message causing a hit-enter prompt.
let gdb_args = get(a:dict, 'gdb_args', [])
let proc_args = get(a:dict, 'proc_args', [])
let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
"call ch_log('executing "' . join(cmd) . '"')
let s:gdbjob = jobstart(cmd, {
\ 'on_exit': function('s:EndPromptDebug'),
\ 'on_stdout': function('s:GdbOutCallback'),
\ })
if s:gdbjob == 0
echoerr 'invalid argument (or job table is full) while starting gdb job'
exe 'bwipe! ' . s:ptybuf
return
elseif s:gdbjob == -1
echoerr 'Failed to start the gdb job'
call s:CloseBuffers()
return
endif
" Interpret commands while the target is running. This should usualy only
" be exec-interrupt, since many commands don't work properly while the
" target is running.
call s:SendCommand('-gdb-set mi-async on')
" Older gdb uses a different command.
call s:SendCommand('-gdb-set target-async on')
let s:ptybuf = 0
if has('win32')
" MS-Windows: run in a new console window for maximum compatibility
call s:SendCommand('set new-console on')
else
" Unix: Run the debugged program in a terminal window. Open it below the
" gdb window.
execute 'new'
wincmd x | wincmd j
belowright let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
if s:pty_job_id == 0
echoerr 'invalid argument (or job table is full) while opening terminal window'
return
elseif s:pty_job_id == -1
echoerr 'Failed to open the program terminal window'
return
endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty']
let s:ptywin = win_getid(winnr())
call s:SendCommand('tty ' . pty)
" Since GDB runs in a prompt window, the environment has not been set to
" match a terminal window, need to do that now.
call s:SendCommand('set env TERM = xterm-color')
call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
call s:SendCommand('set env COLORS = ' . &t_Co)
call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
endif
call s:SendCommand('set print pretty on')
call s:SendCommand('set breakpoint pending on')
" Disable pagination, it causes everything to stop at the gdb
call s:SendCommand('set pagination off')
" Set arguments to be run
if len(proc_args)
call s:SendCommand('set args ' . join(proc_args))
endif
call s:StartDebugCommon(a:dict)
startinsert
endfunc
func s:StartDebugCommon(dict)
" Sign used to highlight the line where the program has stopped.
@ -316,23 +426,99 @@ endfunc
" Send a command to gdb. "cmd" is the string without line terminator.
func s:SendCommand(cmd)
"call ch_log('sending to gdb: ' . a:cmd)
call jobsend(s:comm_job_id, a:cmd . "\r")
if s:way == 'prompt'
call chansend(s:gdbjob, a:cmd . "\n")
else
call chansend(s:comm_job_id, a:cmd . "\r")
endif
endfunc
" This is global so that a user can create their mappings with this.
func TermDebugSendCommand(cmd)
let do_continue = 0
if !s:stopped
let do_continue = 1
call s:SendCommand('-exec-interrupt')
sleep 10m
if s:way == 'prompt'
call chansend(s:gdbjob, a:cmd . "\n")
else
let do_continue = 0
if !s:stopped
let do_continue = 1
if s:way == 'prompt'
" Need to send a signal to get the UI to listen. Strangely this is only
" needed once.
call jobstop(s:gdbjob)
else
call s:SendCommand('-exec-interrupt')
endif
sleep 10m
endif
call chansend(s:gdb_job_id, a:cmd . "\r")
if do_continue
Continue
endif
endif
call jobsend(s:gdb_job_id, a:cmd . "\r")
if do_continue
Continue
endfunc
" Function called when entering a line in the prompt buffer.
func s:PromptCallback(text)
call s:SendCommand(a:text)
endfunc
" Function called when pressing CTRL-C in the prompt buffer and when placing a
" breakpoint.
func s:PromptInterrupt()
if s:pid == 0
echoerr 'Cannot interrupt gdb, did not find a process ID'
else
"call ch_log('Interrupting gdb')
" Using job_stop(s:gdbjob, 'int') does not work.
call debugbreak(s:pid)
endif
endfunc
" Function called when gdb outputs text.
func s:GdbOutCallback(job_id, msgs, event)
"call ch_log('received from gdb: ' . a:text)
" Drop the gdb prompt, we have our own.
" Drop status and echo'd commands.
call filter(a:msgs, { index, val ->
\ val !=# '(gdb)' && val !=# '^done' && val[0] !=# '&'})
let lines = []
let index = 0
for msg in a:msgs
if msg =~ '^^error,msg='
if exists('s:evalexpr')
\ && s:DecodeMessage(msg[11:])
\ =~ 'A syntax error in expression, near\|No symbol .* in current context'
" Silently drop evaluation errors.
call remove(a:msgs, index)
unlet s:evalexpr
continue
endif
elseif msg[0] == '~'
call add(lines, s:DecodeMessage(msg[1:]))
call remove(a:msgs, index)
continue
endif
let index += 1
endfor
let curwinid = win_getid(winnr())
call win_gotoid(s:gdbwin)
" Add the output above the current prompt.
for line in lines
call append(line('$') - 1, line)
endfor
if !empty(lines)
set modified
endif
call win_gotoid(curwinid)
call s:CommOutput(a:job_id, a:msgs, a:event)
endfunc
" Decode a message from gdb. quotedText starts with a ", return the text up
" to the next ", unescaping characters.
func s:DecodeMessage(quotedText)
@ -396,6 +582,19 @@ func s:EndDebugCommon()
au! TermDebug
endfunc
func s:EndPromptDebug(job_id, exit_code, event)
let curwinid = win_getid(winnr())
call win_gotoid(s:gdbwin)
close
if curwinid != s:gdbwin
call win_gotoid(curwinid)
endif
call s:EndDebugCommon()
unlet s:gdbwin
"call ch_log("Returning from EndPromptDebug()")
endfunc
func s:CommOutput(job_id, msgs, event)
for msg in a:msgs
@ -436,7 +635,11 @@ func s:InstallCommands()
command Stop call s:SendCommand('-exec-interrupt')
" using -exec-continue results in CTRL-C in gdb window not working
command Continue call jobsend(s:gdb_job_id, "continue\r")
if s:way == 'prompt'
command Continue call s:SendCommand('continue')
else
command Continue call chansend(s:gdb_job_id, "continue\r")
endif
command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
command Gdb call win_gotoid(s:gdbwin)
@ -494,7 +697,11 @@ func s:SetBreakpoint()
let do_continue = 0
if !s:stopped
let do_continue = 1
call s:SendCommand('-exec-interrupt')
if s:way == 'prompt'
call s:PromptInterrupt()
else
call s:SendCommand('-exec-interrupt')
endif
sleep 10m
endif
" Use the fname:lnum format, older gdb can't handle --source.

View File

@ -763,6 +763,9 @@ static void free_buffer(buf_T *buf)
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
tv_dict_unref(buf->additional_data);
xfree(buf->b_prompt_text);
callback_free(&buf->b_prompt_callback);
callback_free(&buf->b_prompt_interrupt);
clear_fmark(&buf->b_last_cursor);
clear_fmark(&buf->b_last_insert);
clear_fmark(&buf->b_last_change);
@ -1876,6 +1879,10 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
}
}
buf->b_prompt_callback.type = kCallbackNone;
buf->b_prompt_interrupt.type = kCallbackNone;
buf->b_prompt_text = NULL;
return buf;
}
@ -4824,6 +4831,12 @@ do_arg_all(
xfree(opened);
}
// Return TRUE if "buf" is a prompt buffer.
int bt_prompt(buf_T *buf)
{
return buf != NULL && buf->b_p_bt[0] == 'p';
}
/*
* Open a window for a number of buffers.
*/
@ -5218,14 +5231,18 @@ bool bt_nofile(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
|| buf->b_p_bt[0] == 'a' || buf->terminal);
|| buf->b_p_bt[0] == 'a'
|| buf->terminal
|| buf->b_p_bt[0] == 'p');
}
// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer.
bool bt_dontwrite(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal);
return buf != NULL && (buf->b_p_bt[0] == 'n'
|| buf->terminal
|| buf->b_p_bt[0] == 'p');
}
bool bt_dontwrite_msg(const buf_T *const buf)
@ -5278,6 +5295,9 @@ char_u *buf_spname(buf_T *buf)
if (buf->b_fname != NULL) {
return buf->b_fname;
}
if (bt_prompt(buf)) {
return (char_u *)_("[Prompt]");
}
return (char_u *)_("[Scratch]");
}
if (buf->b_fname == NULL) {

View File

@ -791,6 +791,12 @@ struct file_buffer {
// are not used! Use the B_SPELL macro to
// access b_spell without #ifdef.
char_u *b_prompt_text; // set by prompt_setprompt()
Callback b_prompt_callback; // set by prompt_setcallback()
Callback b_prompt_interrupt; // set by prompt_setinterrupt()
int b_prompt_insert; // value for restart_edit when entering
// a prompt buffer window.
synblock_T b_s; // Info related to syntax highlighting. w_s
// normally points to this, but some windows
// may use a different synblock_T.

View File

@ -2432,6 +2432,10 @@ void nv_diffgetput(bool put, size_t count)
exarg_T ea;
char buf[30];
if (bt_prompt(curbuf)) {
vim_beep(BO_OPER);
return;
}
if (count == 0) {
ea.arg = (char_u *)"";
} else {

View File

@ -574,6 +574,12 @@ static int insert_check(VimState *state)
foldCheckClose();
}
int cmdchar_todo = s->cmdchar;
if (bt_prompt(curbuf)) {
init_prompt(cmdchar_todo);
cmdchar_todo = NUL;
}
// If we inserted a character at the last position of the last line in the
// window, scroll the window one line up. This avoids an extra redraw. This
// is detected when the cursor column is smaller after inserting something.
@ -817,6 +823,16 @@ static int insert_handle_key(InsertState *s)
s->nomove = true;
return 0; // exit insert mode
}
if (s->c == Ctrl_C && bt_prompt(curbuf)) {
if (invoke_prompt_interrupt()) {
if (!bt_prompt(curbuf)) {
// buffer changed to a non-prompt buffer, get out of
// Insert mode
return 0;
}
break;
}
}
// when 'insertmode' set, and not halfway through a mapping, don't leave
// Insert mode
@ -1143,6 +1159,15 @@ check_pum:
cmdwin_result = CAR;
return 0;
}
if (bt_prompt(curbuf)) {
invoke_prompt_callback();
if (!bt_prompt(curbuf)) {
// buffer changed to a non-prompt buffer, get out of
// Insert mode
return 0;
}
break;
}
if (!ins_eol(s->c) && !p_im) {
return 0; // out of memory
}
@ -1569,6 +1594,52 @@ void edit_putchar(int c, int highlight)
}
}
// Return the effective prompt for the current buffer.
char_u *prompt_text(void)
{
if (curbuf->b_prompt_text == NULL) {
return (char_u *)"% ";
}
return curbuf->b_prompt_text;
}
// Prepare for prompt mode: Make sure the last line has the prompt text.
// Move the cursor to this line.
static void init_prompt(int cmdchar_todo)
{
char_u *prompt = prompt_text();
char_u *text;
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
text = get_cursor_line_ptr();
if (STRNCMP(text, prompt, STRLEN(prompt)) != 0) {
// prompt is missing, insert it or append a line with it
if (*text == NUL) {
ml_replace(curbuf->b_ml.ml_line_count, prompt, true);
} else {
ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false);
}
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
coladvance((colnr_T)MAXCOL);
changed_bytes(curbuf->b_ml.ml_line_count, 0);
}
if (cmdchar_todo == 'A') {
coladvance((colnr_T)MAXCOL);
}
if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) {
curwin->w_cursor.col = STRLEN(prompt);
}
// Make sure the cursor is in a valid position.
check_cursor();
}
// Return TRUE if the cursor is in the editable position of the prompt line.
int prompt_curpos_editable(void)
{
return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count
&& curwin->w_cursor.col >= (int)STRLEN(prompt_text());
}
/*
* Undo the previous edit_putchar().
*/
@ -8161,10 +8232,14 @@ static void ins_mouse(int c)
win_T *new_curwin = curwin;
if (curwin != old_curwin && win_valid(old_curwin)) {
/* Mouse took us to another window. We need to go back to the
* previous one to stop insert there properly. */
// Mouse took us to another window. We need to go back to the
// previous one to stop insert there properly.
curwin = old_curwin;
curbuf = curwin->w_buffer;
if (bt_prompt(curbuf)) {
// Restart Insert mode when re-entering the prompt buffer.
curbuf->b_prompt_insert = 'A';
}
}
start_arrow(curwin == old_curwin ? &tpos : NULL);
if (curwin != new_curwin && win_valid(new_curwin)) {

View File

@ -5152,6 +5152,10 @@ bool garbage_collect(bool testing)
}
// buffer ShaDa additional data
ABORTING(set_ref_dict)(buf->additional_data, copyID);
// buffer callback functions
set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL);
set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL);
}
FOR_ALL_TAB_WINDOWS(tp, wp) {
@ -7315,9 +7319,7 @@ dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
return dict;
}
/*
* Find window specified by "vp" in tabpage "tp".
*/
// Find window specified by "vp" in tabpage "tp".
win_T *
find_win_by_nr(
typval_T *vp,
@ -13687,3 +13689,51 @@ void ex_checkhealth(exarg_T *eap)
xfree(buf);
}
void invoke_prompt_callback(void)
{
typval_T rettv;
typval_T argv[2];
char_u *text;
char_u *prompt;
linenr_T lnum = 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, (char_u *)"", 0, false);
curwin->w_cursor.lnum = lnum + 1;
curwin->w_cursor.col = 0;
if (curbuf->b_prompt_callback.type == kCallbackNone) {
return;
}
text = ml_get(lnum);
prompt = prompt_text();
if (STRLEN(text) >= STRLEN(prompt)) {
text += STRLEN(prompt);
}
argv[0].v_type = VAR_STRING;
argv[0].vval.v_string = vim_strsave(text);
argv[1].v_type = VAR_UNKNOWN;
callback_call(&curbuf->b_prompt_callback, 1, argv, &rettv);
tv_clear(&argv[0]);
tv_clear(&rettv);
}
// Return true When the interrupt callback was invoked.
bool invoke_prompt_interrupt(void)
{
typval_T rettv;
typval_T argv[1];
if (curbuf->b_prompt_interrupt.type == kCallbackNone) {
return false;
}
argv[0].v_type = VAR_UNKNOWN;
got_int = false; // don't skip executing commands
callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv);
tv_clear(&rettv);
return true;
}

View File

@ -82,6 +82,7 @@ return {
ctxset={args={1, 2}},
ctxsize={},
cursor={args={1, 3}},
debugbreak={args={1, 1}},
deepcopy={args={1, 2}},
delete={args={1,2}},
deletebufline={args={2,3}},
@ -243,6 +244,9 @@ return {
pow={args=2},
prevnonblank={args=1},
printf={args=varargs(1)},
prompt_setcallback={args={2, 2}},
prompt_setinterrupt={args={2, 2}},
prompt_setprompt={args={2, 2}},
pum_getpos={},
pumvisible={},
py3eval={args=1},

View File

@ -1408,9 +1408,31 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 0;
}
/*
* "deepcopy()" function
*/
// "debugbreak()" function
static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int pid;
rettv->vval.v_number = FAIL;
pid = (int)tv_get_number(&argvars[0]);
if (pid == 0) {
EMSG(_(e_invarg));
} else {
#ifdef WIN32
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (hProcess != NULL) {
DebugBreakProcess(hProcess);
CloseHandle(hProcess);
rettv->vval.v_number = OK;
}
#else
uv_kill(pid, SIGINT);
#endif
}
}
// "deepcopy()" function
static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int noref = 0;
@ -6076,6 +6098,76 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
// "prompt_setcallback({buffer}, {callback})" function
static void f_prompt_setcallback(typval_T *argvars,
typval_T *rettv, FunPtr fptr)
{
buf_T *buf;
Callback prompt_callback = { .type = kCallbackNone };
if (check_secure()) {
return;
}
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
static void f_prompt_setinterrupt(typval_T *argvars,
typval_T *rettv, FunPtr fptr)
{
buf_T *buf;
Callback interrupt_callback = { .type = kCallbackNone };
if (check_secure()) {
return;
}
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
static void f_prompt_setprompt(typval_T *argvars,
typval_T *rettv, FunPtr fptr)
{
buf_T *buf;
const char_u *text;
if (check_secure()) {
return;
}
buf = tv_get_buf(&argvars[0], false);
if (buf == NULL) {
return;
}
text = (const char_u *)tv_get_string(&argvars[1]);
xfree(buf->b_prompt_text);
buf->b_prompt_text = vim_strsave(text);
}
// "pum_getpos()" function
static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{

View File

@ -3642,7 +3642,9 @@ static void nv_help(cmdarg_T *cap)
*/
static void nv_addsub(cmdarg_T *cap)
{
if (!VIsual_active && cap->oap->op_type == OP_NOP) {
if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
clearopbeep(cap->oap);
} else if (!VIsual_active && cap->oap->op_type == OP_NOP) {
prep_redo_cmd(cap);
cap->oap->op_type = cap->cmdchar == Ctrl_A ? OP_NR_ADD : OP_NR_SUB;
op_addsub(cap->oap, cap->count1, cap->arg);
@ -5239,6 +5241,13 @@ static void nv_down(cmdarg_T *cap)
// In the cmdline window a <CR> executes the command.
if (cmdwin_type != 0 && cap->cmdchar == CAR) {
cmdwin_result = CAR;
} 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();
if (restart_edit == 0) {
restart_edit = 'a';
}
} else {
cap->oap->motion_type = kMTLineWise;
if (cursor_down(cap->count1, cap->oap->op_type == OP_NOP) == false) {
@ -5831,6 +5840,10 @@ static void nv_undo(cmdarg_T *cap)
static void nv_kundo(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
if (bt_prompt(curbuf)) {
clearopbeep(cap->oap);
return;
}
u_undo((int)cap->count1);
curwin->w_set_curswant = true;
}
@ -5844,8 +5857,13 @@ static void nv_replace(cmdarg_T *cap)
char_u *ptr;
int had_ctrl_v;
if (checkclearop(cap->oap))
if (checkclearop(cap->oap)) {
return;
}
if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
clearopbeep(cap->oap);
return;
}
/* get another character */
if (cap->nchar == Ctrl_V) {
@ -6182,7 +6200,11 @@ static void v_visop(cmdarg_T *cap)
*/
static void nv_subst(cmdarg_T *cap)
{
if (VIsual_active) { /* "vs" and "vS" are the same as "vc" */
if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
clearopbeep(cap->oap);
return;
}
if (VIsual_active) { // "vs" and "vS" are the same as "vc"
if (cap->cmdchar == 'S') {
VIsual_mode_orig = VIsual_mode;
VIsual_mode = 'V';
@ -7120,10 +7142,15 @@ static void nv_tilde(cmdarg_T *cap)
{
if (!p_to
&& !VIsual_active
&& cap->oap->op_type != OP_TILDE)
&& cap->oap->op_type != OP_TILDE) {
if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
clearopbeep(cap->oap);
return;
}
n_swapchar(cap);
else
} else {
nv_operator(cap);
}
}
/*
@ -7136,6 +7163,12 @@ static void nv_operator(cmdarg_T *cap)
op_type = get_op_type(cap->cmdchar, cap->nchar);
if (bt_prompt(curbuf) && op_is_change(op_type)
&& !prompt_curpos_editable()) {
clearopbeep(cap->oap);
return;
}
if (op_type == cap->oap->op_type) /* double operator works on lines */
nv_lineop(cap);
else if (!checkclearop(cap->oap)) {
@ -7796,8 +7829,11 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
clearop(cap->oap);
assert(cap->opcount >= 0);
nv_diffgetput(true, (size_t)cap->opcount);
} else
} else {
clearopbeep(cap->oap);
}
} else if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
clearopbeep(cap->oap);
} else {
if (fix_indent) {
dir = (cap->cmdchar == ']' && cap->nchar == 'p')
@ -7809,8 +7845,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
? BACKWARD : FORWARD;
}
prep_redo_cmd(cap);
if (cap->cmdchar == 'g')
if (cap->cmdchar == 'g') {
flags |= PUT_CURSEND;
}
if (VIsual_active) {
/* Putting in Visual mode: The put text replaces the selected
@ -7916,10 +7953,14 @@ static void nv_open(cmdarg_T *cap)
clearop(cap->oap);
assert(cap->opcount >= 0);
nv_diffgetput(false, (size_t)cap->opcount);
} else if (VIsual_active) /* switch start and end of visual */
} else if (VIsual_active) {
// switch start and end of visual/
v_swap_corners(cap->cmdchar);
else
} else if (bt_prompt(curbuf)) {
clearopbeep(cap->oap);
} else {
n_opencmd(cap);
}
}
// Calculate start/end virtual columns for operating in block mode.

View File

@ -89,6 +89,10 @@ struct block_def {
# include "ops.c.generated.h"
#endif
// Flags for third item in "opchars".
#define OPF_LINES 1 // operator always works on lines
#define OPF_CHANGE 2 // operator changes text
/*
* The names of operators.
* IMPORTANT: Index must correspond with defines in vim.h!!!
@ -96,36 +100,36 @@ struct block_def {
*/
static char opchars[][3] =
{
{ NUL, NUL, false }, // OP_NOP
{ 'd', NUL, false }, // OP_DELETE
{ 'y', NUL, false }, // OP_YANK
{ 'c', NUL, false }, // OP_CHANGE
{ '<', NUL, true }, // OP_LSHIFT
{ '>', NUL, true }, // OP_RSHIFT
{ '!', NUL, true }, // OP_FILTER
{ 'g', '~', false }, // OP_TILDE
{ '=', NUL, true }, // OP_INDENT
{ 'g', 'q', true }, // OP_FORMAT
{ ':', NUL, true }, // OP_COLON
{ 'g', 'U', false }, // OP_UPPER
{ 'g', 'u', false }, // OP_LOWER
{ 'J', NUL, true }, // DO_JOIN
{ 'g', 'J', true }, // DO_JOIN_NS
{ 'g', '?', false }, // OP_ROT13
{ 'r', NUL, false }, // OP_REPLACE
{ 'I', NUL, false }, // OP_INSERT
{ 'A', NUL, false }, // OP_APPEND
{ 'z', 'f', true }, // OP_FOLD
{ 'z', 'o', true }, // OP_FOLDOPEN
{ 'z', 'O', true }, // OP_FOLDOPENREC
{ 'z', 'c', true }, // OP_FOLDCLOSE
{ 'z', 'C', true }, // OP_FOLDCLOSEREC
{ 'z', 'd', true }, // OP_FOLDDEL
{ 'z', 'D', true }, // OP_FOLDDELREC
{ 'g', 'w', true }, // OP_FORMAT2
{ 'g', '@', false }, // OP_FUNCTION
{ Ctrl_A, NUL, false }, // OP_NR_ADD
{ Ctrl_X, NUL, false }, // OP_NR_SUB
{ NUL, NUL, 0 }, // OP_NOP
{ 'd', NUL, OPF_CHANGE }, // OP_DELETE
{ 'y', NUL, 0 }, // OP_YANK
{ 'c', NUL, OPF_CHANGE }, // OP_CHANGE
{ '<', NUL, OPF_LINES | OPF_CHANGE }, // OP_LSHIFT
{ '>', NUL, OPF_LINES | OPF_CHANGE }, // OP_RSHIFT
{ '!', NUL, OPF_LINES | OPF_CHANGE }, // OP_FILTER
{ 'g', '~', OPF_CHANGE }, // OP_TILDE
{ '=', NUL, OPF_LINES | OPF_CHANGE }, // OP_INDENT
{ 'g', 'q', OPF_LINES | OPF_CHANGE }, // OP_FORMAT
{ ':', NUL, OPF_LINES }, // OP_COLON
{ 'g', 'U', OPF_CHANGE }, // OP_UPPER
{ 'g', 'u', OPF_CHANGE }, // OP_LOWER
{ 'J', NUL, OPF_LINES | OPF_CHANGE }, // DO_JOIN
{ 'g', 'J', OPF_LINES | OPF_CHANGE }, // DO_JOIN_NS
{ 'g', '?', OPF_CHANGE }, // OP_ROT13
{ 'r', NUL, OPF_CHANGE }, // OP_REPLACE
{ 'I', NUL, OPF_CHANGE }, // OP_INSERT
{ 'A', NUL, OPF_CHANGE }, // OP_APPEND
{ 'z', 'f', OPF_LINES }, // OP_FOLD
{ 'z', 'o', OPF_LINES }, // OP_FOLDOPEN
{ 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC
{ 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE
{ 'z', 'C', OPF_LINES }, // OP_FOLDCLOSEREC
{ 'z', 'd', OPF_LINES }, // OP_FOLDDEL
{ 'z', 'D', OPF_LINES }, // OP_FOLDDELREC
{ 'g', 'w', OPF_LINES | OPF_CHANGE }, // OP_FORMAT2
{ 'g', '@', OPF_CHANGE }, // OP_FUNCTION
{ Ctrl_A, NUL, OPF_CHANGE }, // OP_NR_ADD
{ Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
/*
@ -169,7 +173,13 @@ int get_op_type(int char1, int char2)
*/
int op_on_lines(int op)
{
return opchars[op][2];
return opchars[op][2] & OPF_LINES;
}
// Return TRUE if operator "op" changes text.
int op_is_change(int op)
{
return opchars[op][2] & OPF_CHANGE;
}
/*

View File

@ -299,7 +299,8 @@ static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL };
static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL };
static char *(p_ead_values[]) = { "both", "ver", "hor", NULL };
static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix",
"help", "acwrite", "terminal", NULL };
"help", "acwrite", "terminal",
"prompt", NULL };
static char *(p_bufhidden_values[]) = { "hide", "unload", "delete",
"wipe", NULL };
@ -7091,10 +7092,13 @@ static int check_opt_wim(void)
*/
bool can_bs(int what)
{
if (what == BS_START && bt_prompt(curbuf)) {
return false;
}
switch (*p_bs) {
case '2': return true;
case '1': return what != BS_START;
case '0': return false;
case '2': return true;
case '1': return what != BS_START;
case '0': return false;
}
return vim_strchr(p_bs, what) != NULL;
}

View File

@ -2971,7 +2971,10 @@ static char_u *u_save_line(linenr_T lnum)
bool bufIsChanged(buf_T *buf)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true));
// In a "prompt" buffer we do respect 'modified', so that we can control
// closing the window by setting or resetting that option.
return (!bt_dontwrite(buf) || bt_prompt(buf))
&& (buf->b_changed || file_ff_differs(buf, true));
}
// Return true if any buffer has changes. Also buffers that are not written.

View File

@ -0,0 +1,153 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local feed= helpers.feed
local source = helpers.source
local clear = helpers.clear
local feed_command = helpers.feed_command
describe('prompt buffer', function()
local screen
before_each(function()
clear()
screen = Screen.new(25, 10)
screen:attach()
source([[
func TextEntered(text)
if a:text == "exit"
set nomodified
stopinsert
close
else
call append(line("$") - 1, 'Command: "' . a:text . '"')
set nomodfied
call timer_start(20, {id -> TimerFunc(a:text)})
endif
endfunc
func TimerFunc(text)
call append(line("$") - 1, 'Result: "' . a:text .'"')
endfunc
]])
feed_command("set noshowmode | set laststatus=0")
feed_command("call setline(1, 'other buffer')")
feed_command("new")
feed_command("set buftype=prompt")
feed_command("call prompt_setcallback(bufnr(''), function('TextEntered'))")
end)
after_each(function()
screen:detach()
end)
it('works', function()
screen:expect([[
^ |
~ |
~ |
~ |
[Prompt] |
other buffer |
~ |
~ |
~ |
|
]])
feed("i")
feed("hello\n")
screen:expect([[
% hello |
Command: "hello" |
Result: "hello" |
% ^ |
[Prompt] [+] |
other buffer |
~ |
~ |
~ |
|
]])
feed("exit\n")
screen:expect([[
^other buffer |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
|
]])
end)
it('editing', function()
screen:expect([[
^ |
~ |
~ |
~ |
[Prompt] |
other buffer |
~ |
~ |
~ |
|
]])
feed("i")
feed("hello<BS><BS>")
screen:expect([[
% hel^ |
~ |
~ |
~ |
[Prompt] [+] |
other buffer |
~ |
~ |
~ |
|
]])
feed("<Left><Left><Left><BS>-")
screen:expect([[
% -^hel |
~ |
~ |
~ |
[Prompt] [+] |
other buffer |
~ |
~ |
~ |
|
]])
feed("<End>x")
screen:expect([[
% -helx^ |
~ |
~ |
~ |
[Prompt] [+] |
other buffer |
~ |
~ |
~ |
|
]])
feed("<C-U>exit\n")
screen:expect([[
^other buffer |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
|
]])
end)
end)