mirror of
https://github.com/vim/vim.git
synced 2025-07-26 11:04:33 -04:00
patch 8.1.2080: the terminal API is limited and can't be disabled
Problem: The terminal API is limited and can't be disabled. Solution: Add term_setapi() to set the function prefix. (Ozaki Kiichi, closes #2907)
This commit is contained in:
parent
d2c1fb476d
commit
d2842ea60b
@ -1,4 +1,4 @@
|
||||
*eval.txt* For Vim version 8.1. Last change: 2019 Sep 19
|
||||
*eval.txt* For Vim version 8.1. Last change: 2019 Sep 26
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -2819,6 +2819,7 @@ term_gettty({buf}, [{input}]) String get the tty name of a terminal
|
||||
term_list() List get the list of terminal buffers
|
||||
term_scrape({buf}, {row}) List get row of a terminal screen
|
||||
term_sendkeys({buf}, {keys}) none send keystrokes to a terminal
|
||||
term_setapi({buf}, {expr}) none set |terminal-api| function name prefix
|
||||
term_setansicolors({buf}, {colors})
|
||||
none set ANSI palette in GUI color mode
|
||||
term_setkill({buf}, {how}) none set signal to stop job in terminal
|
||||
@ -3262,9 +3263,14 @@ bufnr([{expr} [, {create}]])
|
||||
The result is the number of a buffer, as it is displayed by
|
||||
the ":ls" command. For the use of {expr}, see |bufname()|
|
||||
above.
|
||||
|
||||
If the buffer doesn't exist, -1 is returned. Or, if the
|
||||
{create} argument is present and not zero, a new, unlisted,
|
||||
buffer is created and its number is returned.
|
||||
buffer is created and its number is returned. Example: >
|
||||
let newbuf = bufnr('Scratch001', 1)
|
||||
< Using an empty name uses the current buffer. To create a new
|
||||
buffer with an empty name use |bufadd()|.
|
||||
|
||||
bufnr("$") is the last buffer: >
|
||||
:let last_buffer = bufnr("$")
|
||||
< The result is a Number, which is the highest buffer number
|
||||
|
@ -1,4 +1,4 @@
|
||||
*terminal.txt* For Vim version 8.1. Last change: 2019 Sep 20
|
||||
*terminal.txt* For Vim version 8.1. Last change: 2019 Sep 26
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@ -222,7 +222,7 @@ Command syntax ~
|
||||
Vim width (no window left or right of
|
||||
the terminal window) this value is
|
||||
ignored.
|
||||
++eof={text} when using [range]: text to send after
|
||||
++eof={text} When using [range]: text to send after
|
||||
the last line was written. Cannot
|
||||
contain white space. A CR is
|
||||
appended. For MS-Windows the default
|
||||
@ -234,6 +234,10 @@ Command syntax ~
|
||||
++type={pty} (MS-Windows only): Use {pty} as the
|
||||
virtual console. See 'termwintype'
|
||||
for the values.
|
||||
++api={expr} Permit the function name starting with
|
||||
{expr} to be called as |terminal-api|
|
||||
function. If {expr} is empty then no
|
||||
function can be called.
|
||||
|
||||
If you want to use more options use the |term_start()|
|
||||
function.
|
||||
@ -701,6 +705,15 @@ term_sendkeys({buf}, {keys}) *term_sendkeys()*
|
||||
GetBufnr()->term_sendkeys(keys)
|
||||
|
||||
|
||||
term_setapi({buf}, {expr}) *term_setapi()*
|
||||
Set the function name prefix to be used for the |terminal-api|
|
||||
function in terminal {buf}. For example: >
|
||||
:call term_setapi(buf, "Myapi_")
|
||||
:call term_setapi(buf, "")
|
||||
<
|
||||
The default is "Tapi_". When {expr} is an empty string then
|
||||
no |terminal-api| function can be used for {buf}.
|
||||
|
||||
term_setansicolors({buf}, {colors}) *term_setansicolors()*
|
||||
Set the ANSI color palette used by terminal {buf}.
|
||||
{colors} must be a List of 16 valid color names or hexadecimal
|
||||
@ -843,6 +856,9 @@ term_start({cmd} [, {options}]) *term_start()*
|
||||
color modes. See |g:terminal_ansi_colors|.
|
||||
"tty_type" (MS-Windows only): Specify which pty to
|
||||
use. See 'termwintype' for the values.
|
||||
"term_api" function name prefix for the
|
||||
|terminal-api| function. See
|
||||
|term_setapi()|.
|
||||
|
||||
Can also be used as a |method|: >
|
||||
GetCommand()->term_start()
|
||||
@ -902,9 +918,9 @@ Currently supported commands:
|
||||
Call a user defined function with {argument}.
|
||||
The function is called with two arguments: the buffer number
|
||||
of the terminal and {argument}, the decoded JSON argument.
|
||||
The function name must start with "Tapi_" to avoid
|
||||
By default, the function name must start with "Tapi_" to avoid
|
||||
accidentally calling a function not meant to be used for the
|
||||
terminal API.
|
||||
terminal API. This can be changed with |term_setapi()|.
|
||||
The user function should sanity check the argument.
|
||||
The function can use |term_sendkeys()| to send back a reply.
|
||||
Example in JSON: >
|
||||
|
@ -5144,6 +5144,14 @@ get_job_options(typval_T *tv, jobopt_T *opt, int supported, int supported2)
|
||||
memcpy(opt->jo_ansi_colors, rgb, sizeof(rgb));
|
||||
}
|
||||
# endif
|
||||
else if (STRCMP(hi->hi_key, "term_api") == 0)
|
||||
{
|
||||
if (!(supported2 & JO2_TERM_API))
|
||||
break;
|
||||
opt->jo_set2 |= JO2_TERM_API;
|
||||
opt->jo_term_api = tv_get_string_buf_chk(item,
|
||||
opt->jo_term_api_buf);
|
||||
}
|
||||
#endif
|
||||
else if (STRCMP(hi->hi_key, "env") == 0)
|
||||
{
|
||||
|
@ -787,6 +787,7 @@ static funcentry_T global_functions[] =
|
||||
# if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
|
||||
{"term_setansicolors", 2, 2, FEARG_1, f_term_setansicolors},
|
||||
# endif
|
||||
{"term_setapi", 2, 2, FEARG_1, f_term_setapi},
|
||||
{"term_setkill", 2, 2, FEARG_1, f_term_setkill},
|
||||
{"term_setrestore", 2, 2, FEARG_1, f_term_setrestore},
|
||||
{"term_setsize", 3, 3, FEARG_1, f_term_setsize},
|
||||
|
@ -50,6 +50,7 @@ void f_term_scrape(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_sendkeys(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_getansicolors(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_setansicolors(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_setapi(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_setrestore(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_setkill(typval_T *argvars, typval_T *rettv);
|
||||
void f_term_start(typval_T *argvars, typval_T *rettv);
|
||||
|
@ -1938,6 +1938,7 @@ struct channel_S {
|
||||
#define JO2_ANSI_COLORS 0x8000 // "ansi_colors"
|
||||
#define JO2_TTY_TYPE 0x10000 // "tty_type"
|
||||
#define JO2_BUFNR 0x20000 // "bufnr"
|
||||
#define JO2_TERM_API 0x40000 // "term_api"
|
||||
|
||||
#define JO_MODE_ALL (JO_MODE + JO_IN_MODE + JO_OUT_MODE + JO_ERR_MODE)
|
||||
#define JO_CB_ALL \
|
||||
@ -2007,6 +2008,8 @@ typedef struct
|
||||
long_u jo_ansi_colors[16];
|
||||
# endif
|
||||
int jo_tty_type; // first character of "tty_type"
|
||||
char_u *jo_term_api;
|
||||
char_u jo_term_api_buf[NUMBUFLEN];
|
||||
#endif
|
||||
} jobopt_T;
|
||||
|
||||
|
@ -109,6 +109,7 @@ struct terminal_S {
|
||||
#define TL_FINISH_OPEN 'o' /* ++open */
|
||||
char_u *tl_opencmd;
|
||||
char_u *tl_eof_chars;
|
||||
char_u *tl_api; // prefix for terminal API function
|
||||
|
||||
char_u *tl_arg0_cmd; // To format the status bar
|
||||
|
||||
@ -641,6 +642,11 @@ term_start(
|
||||
term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
|
||||
}
|
||||
|
||||
if (opt->jo_term_api != NULL)
|
||||
term->tl_api = vim_strsave(opt->jo_term_api);
|
||||
else
|
||||
term->tl_api = vim_strsave((char_u *)"Tapi_");
|
||||
|
||||
/* System dependent: setup the vterm and maybe start the job in it. */
|
||||
if (argv == NULL
|
||||
&& argvar->v_type == VAR_STRING
|
||||
@ -708,44 +714,58 @@ ex_terminal(exarg_T *eap)
|
||||
cmd += 2;
|
||||
p = skiptowhite(cmd);
|
||||
ep = vim_strchr(cmd, '=');
|
||||
if (ep != NULL && ep < p)
|
||||
p = ep;
|
||||
if (ep != NULL)
|
||||
{
|
||||
if (ep < p)
|
||||
p = ep;
|
||||
else
|
||||
ep = NULL;
|
||||
}
|
||||
|
||||
if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
|
||||
# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
|
||||
&& STRNICMP(cmd, name, sizeof(name) - 1) == 0)
|
||||
if (OPTARG_HAS("close"))
|
||||
opt.jo_term_finish = 'c';
|
||||
else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
|
||||
else if (OPTARG_HAS("noclose"))
|
||||
opt.jo_term_finish = 'n';
|
||||
else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
|
||||
else if (OPTARG_HAS("open"))
|
||||
opt.jo_term_finish = 'o';
|
||||
else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
|
||||
else if (OPTARG_HAS("curwin"))
|
||||
opt.jo_curwin = 1;
|
||||
else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
|
||||
else if (OPTARG_HAS("hidden"))
|
||||
opt.jo_hidden = 1;
|
||||
else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
|
||||
else if (OPTARG_HAS("norestore"))
|
||||
opt.jo_term_norestore = 1;
|
||||
else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
|
||||
&& ep != NULL)
|
||||
else if (OPTARG_HAS("kill") && ep != NULL)
|
||||
{
|
||||
opt.jo_set2 |= JO2_TERM_KILL;
|
||||
opt.jo_term_kill = ep + 1;
|
||||
p = skiptowhite(cmd);
|
||||
}
|
||||
else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
|
||||
&& ep != NULL && isdigit(ep[1]))
|
||||
else if (OPTARG_HAS("api"))
|
||||
{
|
||||
opt.jo_set2 |= JO2_TERM_API;
|
||||
if (ep != NULL)
|
||||
{
|
||||
opt.jo_term_api = ep + 1;
|
||||
p = skiptowhite(cmd);
|
||||
}
|
||||
else
|
||||
opt.jo_term_api = NULL;
|
||||
}
|
||||
else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
|
||||
{
|
||||
opt.jo_set2 |= JO2_TERM_ROWS;
|
||||
opt.jo_term_rows = atoi((char *)ep + 1);
|
||||
p = skiptowhite(cmd);
|
||||
}
|
||||
else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
|
||||
&& ep != NULL && isdigit(ep[1]))
|
||||
else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
|
||||
{
|
||||
opt.jo_set2 |= JO2_TERM_COLS;
|
||||
opt.jo_term_cols = atoi((char *)ep + 1);
|
||||
p = skiptowhite(cmd);
|
||||
}
|
||||
else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
|
||||
&& ep != NULL)
|
||||
else if (OPTARG_HAS("eof") && ep != NULL)
|
||||
{
|
||||
char_u *buf = NULL;
|
||||
char_u *keys;
|
||||
@ -785,6 +805,7 @@ ex_terminal(exarg_T *eap)
|
||||
semsg(_("E181: Invalid attribute: %s"), cmd);
|
||||
goto theend;
|
||||
}
|
||||
# undef OPTARG_HAS
|
||||
cmd = skipwhite(p);
|
||||
}
|
||||
if (*cmd == NUL)
|
||||
@ -933,6 +954,7 @@ free_unused_terminals()
|
||||
free_scrollback(term);
|
||||
|
||||
term_free_vterm(term);
|
||||
vim_free(term->tl_api);
|
||||
vim_free(term->tl_title);
|
||||
#ifdef FEAT_SESSION
|
||||
vim_free(term->tl_command);
|
||||
@ -3769,6 +3791,15 @@ handle_drop_command(listitem_T *item)
|
||||
vim_free(tofree);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE if "func" starts with "pat" and "pat" isn't empty.
|
||||
*/
|
||||
static int
|
||||
is_permitted_term_api(char_u *func, char_u *pat)
|
||||
{
|
||||
return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles a function call from the job running in a terminal.
|
||||
* "item" is the function name, "item->li_next" has the arguments.
|
||||
@ -3788,9 +3819,9 @@ handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
|
||||
}
|
||||
func = tv_get_string(&item->li_tv);
|
||||
|
||||
if (STRNCMP(func, "Tapi_", 5) != 0)
|
||||
if (!is_permitted_term_api(func, term->tl_api))
|
||||
{
|
||||
ch_log(channel, "Invalid function name: %s", func);
|
||||
ch_log(channel, "Unpermitted function: %s", func);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5545,6 +5576,27 @@ f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* "term_setapi(buf, api)" function
|
||||
*/
|
||||
void
|
||||
f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
|
||||
{
|
||||
buf_T *buf = term_get_buf(argvars, "term_setapi()");
|
||||
term_T *term;
|
||||
char_u *api;
|
||||
|
||||
if (buf == NULL)
|
||||
return;
|
||||
term = buf->b_term;
|
||||
vim_free(term->tl_api);
|
||||
api = tv_get_string_chk(&argvars[1]);
|
||||
if (api != NULL)
|
||||
term->tl_api = vim_strsave(api);
|
||||
else
|
||||
term->tl_api = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* "term_setrestore(buf, command)" function
|
||||
*/
|
||||
@ -5608,7 +5660,7 @@ f_term_start(typval_T *argvars, typval_T *rettv)
|
||||
+ JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
|
||||
+ JO2_CWD + JO2_ENV + JO2_EOF_CHARS
|
||||
+ JO2_NORESTORE + JO2_TERM_KILL
|
||||
+ JO2_ANSI_COLORS + JO2_TTY_TYPE) == FAIL)
|
||||
+ JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
|
||||
return;
|
||||
|
||||
buf = term_start(&argvars[0], NULL, &opt, 0);
|
||||
|
@ -61,11 +61,16 @@ func RunVimInTerminal(arguments, options)
|
||||
|
||||
let cmd = GetVimCommandCleanTerm() .. a:arguments
|
||||
|
||||
let buf = term_start(cmd, {
|
||||
let options = {
|
||||
\ 'curwin': 1,
|
||||
\ 'term_rows': rows,
|
||||
\ 'term_cols': cols,
|
||||
\ })
|
||||
\ }
|
||||
" Accept other options whose name starts with 'term_'.
|
||||
call extend(options, filter(copy(a:options), 'v:key =~# "^term_"'))
|
||||
|
||||
let buf = term_start(cmd, options)
|
||||
|
||||
if &termwinsize == ''
|
||||
" in the GUI we may end up with a different size, try to set it.
|
||||
if term_getsize(buf) != [rows, cols]
|
||||
|
@ -1353,30 +1353,90 @@ endfunc
|
||||
func Test_terminal_api_call()
|
||||
CheckRunVimInTerminal
|
||||
|
||||
call ch_logfile('logfile', 'w')
|
||||
unlet! g:called_bufnum
|
||||
unlet! g:called_arg
|
||||
|
||||
call WriteApiCall('Tapi_TryThis')
|
||||
|
||||
" Default
|
||||
let buf = RunVimInTerminal('-S Xscript', {})
|
||||
call WaitFor({-> exists('g:called_bufnum')})
|
||||
call assert_equal(buf, g:called_bufnum)
|
||||
call assert_equal(['hello', 123], g:called_arg)
|
||||
|
||||
call StopVimInTerminal(buf)
|
||||
|
||||
unlet! g:called_bufnum
|
||||
unlet! g:called_arg
|
||||
|
||||
" Enable explicitly
|
||||
let buf = RunVimInTerminal('-S Xscript', {'term_api': 'Tapi_Try'})
|
||||
call WaitFor({-> exists('g:called_bufnum')})
|
||||
call assert_equal(buf, g:called_bufnum)
|
||||
call assert_equal(['hello', 123], g:called_arg)
|
||||
call StopVimInTerminal(buf)
|
||||
|
||||
unlet! g:called_bufnum
|
||||
unlet! g:called_arg
|
||||
|
||||
func! ApiCall_TryThis(bufnum, arg)
|
||||
let g:called_bufnum2 = a:bufnum
|
||||
let g:called_arg2 = a:arg
|
||||
endfunc
|
||||
|
||||
call WriteApiCall('ApiCall_TryThis')
|
||||
|
||||
" Use prefix match
|
||||
let buf = RunVimInTerminal('-S Xscript', {'term_api': 'ApiCall_'})
|
||||
call WaitFor({-> exists('g:called_bufnum2')})
|
||||
call assert_equal(buf, g:called_bufnum2)
|
||||
call assert_equal(['hello', 123], g:called_arg2)
|
||||
call StopVimInTerminal(buf)
|
||||
|
||||
unlet! g:called_bufnum2
|
||||
unlet! g:called_arg2
|
||||
|
||||
call delete('Xscript')
|
||||
unlet g:called_bufnum
|
||||
unlet g:called_arg
|
||||
delfunction! ApiCall_TryThis
|
||||
unlet! g:called_bufnum2
|
||||
unlet! g:called_arg2
|
||||
endfunc
|
||||
|
||||
func Test_terminal_api_call_fails()
|
||||
CheckRunVimInTerminal
|
||||
|
||||
call WriteApiCall('TryThis')
|
||||
call ch_logfile('Xlog', 'w')
|
||||
let buf = RunVimInTerminal('-S Xscript', {})
|
||||
call WaitForAssert({-> assert_match('Invalid function name: TryThis', string(readfile('Xlog')))})
|
||||
func! TryThis(bufnum, arg)
|
||||
let g:called_bufnum3 = a:bufnum
|
||||
let g:called_arg3 = a:arg
|
||||
endfunc
|
||||
|
||||
call WriteApiCall('TryThis')
|
||||
|
||||
unlet! g:called_bufnum3
|
||||
unlet! g:called_arg3
|
||||
|
||||
" Not permitted
|
||||
call ch_logfile('Xlog', 'w')
|
||||
let buf = RunVimInTerminal('-S Xscript', {'term_api': ''})
|
||||
call WaitForAssert({-> assert_match('Unpermitted function: TryThis', string(readfile('Xlog')))})
|
||||
call assert_false(exists('g:called_bufnum3'))
|
||||
call assert_false(exists('g:called_arg3'))
|
||||
call StopVimInTerminal(buf)
|
||||
|
||||
" No match
|
||||
call ch_logfile('Xlog', 'w')
|
||||
let buf = RunVimInTerminal('-S Xscript', {'term_api': 'TryThat'})
|
||||
call WaitFor({-> string(readfile('Xlog')) =~ 'Unpermitted function: TryThis'})
|
||||
call assert_false(exists('g:called_bufnum3'))
|
||||
call assert_false(exists('g:called_arg3'))
|
||||
call StopVimInTerminal(buf)
|
||||
|
||||
call delete('Xscript')
|
||||
call ch_logfile('', '')
|
||||
call ch_logfile('')
|
||||
call delete('Xlog')
|
||||
delfunction! TryThis
|
||||
unlet! g:called_bufnum3
|
||||
unlet! g:called_arg3
|
||||
endfunc
|
||||
|
||||
let s:caught_e937 = 0
|
||||
@ -2061,3 +2121,34 @@ func Test_terminal_altscreen()
|
||||
exe buf . "bwipe!"
|
||||
call delete('Xtext')
|
||||
endfunc
|
||||
|
||||
func Test_terminal_setapi_and_call()
|
||||
if !CanRunVimInTerminal()
|
||||
return
|
||||
endif
|
||||
|
||||
call WriteApiCall('Tapi_TryThis')
|
||||
call ch_logfile('Xlog', 'w')
|
||||
|
||||
unlet! g:called_bufnum
|
||||
unlet! g:called_arg
|
||||
|
||||
let buf = RunVimInTerminal('-S Xscript', {'term_api': 0})
|
||||
call WaitForAssert({-> assert_match('Unpermitted function: Tapi_TryThis', string(readfile('Xlog')))})
|
||||
call assert_false(exists('g:called_bufnum'))
|
||||
call assert_false(exists('g:called_arg'))
|
||||
|
||||
call term_setapi(buf, 'Tapi_TryThis')
|
||||
call term_sendkeys(buf, ":set notitle\<CR>")
|
||||
call term_sendkeys(buf, ":source Xscript\<CR>")
|
||||
call WaitFor({-> exists('g:called_bufnum')})
|
||||
call assert_equal(buf, g:called_bufnum)
|
||||
call assert_equal(['hello', 123], g:called_arg)
|
||||
call StopVimInTerminal(buf)
|
||||
|
||||
call delete('Xscript')
|
||||
call ch_logfile('')
|
||||
call delete('Xlog')
|
||||
unlet! g:called_bufnum
|
||||
unlet! g:called_arg
|
||||
endfunc
|
||||
|
@ -757,6 +757,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
2080,
|
||||
/**/
|
||||
2079,
|
||||
/**/
|
||||
|
Loading…
x
Reference in New Issue
Block a user