# HG changeset patch # User Christian Brabandt # Date 1528030807 -7200 # Node ID dc67449d648c569270f21913df184520cde0bbc4 # Parent c3064aaf53fb099fcbfe62290f8d2eb945d0a43f patch 8.1.0027: difficult to make a plugin that feeds a line to a job commit https://github.com/vim/vim/commit/f273245f6433d5d43a5671306b520a3230c35787 Author: Bram Moolenaar Date: Sun Jun 3 14:47:35 2018 +0200 patch 8.1.0027: difficult to make a plugin that feeds a line to a job Problem: Difficult to make a plugin that feeds a line to a job. Solution: Add the nitial code for the "prompt" buftype. diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -22,6 +22,7 @@ 8. Starting a job with a channel |job-st 9. Starting a job without a channel |job-start-nochannel| 10. Job options |job-options| 11. Controlling a job |job-control| +12. Using a prompt buffer |prompt-buffer| {Vi does not have any of these features} {only when compiled with the |+channel| feature for channel stuff} @@ -770,5 +771,43 @@ signals. E.g. to force a job to stop, " For more options see |job_stop()|. +============================================================================== +12. Using a prompt buffer *prompt-buffer* + +If you want to type input for the job in a Vim window you have a few options: +- Use a normal buffer and handle all possible commands yourself. + This will be complicated, since there are so many possible commands. +- Use a terminal window. This works well if what you type goes directly to + the job and the job output is directly displayed in the window. + See |terminal-window|. +- Use a prompt window. This works well when entering a line for the job in Vim + while displaying (possibly filtered) output from the job. + +A prompt buffer is created by setting 'buftype' to "prompt". You would +normally only do that in a newly created buffer. + +The user can edit and enter one line of text at the very last line of the +buffer. When pressing Enter in the prompt line the callback set with +|prompt_setcallback()| is invoked. It would normally send the line to a job. +Another callback would receive the output from the job and display it in the +buffer, below the prompt (and above the next prompt). + +Only the text in the last line, after the prompt, is editable. The rest of the +buffer is not modifiable with Normal mode commands. It can be modified by +calling functions, such as |append()|. Using other commands may mess up the +buffer. + +After setting 'buftype' to "prompt" Vim does not automatically start Insert +mode, use `:startinsert` if you want to enter Insert mode, so that the user +can start typing a line. + +The text of the prompt can be set with the |prompt_setprompt()| function. + +The user can go to Normal mode and navigate through the buffer. This can be +useful see older output or copy text. + +Any command that starts Insert mode, such as "a", "i", "A" and "I", will move +the cursor to the last line, after the prompt. + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2294,6 +2294,9 @@ perleval({expr}) any evaluate |Perl| ex 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_setprompt({buf}, {text}) none set prompt text +prompt_setcallback({buf}, {expr}) none set prompt callback function pumvisible() Number whether popup menu is visible pyeval({expr}) any evaluate |Python| expression py3eval({expr}) any evaluate |python3| expression @@ -2302,7 +2305,7 @@ range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} 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 @@ -4650,7 +4653,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 @@ -6475,6 +6478,42 @@ printf({fmt}, {expr1} ...) *printf()* arguments an error is given. Up to 18 arguments can be used. +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: ') + + +prompt_setcallback({buf}, {expr}) *prompt_setcallback()* + Set prompt callback for buffer {buf} to {expr}. 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 + + pumvisible() *pumvisible()* Returns non-zero when the popup menu is visible, zero otherwise. See |ins-completion-menu|. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1394,6 +1394,9 @@ A jump table for the options with a shor manually) terminal buffer for a |terminal| (you are not supposed to set this manually) + prompt buffer where only the last line can be edited, meant + to be used by a plugin, see |prompt-buffer| + {only when compiled with the |+channel| feature} This option is used together with 'bufhidden' and 'swapfile' to specify special kinds of buffers. See |special-buffers|. @@ -4264,7 +4267,8 @@ A jump table for the options with a shor 'imactivatefunc' 'imaf' string (default "") global {not in Vi} - {only available when compiled with |+mbyte|} + {only available when compiled with the |+multi_byte| + feature} This option specifies a function that will be called to activate or deactivate the Input Method. It is not used in the GUI. @@ -4316,7 +4320,8 @@ A jump table for the options with a shor 'imcmdline' 'imc' boolean (default off) global {not in Vi} - {only available when compiled with |+mbyte|} + {only available when compiled with the |+multi_byte| + feature} When set the Input Method is always on when starting to edit a command line, unless entering a search pattern (see 'imsearch' for that). Setting this option is useful when your input method allows entering @@ -4327,7 +4332,8 @@ A jump table for the options with a shor 'imdisable' 'imd' boolean (default off, on for some systems (SGI)) global {not in Vi} - {only available when compiled with |+mbyte|} + {only available when compiled with the |+multi_byte| + feature} When set the Input Method is never used. This is useful to disable the IM when it doesn't work properly. Currently this option is on by default for SGI/IRIX machines. This @@ -4380,7 +4386,8 @@ A jump table for the options with a shor 'imstatusfunc' 'imsf' string (default "") global {not in Vi} - {only available when compiled with |+mbyte|} + {only available when compiled with the |+multi_byte| + feature} This option specifies a function that is called to obtain the status of Input Method. It must return a positive number when IME is active. It is not used in the GUI. diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -38,6 +38,10 @@ browser use: https://github.com/vim/vim/ *known-bugs* -------------------- Known bugs and current work ----------------------- +Prompt buffer: +- Add a command line history. +- delay next prompt until plugin gives OK? + Terminal emulator window: - Win32: Termdebug doesn't work, because gdb does not support mi2 on a tty. This plugin: https://github.com/cpiger/NeoDebug runs gdb as a job, @@ -71,9 +75,15 @@ 13, #2910) Can't reproduce? On Win32 when not in the console and t_Co >= 256, allow using 'tgc'. (Nobuhiro Takasaki, #2833) Also check t_Co. -balloon_show() does not work properly in the terminal. (Ben Jackson, 2017 Dec -20, #2481) -Also see #2352, want better control over balloon, perhaps set the position. +Patch to fix arguments of :edit. (Dominique Pelle, 2018 May 28 #2966) + +Ptch to update html syntax. (Jorge Maldonado Ventura, #2974) + +Patch to fix that restoring window doesn't work when 'winheight' is large. +(Darrell Nash, 2018 May 30, #2971) Doesn't work? Issue #2970 + +Patch to add completion to :unlet for environment vars. (Jason Franklin, 2018 +May 30) Last update. Errors found with random data: heap-buffer-overflow in alist_add (#2472) @@ -81,6 +91,22 @@ Errors found with random data: More warnings from static analysis: https://lgtm.com/projects/g/vim/vim/alerts/?mode=list +Patch to make "is" and "as" work bettter. (Jason Franklin, 2018 May 19) + +Patch to add tests for user and language completion. (Dominique Pelle, 2018 +Jun 2, #2978) + +Using ":file" in quickfix window during an autocommand doesn't work. +(Jason Franklin, 2018 May 23) Allow for using it when there is no argument. + +Pull request #2967: Allow white space in sign text. (Ben Jackson) + +Patch for xterm and vt320 builtin termcap. (Kouichi Iwamoto, 2018 May 31, +#2973) + +Patch to add more testing for :cd command. (Dominique Pelle, 2018 May 30, +#2972) + Script generated by :mksession does not work well if there are windows with modified buffers change "silent only" into "silent only!" @@ -88,16 +114,27 @@ modified buffers skip "badd fname" if "fname" is already in the buffer list remove remark about unloading buffers from documentation +Patch to make :help work for tags with a ?. (Hirohito Higashi, 2018 May 28) + Compiler warnings (geeknik, 2017 Oct 26): - signed integer overflow in do_sub() (#2249) - signed integer overflow in get_address() (#2248) - signed integer overflow in getdecchrs() (#2254) - undefined left shift in get_string_tv() (#2250) +Patch for more quickfix refactoring. (Yegappan Lakshmanan, #2950) + Tests failing for "make testgui" with GTK: - Test_setbufvar_options() - Test_exit_callback_interval() +Make balloon_show() work outside of 'balloonexpr'? Users expect it to work: +#2948. (related to #1512?) +On Win32 it stops showing, because showState is already ShS_SHOWING. +balloon_show() does not work properly in the terminal. (Ben Jackson, 2017 Dec +20, #2481) +Also see #2352, want better control over balloon, perhaps set the position. + Try out background make plugin: https://github.com/AndrewVos/vim-make-background or asyncmake: @@ -112,6 +149,8 @@ used for git temp files. Cursor in wrong position when line wraps. (#2540) +Patch for Lua support. (Kazunobu Kuriyama, 2018 May 26) + Add an option similar to 'lazyredraw' to skip redrawing while executing a script or function. @@ -141,6 +180,9 @@ How to test that it works well for all V Alternative manpager.vim. (Enno, 2018 Jan 5, #2529) +Patch to use NGETTEXT() in many more places. (Sergey Alyoshin, 2018 May 25) +Updated ptach May 27. + Does setting 'cursorline' cause syntax highlighting to slow down? Perhaps is mess up the cache? (Mike Lee Williams, 2018 Jan 27, #2539) Also: 'foldtext' is evaluated too often. (Daniel Hahler, #2773) diff --git a/src/Makefile b/src/Makefile --- a/src/Makefile +++ b/src/Makefile @@ -2252,6 +2252,7 @@ test_arglist \ test_popup \ test_preview \ test_profile \ + test_prompt_buffer \ test_put \ test_python2 \ test_python3 \ diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -851,6 +851,10 @@ free_buffer(buf_T *buf) #ifdef FEAT_TERMINAL free_terminal(buf); #endif +#ifdef FEAT_JOB_CHANNEL + vim_free(buf->b_prompt_text); + free_callback(buf->b_prompt_callback, buf->b_prompt_partial); +#endif buf_hashtab_remove(buf); @@ -5634,6 +5638,15 @@ bt_help(buf_T *buf) } /* + * Return TRUE if "buf" is a prompt buffer. + */ + int +bt_prompt(buf_T *buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'p'; +} + +/* * Return TRUE if "buf" is a "nofile", "acwrite" or "terminal" buffer. * This means the buffer name is not a file name. */ @@ -5642,7 +5655,8 @@ bt_nofile(buf_T *buf) { return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') || buf->b_p_bt[0] == 'a' - || buf->b_p_bt[0] == 't'); + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'p'); } /* @@ -5651,7 +5665,9 @@ bt_nofile(buf_T *buf) int bt_dontwrite(buf_T *buf) { - return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->b_p_bt[0] == 't'); + return buf != NULL && (buf->b_p_bt[0] == 'n' + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'p'); } int diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -5836,4 +5836,38 @@ job_stop(job_T *job, typval_T *argvars, return 1; } + void +invoke_prompt_callback(void) +{ + typval_T rettv; + int dummy; + 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 == NULL) + 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; + + call_func(curbuf->b_prompt_callback, + (int)STRLEN(curbuf->b_prompt_callback), + &rettv, 1, argv, NULL, 0L, 0L, &dummy, TRUE, + curbuf->b_prompt_partial, NULL); + clear_tv(&argv[0]); + clear_tv(&rettv); +} + #endif /* FEAT_JOB_CHANNEL */ diff --git a/src/diff.c b/src/diff.c --- a/src/diff.c +++ b/src/diff.c @@ -2141,6 +2141,13 @@ nv_diffgetput(int put, long count) exarg_T ea; char_u buf[30]; +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + vim_beep(BO_OPER); + return; + } +#endif if (count == 0) ea.arg = (char_u *)""; else diff --git a/src/edit.c b/src/edit.c --- a/src/edit.c +++ b/src/edit.c @@ -203,6 +203,9 @@ static unsigned quote_meta(char_u *dest static void ins_redraw(int ready); static void ins_ctrl_v(void); +#ifdef FEAT_JOB_CHANNEL +static void init_prompt(int cmdchar_todo); +#endif static void undisplay_dollar(void); static void insert_special(int, int, int); static void internal_format(int textwidth, int second_indent, int flags, int format_only, int c); @@ -351,6 +354,9 @@ edit( int inserted_space = FALSE; /* just inserted a space */ int replaceState = REPLACE; int nomove = FALSE; /* don't move cursor on return */ +#ifdef FEAT_JOB_CHANNEL + int cmdchar_todo = cmdchar; +#endif /* Remember whether editing was restarted after CTRL-O. */ did_restart_edit = restart_edit; @@ -707,6 +713,14 @@ edit( foldCheckClose(); #endif +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + init_prompt(cmdchar_todo); + cmdchar_todo = NUL; + } +#endif + /* * 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 @@ -1374,6 +1388,18 @@ doESCkey: goto doESCkey; } #endif +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + buf_T *buf = curbuf; + + invoke_prompt_callback(); + if (curbuf != buf) + // buffer changed, get out of Insert mode + goto doESCkey; + break; + } +#endif if (ins_eol(c) == FAIL && !p_im) goto doESCkey; /* out of memory */ auto_format(FALSE, FALSE); @@ -1808,6 +1834,58 @@ edit_putchar(int c, int highlight) } } +#if defined(FEAT_JOB_CHANNEL) || defined(PROTO) +/* + * 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 = ml_get_curline(); + 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); +} + +/* + * Return TRUE if the cursor is in the editable position of the prompt line. + */ + int +prompt_curpos_editable() +{ + return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count + && curwin->w_cursor.col >= (int)STRLEN(prompt_text()); +} +#endif + /* * Undo the previous edit_putchar(). */ diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -294,6 +294,10 @@ static void f_pow(typval_T *argvars, typ #endif static void f_prevnonblank(typval_T *argvars, typval_T *rettv); static void f_printf(typval_T *argvars, typval_T *rettv); +#ifdef FEAT_JOB_CHANNEL +static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv); +static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv); +#endif static void f_pumvisible(typval_T *argvars, typval_T *rettv); #ifdef FEAT_PYTHON3 static void f_py3eval(typval_T *argvars, typval_T *rettv); @@ -744,6 +748,10 @@ static struct fst #endif {"prevnonblank", 1, 1, f_prevnonblank}, {"printf", 1, 19, f_printf}, +#ifdef FEAT_JOB_CHANNEL + {"prompt_setcallback", 2, 2, f_prompt_setcallback}, + {"prompt_setprompt", 2, 2, f_prompt_setprompt}, +#endif {"pumvisible", 0, 0, f_pumvisible}, #ifdef FEAT_PYTHON3 {"py3eval", 1, 1, f_py3eval}, @@ -1240,6 +1248,11 @@ f_append(typval_T *argvars, typval_T *re appended_lines_mark(lnum, added); if (curwin->w_cursor.lnum > lnum) curwin->w_cursor.lnum += added; +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && (State & INSERT)) + // show the line with the prompt + update_topline(); +#endif } else rettv->vval.v_number = 1; /* Failed */ @@ -8379,6 +8392,57 @@ f_printf(typval_T *argvars, typval_T *re did_emsg |= saved_did_emsg; } +#ifdef FEAT_JOB_CHANNEL +/* + * "prompt_setcallback({buffer}, {callback})" function + */ + static void +f_prompt_setcallback(typval_T *argvars, typval_T *rettv UNUSED) +{ + buf_T *buf; + char_u *callback; + partial_T *partial; + + if (check_secure()) + return; + buf = get_buf_tv(&argvars[0], FALSE); + if (buf == NULL) + return; + + callback = get_callback(&argvars[1], &partial); + if (callback == NULL) + return; + + free_callback(buf->b_prompt_callback, buf->b_prompt_partial); + if (partial == NULL) + buf->b_prompt_callback = vim_strsave(callback); + else + /* pointer into the partial */ + buf->b_prompt_callback = callback; + buf->b_prompt_partial = partial; +} + +/* + * "prompt_setprompt({buffer}, {text})" function + */ + static void +f_prompt_setprompt(typval_T *argvars, typval_T *rettv UNUSED) +{ + buf_T *buf; + char_u *text; + + if (check_secure()) + return; + buf = get_buf_tv(&argvars[0], FALSE); + if (buf == NULL) + return; + + text = get_tv_string(&argvars[1]); + vim_free(buf->b_prompt_text); + buf->b_prompt_text = vim_strsave(text); +} +#endif + /* * "pumvisible()" function */ diff --git a/src/normal.c b/src/normal.c --- a/src/normal.c +++ b/src/normal.c @@ -4180,6 +4180,11 @@ nv_help(cmdarg_T *cap) static void nv_addsub(cmdarg_T *cap) { +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && !prompt_curpos_editable()) + clearopbeep(cap->oap); + else +#endif if (!VIsual_active && cap->oap->op_type == OP_NOP) { prep_redo_cmd(cap); @@ -6214,6 +6219,17 @@ nv_down(cmdarg_T *cap) cmdwin_result = CAR; else #endif +#ifdef FEAT_JOB_CHANNEL + /* In a prompt buffer a in the last line invokes the callback. */ + if (bt_prompt(curbuf) && cap->cmdchar == CAR + && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + { + invoke_prompt_callback(); + if (restart_edit == 0) + restart_edit = 'a'; + } + else +#endif { cap->oap->motion_type = MLINE; if (cursor_down(cap->count1, cap->oap->op_type == OP_NOP) == FAIL) @@ -6972,6 +6988,13 @@ nv_kundo(cmdarg_T *cap) { if (!checkclearopq(cap->oap)) { +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + { + clearopbeep(cap->oap); + return; + } +#endif u_undo((int)cap->count1); curwin->w_set_curswant = TRUE; } @@ -6989,6 +7012,13 @@ nv_replace(cmdarg_T *cap) if (checkclearop(cap->oap)) return; +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && !prompt_curpos_editable()) + { + clearopbeep(cap->oap); + return; + } +#endif /* get another character */ if (cap->nchar == Ctrl_V) @@ -7465,6 +7495,13 @@ nv_subst(cmdarg_T *cap) if (term_swap_diff() == OK) return; #endif +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && !prompt_curpos_editable()) + { + clearopbeep(cap->oap); + return; + } +#endif if (VIsual_active) /* "vs" and "vS" are the same as "vc" */ { if (cap->cmdchar == 'S') @@ -8570,7 +8607,16 @@ nv_Undo(cmdarg_T *cap) nv_tilde(cmdarg_T *cap) { if (!p_to && !VIsual_active && cap->oap->op_type != OP_TILDE) + { +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && !prompt_curpos_editable()) + { + clearopbeep(cap->oap); + return; + } +#endif n_swapchar(cap); + } else nv_operator(cap); } @@ -8585,6 +8631,13 @@ nv_operator(cmdarg_T *cap) int op_type; op_type = get_op_type(cap->cmdchar, cap->nchar); +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf) && op_is_change(op_type) && !prompt_curpos_editable()) + { + clearopbeep(cap->oap); + return; + } +#endif if (op_type == cap->oap->op_type) /* double operator works on lines */ nv_lineop(cap); @@ -9426,6 +9479,12 @@ nv_put(cmdarg_T *cap) #endif clearopbeep(cap->oap); } +#ifdef FEAT_JOB_CHANNEL + else if (bt_prompt(curbuf) && !prompt_curpos_editable()) + { + clearopbeep(cap->oap); + } +#endif else { dir = (cap->cmdchar == 'P' @@ -9551,6 +9610,12 @@ nv_open(cmdarg_T *cap) #endif if (VIsual_active) /* switch start and end of visual */ v_swap_corners(cap->cmdchar); +#ifdef FEAT_JOB_CHANNEL + else if (bt_prompt(curbuf)) + { + clearopbeep(cap->oap); + } +#endif else n_opencmd(cap); } diff --git a/src/ops.c b/src/ops.c --- a/src/ops.c +++ b/src/ops.c @@ -126,43 +126,47 @@ static int fmt_check_par(linenr_T, int * static int fmt_check_par(linenr_T); #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!!! - * The third field indicates whether the operator always works on lines. + * The third field holds OPF_ flags. */ 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 }; /* @@ -201,7 +205,16 @@ 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/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -3229,7 +3229,7 @@ static char *(p_bsdir_values[]) = {"curr 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", "terminal", "acwrite", NULL}; +static char *(p_buftype_values[]) = {"nofile", "nowrite", "quickfix", "help", "terminal", "acwrite", "prompt", NULL}; static char *(p_bufhidden_values[]) = {"hide", "unload", "delete", "wipe", NULL}; static char *(p_bs_values[]) = {"indent", "eol", "start", NULL}; #ifdef FEAT_FOLDING diff --git a/src/proto/buffer.pro b/src/proto/buffer.pro --- a/src/proto/buffer.pro +++ b/src/proto/buffer.pro @@ -59,6 +59,7 @@ void write_viminfo_bufferlist(FILE *fp); int bt_quickfix(buf_T *buf); int bt_terminal(buf_T *buf); int bt_help(buf_T *buf); +int bt_prompt(buf_T *buf); int bt_nofile(buf_T *buf); int bt_dontwrite(buf_T *buf); int bt_dontwrite_msg(buf_T *buf); diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -71,4 +71,5 @@ char *job_status(job_T *job); void job_info(job_T *job, dict_T *dict); void job_info_all(list_T *l); int job_stop(job_T *job, typval_T *argvars, char *type); +void invoke_prompt_callback(void); /* vim: set ft=c : */ diff --git a/src/proto/edit.pro b/src/proto/edit.pro --- a/src/proto/edit.pro +++ b/src/proto/edit.pro @@ -1,6 +1,8 @@ /* edit.c */ int edit(int cmdchar, int startln, long count); void edit_putchar(int c, int highlight); +char_u *prompt_text(void); +int prompt_curpos_editable(void); void edit_unputchar(void); void display_dollar(colnr_T col); void change_indent(int type, int amount, int round, int replaced, int call_changed_bytes); diff --git a/src/proto/ops.pro b/src/proto/ops.pro --- a/src/proto/ops.pro +++ b/src/proto/ops.pro @@ -1,6 +1,7 @@ /* ops.c */ int get_op_type(int char1, int char2); int op_on_lines(int op); +int op_is_change(int op); int get_op_char(int optype); int get_extra_op_char(int optype); void op_shift(oparg_T *oap, int curs_top, int amount); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -2356,6 +2356,11 @@ struct file_buffer int b_shortname; /* this file has an 8.3 file name */ +#ifdef FEAT_JOB_CHANNEL + char_u *b_prompt_text; // set by prompt_setprompt() + char_u *b_prompt_callback; // set by prompt_setcallback() + partial_T *b_prompt_partial; // set by prompt_setcallback() +#endif #ifdef FEAT_MZSCHEME void *b_mzscheme_ref; /* The MzScheme reference to this buffer */ #endif diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -147,6 +147,7 @@ NEW_TESTS = test_arabic.res \ test_perl.res \ test_plus_arg_edit.res \ test_preview.res \ + test_prompt_buffer.res \ test_profile.res \ test_python2.res \ test_python3.res \ diff --git a/src/testdir/screendump.vim b/src/testdir/screendump.vim --- a/src/testdir/screendump.vim +++ b/src/testdir/screendump.vim @@ -5,19 +5,18 @@ if exists('*CanRunVimInTerminal') finish endif -" Need to be able to run terminal Vim with 256 colors. On MS-Windows the -" console only has 16 colors and the GUI can't run in a terminal. -if !has('terminal') || has('win32') - func CanRunVimInTerminal() - return 0 - endfunc +" For most tests we need to be able to run terminal Vim with 256 colors. On +" MS-Windows the console only has 16 colors and the GUI can't run in a +" terminal. +func CanRunVimInTerminal() + return has('terminal') && !has('win32') +endfunc + +" Skip the rest if there is no terminal feature at all. +if !has('terminal') finish endif -func CanRunVimInTerminal() - return 1 -endfunc - source shared.vim " Run Vim with "arguments" in a new terminal window. @@ -54,6 +53,7 @@ func RunVimInTerminal(arguments, options let cols = get(a:options, 'cols', 75) let cmd = GetVimCommandClean() + " Add -v to have gvim run in the terminal (if possible) let cmd .= ' -v ' . a:arguments let buf = term_start(cmd, {'curwin': 1, 'term_rows': rows, 'term_cols': cols}) @@ -64,11 +64,12 @@ func RunVimInTerminal(arguments, options let cols = term_getsize(buf)[1] endif - " Wait for "All" or "Top" of the ruler in the status line to be shown. This - " can be quite slow (e.g. when using valgrind). + " Wait for "All" or "Top" of the ruler to be shown in the last line or in + " the status line of the last window. This can be quite slow (e.g. when + " using valgrind). " If it fails then show the terminal contents for debugging. try - call WaitFor({-> len(term_getline(buf, rows)) >= cols - 1}) + call WaitFor({-> len(term_getline(buf, rows)) >= cols - 1 || len(term_getline(buf, rows - 1)) >= cols - 1}) catch /timed out after/ let lines = map(range(1, rows), {key, val -> term_getline(buf, val)}) call assert_report('RunVimInTerminal() failed, screen contents: ' . join(lines, "")) @@ -80,7 +81,7 @@ endfunc " Stop a Vim running in terminal buffer "buf". func StopVimInTerminal(buf) call assert_equal("running", term_getstatus(a:buf)) - call term_sendkeys(a:buf, "\\:qa!\") + call term_sendkeys(a:buf, "\:qa!\") call WaitForAssert({-> assert_equal("finished", term_getstatus(a:buf))}) only! endfunc diff --git a/src/testdir/test_prompt_buffer.vim b/src/testdir/test_prompt_buffer.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_prompt_buffer.vim @@ -0,0 +1,55 @@ +" Tests for setting 'buftype' to "prompt" + +if !has('channel') + finish +endif + +source shared.vim +source screendump.vim + +func Test_prompt_basic() + " We need to use a terminal window to be able to feed keys without leaving + " Insert mode. + if !has('terminal') + call assert_report('no terminal') + return + endif + call writefile([ + \ 'func TextEntered(text)', + \ ' if a:text == "exit"', + \ ' stopinsert', + \ ' close', + \ ' else', + \ ' " Add the output above the current prompt.', + \ ' call append(line("$") - 1, "Command: \"" . a:text . "\"")', + \ ' " 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, "Result: \"" . a:text . "\"")', + \ 'endfunc', + \ '', + \ 'call setline(1, "other buffer")', + \ 'new', + \ 'set buftype=prompt', + \ 'call prompt_setcallback(bufnr(""), function("TextEntered"))', + \ 'startinsert', + \ ], 'Xpromptscript') + let buf = RunVimInTerminal('-S Xpromptscript', {}) + call WaitForAssert({-> assert_equal('%', term_getline(buf, 1))}) + + call term_sendkeys(buf, "hello\") + call WaitForAssert({-> assert_equal('% hello', term_getline(buf, 1))}) + call WaitForAssert({-> assert_equal('Command: "hello"', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('Result: "hello"', term_getline(buf, 3))}) + + call term_sendkeys(buf, "exit\") + call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) + call delete('Xpromptscript') +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -762,6 +762,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 27, +/**/ 26, /**/ 25,