vim-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.
f273245f64
This commit is contained in:
erw7 2019-05-20 11:57:45 +09:00
parent 58ec72f9fd
commit 4813ad48cd
13 changed files with 378 additions and 46 deletions

View File

@ -2280,6 +2280,9 @@ 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_setprompt({buf}, {text}) none set prompt text
prompt_setcallback({buf}, {expr}) none set prompt callback function
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 +2293,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
@ -4541,7 +4544,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 +6544,40 @@ 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_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
pum_getpos() *pum_getpos()*
If the popup menu (see |ins-completion-menu|) is not visible,
@ -6554,7 +6591,6 @@ pum_getpos() *pum_getpos()*
scrollbar |TRUE| if visible
The values are the same as in |v:event| during |CompleteChanged|.
pumvisible() *pumvisible()*
Returns non-zero when the popup menu is visible, zero
otherwise. See |ins-completion-menu|.

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

@ -763,6 +763,8 @@ 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);
clear_fmark(&buf->b_last_cursor);
clear_fmark(&buf->b_last_insert);
clear_fmark(&buf->b_last_change);
@ -1876,6 +1878,9 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
}
}
buf->b_prompt_callback.type = kCallbackNone;
buf->b_prompt_text = NULL;
return buf;
}
@ -4824,6 +4829,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 +5229,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)

View File

@ -791,6 +791,9 @@ 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()
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.
@ -1143,6 +1149,14 @@ check_pum:
cmdwin_result = CAR;
return 0;
}
if (bt_prompt(curbuf)) {
invoke_prompt_callback();
if (curbuf != buf) {
// buffer changed, get out of Insert mode
return 0;
}
break;
}
if (!ins_eol(s->c) && !p_im) {
return 0; // out of memory
}
@ -1569,6 +1583,50 @@ 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);
}
}
// 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().
*/

View File

@ -13687,3 +13687,34 @@ 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);
}

View File

@ -243,6 +243,8 @@ return {
pow={args=2},
prevnonblank={args=1},
printf={args=varargs(1)},
prompt_setcallback={args={2, 2}},
prompt_setprompt={args={2, 2}},
pum_getpos={},
pumvisible={},
py3eval={args=1},

View File

@ -6076,6 +6076,51 @@ 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_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 };

View File

@ -0,0 +1,84 @@
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"
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
]])
end)
after_each(function()
screen:detach()
end)
it('works', function()
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'))")
screen:expect([[
^ |
~ |
~ |
~ |
[Scratch] |
other buffer |
~ |
~ |
~ |
|
]])
feed_command("startinsert")
feed("hello\n")
screen:expect([[
% hello |
Command: "hello" |
Result: "hello" |
% ^ |
[Scratch] |
other buffer |
~ |
~ |
~ |
|
]])
feed("exit\n")
screen:expect([[
^other buffer |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
|
]])
end)
end)