mirror of
https://github.com/vim/vim.git
synced 2025-09-25 03:54:15 -04:00
patch 8.0.1641: job in terminal can't communicate with Vim
Problem: Job in terminal can't communicate with Vim. Solution: Add the terminal API.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
*terminal.txt* For Vim version 8.0. Last change: 2018 Mar 16
|
*terminal.txt* For Vim version 8.0. Last change: 2018 Mar 25
|
||||||
|
|
||||||
|
|
||||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||||
@@ -23,12 +23,16 @@ If the result is "1" you have it.
|
|||||||
Session |terminal-session|
|
Session |terminal-session|
|
||||||
Unix |terminal-unix|
|
Unix |terminal-unix|
|
||||||
MS-Windows |terminal-ms-windows|
|
MS-Windows |terminal-ms-windows|
|
||||||
2. Remote testing |terminal-testing|
|
2. Terminal communication |terminal-communication|
|
||||||
3. Diffing screen dumps |terminal-diff|
|
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|
|
Writing a screen dump test for Vim |terminal-dumptest|
|
||||||
Creating a screen dump |terminal-screendump|
|
Creating a screen dump |terminal-screendump|
|
||||||
Comparing screen dumps |terminal-diffscreendump|
|
Comparing screen dumps |terminal-diffscreendump|
|
||||||
4. Debugging |terminal-debug|
|
5. Debugging |terminal-debug|
|
||||||
Starting |termdebug-starting|
|
Starting |termdebug-starting|
|
||||||
Example session |termdebug-example|
|
Example session |termdebug-example|
|
||||||
Stepping through code |termdebug-stepping|
|
Stepping through code |termdebug-stepping|
|
||||||
@@ -355,15 +359,6 @@ Environment variables are used to pass information to the running job:
|
|||||||
COLORS number of colors, 't_Co' (256*256*256 in the GUI)
|
COLORS number of colors, 't_Co' (256*256*256 in the GUI)
|
||||||
VIM_SERVERNAME v:servername
|
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 ~
|
MS-Windows ~
|
||||||
*terminal-ms-windows*
|
*terminal-ms-windows*
|
||||||
@@ -389,7 +384,91 @@ Environment variables are used to pass information to the running job:
|
|||||||
VIM_SERVERNAME v:servername
|
VIM_SERVERNAME v:servername
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
2. Remote testing *terminal-testing*
|
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\<CR>")
|
||||||
|
|
||||||
|
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, "\<Esc>]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: >
|
||||||
|
<Esc>]51;["drop", "README.md"]<07>
|
||||||
|
|
||||||
|
The body is always a list, making it easy to find the end: ]<07>.
|
||||||
|
The <Esc>]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=\<Esc>]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
|
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
|
work, running the test interferes with the code being tested. To avoid this
|
||||||
@@ -404,7 +483,7 @@ term_scrape() inspect terminal screen
|
|||||||
|
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
3. Diffing screen dumps *terminal-diff*
|
4. Diffing screen dumps *terminal-diff*
|
||||||
|
|
||||||
In some cases it can be bothersome to test that Vim displays the right
|
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
|
characters on the screen. E.g. with syntax highlighting. To make this
|
||||||
@@ -494,7 +573,7 @@ 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.
|
times so that you can spot the difference in the context of the text.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
4. Debugging *terminal-debug*
|
5. Debugging *terminal-debug*
|
||||||
|
|
||||||
The Terminal debugging plugin can be used to debug a program with gdb and view
|
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
|
the source code in a Vim window. Since this is completely contained inside
|
||||||
|
@@ -948,7 +948,7 @@ clear_wininfo(buf_T *buf)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(FEAT_LISTCMDS) || defined(PROTO)
|
#if defined(FEAT_LISTCMDS) || defined(FEAT_TERMINAL) || defined(PROTO)
|
||||||
/*
|
/*
|
||||||
* Go to another buffer. Handles the result of the ATTENTION dialog.
|
* Go to another buffer. Handles the result of the ATTENTION dialog.
|
||||||
*/
|
*/
|
||||||
|
147
src/terminal.c
147
src/terminal.c
@@ -38,12 +38,11 @@
|
|||||||
* in tl_scrollback are no longer used.
|
* in tl_scrollback are no longer used.
|
||||||
*
|
*
|
||||||
* TODO:
|
* TODO:
|
||||||
* - Win32: In the GUI use a terminal emulator for :!cmd.
|
* - 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
|
* - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in
|
||||||
* the GUI.
|
* the GUI.
|
||||||
* - Some way for the job running in the terminal to send a :drop command back
|
* - Win32: Make terminal used for :!cmd in the GUI work better. Allow for
|
||||||
* to the Vim running the terminal. Should be usable by a simple shell or
|
* redirection.
|
||||||
* python script.
|
|
||||||
* - implement term_setsize()
|
* - implement term_setsize()
|
||||||
* - Copy text in the vterm to the Vim buffer once in a while, so that
|
* - Copy text in the vterm to the Vim buffer once in a while, so that
|
||||||
* completion works.
|
* completion works.
|
||||||
@@ -3145,6 +3144,140 @@ init_default_colors(term_T *term)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
* Create a new vterm and initialize it.
|
||||||
*/
|
*/
|
||||||
@@ -3153,6 +3286,7 @@ create_vterm(term_T *term, int rows, int cols)
|
|||||||
{
|
{
|
||||||
VTerm *vterm;
|
VTerm *vterm;
|
||||||
VTermScreen *screen;
|
VTermScreen *screen;
|
||||||
|
VTermState *state;
|
||||||
VTermValue value;
|
VTermValue value;
|
||||||
|
|
||||||
vterm = vterm_new(rows, cols);
|
vterm = vterm_new(rows, cols);
|
||||||
@@ -3186,8 +3320,9 @@ create_vterm(term_T *term, int rows, int cols)
|
|||||||
#else
|
#else
|
||||||
value.boolean = 0;
|
value.boolean = 0;
|
||||||
#endif
|
#endif
|
||||||
vterm_state_set_termprop(vterm_obtain_state(vterm),
|
state = vterm_obtain_state(vterm);
|
||||||
VTERM_PROP_CURSORBLINK, &value);
|
vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
|
||||||
|
vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -38,8 +38,8 @@ func RunVimInTerminal(arguments, options)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
" Make a horizontal and vertical split, so that we can get exactly the right
|
" 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.
|
" size terminal window. Works only when the current window is full width.
|
||||||
call assert_equal(1, winnr('$'))
|
call assert_equal(&columns, winwidth(0))
|
||||||
split
|
split
|
||||||
vsplit
|
vsplit
|
||||||
|
|
||||||
|
@@ -1023,3 +1023,79 @@ func Test_terminal_dumpdiff_options()
|
|||||||
|
|
||||||
set laststatus&
|
set laststatus&
|
||||||
endfunc
|
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=\<Esc>]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=\<Esc>]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=\<Esc>]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
|
||||||
|
@@ -766,6 +766,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1641,
|
||||||
/**/
|
/**/
|
||||||
1640,
|
1640,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user