aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt58
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt17
-rw-r--r--runtime/doc/options.txt2
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim241
-rw-r--r--src/nvim/buffer.c24
-rw-r--r--src/nvim/buffer_defs.h6
-rw-r--r--src/nvim/diff.c4
-rw-r--r--src/nvim/edit.c79
-rw-r--r--src/nvim/eval.c56
-rw-r--r--src/nvim/eval.lua4
-rw-r--r--src/nvim/eval/funcs.c98
-rw-r--r--src/nvim/normal.c59
-rw-r--r--src/nvim/ops.c72
-rw-r--r--src/nvim/option.c12
-rw-r--r--src/nvim/undo.c5
-rw-r--r--test/functional/legacy/prompt_buffer_spec.lua153
16 files changed, 816 insertions, 74 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index e0ce83f8d2..b8739ff9b3 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -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,
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index 1a5e6421e7..55c5335a60 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -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
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 70af23ee29..7107a0135d 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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|.
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 7a757ef7d6..aa2b69ad97 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -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.
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 19d0cac2d3..790609ab94 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -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) {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index a0379740b6..9cdf36e4ed 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -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.
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index c31adc01fd..04309444d9 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -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 {
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index e253905057..cebd08af28 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -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)) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0dceca671b..ddafe1981f 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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;
+}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index efeac70816..17d9cc56aa 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -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},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 8232136783..36782e4223 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -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)
{
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index dac4c8f527..c69b10f99a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -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.
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 6d327c0814..735a33ca97 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -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;
}
/*
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 15ff8414ce..100902897a 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -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;
}
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 1f74bada41..df92b2c036 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -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.
diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua
new file mode 100644
index 0000000000..513be807be
--- /dev/null
+++ b/test/functional/legacy/prompt_buffer_spec.lua
@@ -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)