To: vim_dev@googlegroups.com Subject: Patch 8.0.1641 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.0.1641 Problem: Job in terminal can't communicate with Vim. Solution: Add the terminal API. Files: src/terminal.c, src/buffer.c, src/testdir/test_terminal.vim, src/testdir/screendump.vim, runtime/doc/terminal.txt *** ../vim-8.0.1640/src/terminal.c 2018-03-23 22:10:26.164804315 +0100 --- src/terminal.c 2018-03-25 18:18:57.005331832 +0200 *************** *** 38,49 **** * in tl_scrollback are no longer used. * * TODO: ! * - Win32: In the GUI use a terminal emulator for :!cmd. * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in * the GUI. ! * - Some way for the job running in the terminal to send a :drop command back ! * to the Vim running the terminal. Should be usable by a simple shell or ! * python script. * - implement term_setsize() * - Copy text in the vterm to the Vim buffer once in a while, so that * completion works. --- 38,48 ---- * in tl_scrollback are no longer used. * * TODO: ! * - For the "drop" command accept another argument for options. * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in * the GUI. ! * - Win32: Make terminal used for :!cmd in the GUI work better. Allow for ! * redirection. * - implement term_setsize() * - Copy text in the vterm to the Vim buffer once in a while, so that * completion works. *************** *** 3146,3151 **** --- 3145,3284 ---- } /* + * Handles a "drop" command from the job in the terminal. + * "item" is the file name, "item->li_next" may have options. + */ + static void + handle_drop_command(listitem_T *item) + { + char_u *fname = get_tv_string(&item->li_tv); + int bufnr; + win_T *wp; + tabpage_T *tp; + exarg_T ea; + + bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT); + FOR_ALL_TAB_WINDOWS(tp, wp) + { + if (wp->w_buffer->b_fnum == bufnr) + { + /* buffer is in a window already, go there */ + goto_tabpage_win(tp, wp); + return; + } + } + + /* open in new window, like ":sbuffer N" */ + vim_memset(&ea, 0, sizeof(ea)); + ea.cmd = (char_u *)"sbuffer"; + goto_buffer(&ea, DOBUF_FIRST, FORWARD, bufnr); + } + + /* + * Handles a function call from the job running in a terminal. + * "item" is the function name, "item->li_next" has the arguments. + */ + static void + handle_call_command(term_T *term, channel_T *channel, listitem_T *item) + { + char_u *func; + typval_T argvars[2]; + typval_T rettv; + int doesrange; + + if (item->li_next == NULL) + { + ch_log(channel, "Missing function arguments for call"); + return; + } + func = get_tv_string(&item->li_tv); + + if (!ASCII_ISUPPER(*func)) + { + ch_log(channel, "Invalid function name: %s", func); + return; + } + + argvars[0].v_type = VAR_NUMBER; + argvars[0].vval.v_number = term->tl_buffer->b_fnum; + argvars[1] = item->li_next->li_tv; + if (call_func(func, STRLEN(func), &rettv, + 2, argvars, /* argv_func */ NULL, + /* firstline */ 1, /* lastline */ 1, + &doesrange, /* evaluate */ TRUE, + /* partial */ NULL, /* selfdict */ NULL) == OK) + { + clear_tv(&rettv); + ch_log(channel, "Function %s called", func); + } + else + ch_log(channel, "Calling function %s failed", func); + } + + /* + * Called by libvterm when it cannot recognize an OSC sequence. + * We recognize a terminal API command. + */ + static int + parse_osc(const char *command, size_t cmdlen, void *user) + { + term_T *term = (term_T *)user; + js_read_T reader; + typval_T tv; + channel_T *channel = term->tl_job == NULL ? NULL + : term->tl_job->jv_channel; + + /* We recognize only OSC 5 1 ; {command} */ + if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0) + return 0; /* not handled */ + + reader.js_buf = vim_strnsave((char_u *)command + 3, cmdlen - 3); + if (reader.js_buf == NULL) + return 1; + reader.js_fill = NULL; + reader.js_used = 0; + if (json_decode(&reader, &tv, 0) == OK + && tv.v_type == VAR_LIST + && tv.vval.v_list != NULL) + { + listitem_T *item = tv.vval.v_list->lv_first; + + if (item == NULL) + ch_log(channel, "Missing command"); + else + { + char_u *cmd = get_tv_string(&item->li_tv); + + item = item->li_next; + if (item == NULL) + ch_log(channel, "Missing argument for %s", cmd); + else if (STRCMP(cmd, "drop") == 0) + handle_drop_command(item); + else if (STRCMP(cmd, "call") == 0) + handle_call_command(term, channel, item); + else + ch_log(channel, "Invalid command received: %s", cmd); + } + } + else + ch_log(channel, "Invalid JSON received"); + + vim_free(reader.js_buf); + clear_tv(&tv); + return 1; + } + + static VTermParserCallbacks parser_fallbacks = { + NULL, /* text */ + NULL, /* control */ + NULL, /* escape */ + NULL, /* csi */ + parse_osc, /* osc */ + NULL, /* dcs */ + NULL /* resize */ + }; + + /* * Create a new vterm and initialize it. */ static void *************** *** 3153,3158 **** --- 3286,3292 ---- { VTerm *vterm; VTermScreen *screen; + VTermState *state; VTermValue value; vterm = vterm_new(rows, cols); *************** *** 3186,3193 **** #else value.boolean = 0; #endif ! vterm_state_set_termprop(vterm_obtain_state(vterm), ! VTERM_PROP_CURSORBLINK, &value); } /* --- 3320,3328 ---- #else value.boolean = 0; #endif ! state = vterm_obtain_state(vterm); ! vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value); ! vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term); } /* *** ../vim-8.0.1640/src/buffer.c 2018-03-23 22:39:27.321233962 +0100 --- src/buffer.c 2018-03-25 17:46:10.476283979 +0200 *************** *** 948,954 **** } } ! #if defined(FEAT_LISTCMDS) || defined(PROTO) /* * Go to another buffer. Handles the result of the ATTENTION dialog. */ --- 948,954 ---- } } ! #if defined(FEAT_LISTCMDS) || defined(FEAT_TERMINAL) || defined(PROTO) /* * Go to another buffer. Handles the result of the ATTENTION dialog. */ *** ../vim-8.0.1640/src/testdir/test_terminal.vim 2018-03-24 17:16:29.441976567 +0100 --- src/testdir/test_terminal.vim 2018-03-25 18:02:18.154736269 +0200 *************** *** 1023,1025 **** --- 1023,1101 ---- set laststatus& endfunc + + func Test_terminal_api_drop_newwin() + if !CanRunVimInTerminal() + return + endif + call assert_equal(1, winnr('$')) + + " Use the title termcap entries to output the escape sequence. + call writefile([ + \ 'exe "set t_ts=\]51; t_fs=\x07"', + \ 'let &titlestring = ''["drop","Xtextfile"]''', + \ 'redraw', + \ "set t_ts=", + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {}) + call WaitFor({-> bufnr('Xtextfile') > 0}) + call assert_equal('Xtextfile', expand('%:t')) + call assert_true(winnr('$') >= 3) + + call StopVimInTerminal(buf) + call delete('Xscript') + bwipe Xtextfile + endfunc + + func Test_terminal_api_drop_oldwin() + if !CanRunVimInTerminal() + return + endif + let firstwinid = win_getid() + split Xtextfile + let textfile_winid = win_getid() + call assert_equal(2, winnr('$')) + call win_gotoid(firstwinid) + + " Use the title termcap entries to output the escape sequence. + call writefile([ + \ 'exe "set t_ts=\]51; t_fs=\x07"', + \ 'let &titlestring = ''["drop","Xtextfile"]''', + \ 'redraw', + \ "set t_ts=", + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {}) + call WaitFor({-> expand('%:t') =='Xtextfile'}) + call assert_equal(textfile_winid, win_getid()) + + call StopVimInTerminal(buf) + call delete('Xscript') + bwipe Xtextfile + endfunc + + func TryThis(bufnum, arg) + let g:called_bufnum = a:bufnum + let g:called_arg = a:arg + endfunc + + func Test_terminal_api_call() + if !CanRunVimInTerminal() + return + endif + " Use the title termcap entries to output the escape sequence. + call writefile([ + \ 'exe "set t_ts=\]51; t_fs=\x07"', + \ 'let &titlestring = ''["call","TryThis",["hello",123]]''', + \ 'redraw', + \ "set t_ts=", + \ ], 'Xscript') + 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) + call delete('Xscript') + unlet g:called_bufnum + unlet g:called_arg + endfunc *** ../vim-8.0.1640/src/testdir/screendump.vim 2018-03-24 17:56:09.201107418 +0100 --- src/testdir/screendump.vim 2018-03-25 18:05:37.257603139 +0200 *************** *** 38,45 **** endif " Make a horizontal and vertical split, so that we can get exactly the right ! " size terminal window. Works only when we currently have one window. ! call assert_equal(1, winnr('$')) split vsplit --- 38,45 ---- endif " Make a horizontal and vertical split, so that we can get exactly the right ! " size terminal window. Works only when the current window is full width. ! call assert_equal(&columns, winwidth(0)) split vsplit *** ../vim-8.0.1640/runtime/doc/terminal.txt 2018-03-16 22:54:47.342973089 +0100 --- runtime/doc/terminal.txt 2018-03-25 18:15:13.446494414 +0200 *************** *** 1,4 **** ! *terminal.txt* For Vim version 8.0. Last change: 2018 Feb 20 VIM REFERENCE MANUAL by Bram Moolenaar --- 1,4 ---- ! *terminal.txt* For Vim version 8.0. Last change: 2018 Mar 25 VIM REFERENCE MANUAL by Bram Moolenaar *************** *** 23,34 **** Session |terminal-session| Unix |terminal-unix| MS-Windows |terminal-ms-windows| ! 2. Remote testing |terminal-testing| ! 3. Diffing screen dumps |terminal-diff| Writing a screen dump test for Vim |terminal-dumptest| Creating a screen dump |terminal-screendump| Comparing screen dumps |terminal-diffscreendump| ! 4. Debugging |terminal-debug| Starting |termdebug-starting| Example session |termdebug-example| Stepping through code |termdebug-stepping| --- 23,38 ---- Session |terminal-session| Unix |terminal-unix| MS-Windows |terminal-ms-windows| ! 2. Terminal communication |terminal-communication| ! Vim to job: term_sendkeys() |terminal-to-job| ! Job to Vim: JSON API |terminal-api| ! Using the client-server feature |terminal-client-server| ! 3. Remote testing |terminal-testing| ! 4. Diffing screen dumps |terminal-diff| Writing a screen dump test for Vim |terminal-dumptest| Creating a screen dump |terminal-screendump| Comparing screen dumps |terminal-diffscreendump| ! 5. Debugging |terminal-debug| Starting |termdebug-starting| Example session |termdebug-example| Stepping through code |termdebug-stepping| *************** *** 99,107 **** To change the keys you type use terminal mode mappings, see |:tmap|. These are defined like any mapping, but apply only when typing keys that are ! sent to the job running in the terminal. For example, to make Escape switch to Terminal-Normal mode: > tnoremap N < *options-in-terminal* After opening the terminal window and setting 'buftype' to "terminal" the BufWinEnter autocommand event is triggered. This makes it possible to set --- 103,116 ---- To change the keys you type use terminal mode mappings, see |:tmap|. These are defined like any mapping, but apply only when typing keys that are ! sent to the job running in the terminal. For example, to make F1 switch to Terminal-Normal mode: > + tnoremap N + You can use Esc, but you need to make sure it won't cause other keys to + break: > tnoremap N + set notimeout ttimeout timeoutlen=100 + < *options-in-terminal* After opening the terminal window and setting 'buftype' to "terminal" the BufWinEnter autocommand event is triggered. This makes it possible to set *************** *** 350,364 **** COLORS number of colors, 't_Co' (256*256*256 in the GUI) VIM_SERVERNAME v:servername - The |client-server| feature can be used to communicate with the Vim instance - where the job was started. This only works when v:servername is not empty. - If needed you can set it with: > - call remote_startserver('vim-server') - - In the job you can then do something like: > - vim --servername $VIM_SERVERNAME --remote +123 some_file.c - This will open the file "some_file.c" and put the cursor on line 123. - MS-Windows ~ *terminal-ms-windows* --- 359,364 ---- *************** *** 384,390 **** VIM_SERVERNAME v:servername ============================================================================== ! 2. Remote testing *terminal-testing* Most Vim tests execute a script inside Vim. For some tests this does not work, running the test interferes with the code being tested. To avoid this --- 384,474 ---- VIM_SERVERNAME v:servername ============================================================================== ! 2. Terminal communication *terminal-communication* ! ! There are several ways to communicate with the job running in a terminal: ! - Use |term_sendkeys()| to send text and escape sequences from Vim to the job. ! - Use the JSON API to send encoded commands from the job to Vim. ! - Use the |client-server| mechanism. This works on machines with an X server ! and on MS-Windows. ! ! ! Vim to job: term_sendkeys() ~ ! *terminal-to-job* ! This allows for remote controlling the job running in the terminal. It is a ! one-way mechanism. The job can update the display to signal back to Vim. ! For example, if a shell is running in a terminal, you can do: > ! call term_sendkeys(buf, "ls *.java\") ! ! This requires for the job to be in the right state where it will do the right ! thing when receiving the keys. For the above example, the shell must be ! waiting for a command to be typed. ! ! For a job that was written for the purpose, you can use the JSON API escape ! sequence in the other direction. E.g.: > ! call term_sendkeys(buf, "\]51;["response"]\x07") ! ! ! Job to Vim: JSON API ~ ! *terminal-api* ! The job can send JSON to Vim, using a special escape sequence. The JSON ! encodes a command that Vim understands. Example of such a message: > ! ]51;["drop", "README.md"]<07> ! ! The body is always a list, making it easy to find the end: ]<07>. ! The ]51;msg<07> sequence is reserved by xterm for "Emacs shell", which is ! similar to what we are doing here. ! ! Currently supported commands: ! ! call {funcname} {argument} ! ! Call a user defined function with [argument]. The function is ! called with the buffer number of the terminal and the decoded ! argument. The user function must sanity check the argument. ! The function can use |term_sendkeys()| to send back a reply. ! Example in JSON: > ! ["call", "Impression", ["play", 14]] ! < Calls a function defined like this: > ! function Impression(bufnum, arglist) ! if len(a:arglist) == 2 ! echo "impression " . a:arglist[0] ! echo "count " . a:arglist[1] ! endif ! endfunc ! < ! drop {filename} ! ! Let Vim open a file, like the `:drop` command. If {filename} ! is already open in a window, switch to that window. Otherwise ! open a new window to edit {filename}. ! Example in JSON: > ! ["drop", "path/file.txt", {"ff": "dos"}] ! ! A trick to have Vim send this escape sequence: > ! exe "set t_ts=\]51; t_fs=\x07" ! let &titlestring = '["call","TryThis",["hello",123]]' ! redraw ! set t_ts& t_fs& ! ! Rationale: Why not allow for any command or expression? Because that might ! create a security problem. ! ! ! Using the client-server feature ~ ! *terminal-client-server* ! This only works when v:servername is not empty. If needed you can set it, ! before opening the terminal, with: > ! call remote_startserver('vim-server') ! ! $VIM_SERVERNAME is set in the terminal to pass on the server name. ! ! In the job you can then do something like: > ! vim --servername $VIM_SERVERNAME --remote +123 some_file.c ! This will open the file "some_file.c" and put the cursor on line 123. ! ! ============================================================================== ! 3. Remote testing *terminal-testing* Most Vim tests execute a script inside Vim. For some tests this does not work, running the test interferes with the code being tested. To avoid this *************** *** 399,405 **** ============================================================================== ! 3. Diffing screen dumps *terminal-diff* In some cases it can be bothersome to test that Vim displays the right characters on the screen. E.g. with syntax highlighting. To make this --- 483,489 ---- ============================================================================== ! 4. Diffing screen dumps *terminal-diff* In some cases it can be bothersome to test that Vim displays the right characters on the screen. E.g. with syntax highlighting. To make this *************** *** 408,414 **** Vim uses the window size, text, color and other attributes as displayed. The Vim screen size, font and other properties do not matter. Therefore this ! mechanism is portable across systems. A convential screenshot would reflect all differences, including font size and family. --- 492,498 ---- Vim uses the window size, text, color and other attributes as displayed. The Vim screen size, font and other properties do not matter. Therefore this ! mechanism is portable across systems. A conventional screenshot would reflect all differences, including font size and family. *************** *** 483,495 **** 3. The contents of the second dump You can usually see what differs in the second part. Use the 'ruler' to ! relate it to the postion in the first or second dump. ! Alternatively, press "s" to swap the first and second dump. Do this everal times so that you can spot the difference in the context of the text. ============================================================================== ! 4. Debugging *terminal-debug* The Terminal debugging plugin can be used to debug a program with gdb and view the source code in a Vim window. Since this is completely contained inside --- 567,579 ---- 3. The contents of the second dump You can usually see what differs in the second part. Use the 'ruler' to ! relate it to the position in the first or second dump. ! Alternatively, press "s" to swap the first and second dump. Do this several times so that you can spot the difference in the context of the text. ============================================================================== ! 5. Debugging *terminal-debug* The Terminal debugging plugin can be used to debug a program with gdb and view the source code in a Vim window. Since this is completely contained inside *************** *** 659,665 **** let termdebugger = "mygdb" < *gdb-version* Only debuggers fully compatible with gdb will work. Vim uses the GDB/MI ! interface. This probably requires gdb version 7.12. if you get this error: Undefined command: "new-ui". Try "help".~ Then your gdb is too old. --- 743,750 ---- let termdebugger = "mygdb" < *gdb-version* Only debuggers fully compatible with gdb will work. Vim uses the GDB/MI ! interface. The "new-ui" command requires gdb version 7.12 or later. if you ! get this error: Undefined command: "new-ui". Try "help".~ Then your gdb is too old. *** ../vim-8.0.1640/src/version.c 2018-03-25 17:12:53.927703230 +0200 --- src/version.c 2018-03-25 18:18:15.525550221 +0200 *************** *** 768,769 **** --- 768,771 ---- { /* Add new patch number below this line */ + /**/ + 1641, /**/ -- Be thankful to be in a traffic jam, because it means you own a car. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///