]> granicus.if.org Git - vim/commitdiff
patch 8.1.2080: the terminal API is limited and can't be disabled v8.1.2080
authorBram Moolenaar <Bram@vim.org>
Thu, 26 Sep 2019 21:08:54 +0000 (23:08 +0200)
committerBram Moolenaar <Bram@vim.org>
Thu, 26 Sep 2019 21:08:54 +0000 (23:08 +0200)
Problem:    The terminal API is limited and can't be disabled.
Solution:   Add term_setapi() to set the function prefix. (Ozaki Kiichi,
            closes #2907)

runtime/doc/eval.txt
runtime/doc/terminal.txt
src/channel.c
src/evalfunc.c
src/proto/terminal.pro
src/structs.h
src/terminal.c
src/testdir/term_util.vim
src/testdir/test_terminal.vim
src/version.c

index 6bbfa26d6826a2d821b701d2963cd601589c8152..f8f7e484431a431a58d90fe7ea5dd6dbe77f652f 100644 (file)
@@ -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
index c0caa6797168c7e9c6f170c92e431f36e3cd63e3..ab26631e276204a2cf31212bafe6fa5565f2e751 100644 (file)
@@ -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: >
index 34ee02a662a14d0535b612133c4fbf273bc46d73..e42c9575f94bf17ecfe6f7e6fa81da9699ebd029 100644 (file)
@@ -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)
            {
index 04e131391682a1953bd37642fe803b0e22841427..c9d9287548699f1876806560a2e03b71d6de7eba 100644 (file)
@@ -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},
index 0527aa88c9966e1258d3255609ec10dcf122bf4e..52bb1b8d4a3713d8253ee24f1730732e46552148 100644 (file)
@@ -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);
index e3c73cddda5a2bf7835094b8dd157943fb312fbb..9d2ec16390cec7aabc1ba563c5705d937991a628 100644 (file)
@@ -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;
 
index f1d89ab9e4bc7692122eec9a7c411c52f88a67db..4f2b8bb25caf4b41d1d8ed927cb1a3a5b04942fc 100644 (file)
@@ -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);
index 1cfac0e9d0bcf466847b0f06183cb537597d11e9..4eaf6d8beedbf7971ad98ddec7ab3ea0cdc98c4d 100644 (file)
@@ -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]
index 814a989c8e409860d0b3777a20baee81258d3629..0041965f30c4546854ed24480d0b1379c45bc31f 100644 (file)
@@ -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
 
+  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', {})
-  call WaitForAssert({-> assert_match('Invalid function name: TryThis', string(readfile('Xlog')))})
+  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
index f7d7ede5ff062083ca1c5e14db4274d4e87fd848..44d4d04148e4f18b59c517bb267115ebc89e4dd0 100644 (file)
@@ -757,6 +757,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2080,
 /**/
     2079,
 /**/