0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

patch 8.0.0804: terminal window functions not yet implemented

Problem:    Terminal window functions not yet implemented.
Solution:   Implement several functions. Add a first test. (Yasuhiro
            Matsumoto, closes #1871)
This commit is contained in:
Bram Moolenaar 2017-07-29 20:15:08 +02:00
parent 70229f951f
commit c6df10e5d3
9 changed files with 579 additions and 91 deletions

View File

@ -1,4 +1,4 @@
*eval.txt* For Vim version 8.0. Last change: 2017 Jul 28
*eval.txt* For Vim version 8.0. Last change: 2017 Jul 29
VIM REFERENCE MANUAL by Bram Moolenaar
@ -2369,12 +2369,15 @@ tagfiles() List tags files used
tan({expr}) Float tangent of {expr}
tanh({expr}) Float hyperbolic tangent of {expr}
tempname() String name for a temporary file
term_getsize() Dict get the size of a terminal
term_open() Job open a terminal window and run a job
term_scrape() List inspect terminal screen
term_sendkeys() Number send keystrokes to a terminal
term_setsize() Number set the size of a terminal
term_wait() Number wait for screen to be updated
term_getattr({attr}, {what} Number get the value of attribute {what}
term_getjob({buf}) Job get the job associated with a terminal
term_getline({buf}, {row}) String get a line of text from a terminal
term_getsize({buf}) List get the size 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_start({cmd}, {options}) Job open a terminal window and run a job
term_wait({buf}) Number wait for screen to be updated
test_alloc_fail({id}, {countdown}, {repeat})
none make memory allocation fail
test_autochdir() none enable 'autochdir' during startup
@ -7898,23 +7901,72 @@ tempname() *tempname()* *temp-file-name*
For MS-Windows forward slashes are used when the 'shellslash'
option is set or when 'shellcmdflag' starts with '-'.
term_getsize() *term_getsize()*
Get the size of a terminal. NOT IMPLEMENTED YET
term_getattr({attr}, {what}) *term_getattr()*
Given {attr}, a value returned by term_scrape() in the "attr"
item, return whether {what} is on. {what} can be one of:
bold
italic
underline
strike
reverse
term_open() *term_open()*
Open a terminal window and run a job. NOT IMPLEMENTED YET
term_getjob({buf}) *term_getjob()*
Get the Job associated with terminal window {buf}.
{buf} is used as with |term_getsize()|.
term_scrape() *term_scrape()*
Inspect terminal screen. NOT IMPLEMENTED YET
term_getline({buf}, {row}) *term_getline()*
Get a line of text from the terminal window of {buf}.
{buf} is used as with |term_getsize()|.
term_sendkeys() *term_sendkeys()*
Send keystrokes to a terminal. NOT IMPLEMENTED YET
The first line has {row} zero. When {row} is invalid an empty
string is returned.
term_setsize() *term_setsize()*
Set the size of a terminal. NOT IMPLEMENTED YET
term_getsize({buf}) *term_getsize()*
Get the size of terminal {buf}. Returns a list with two
numbers: [rows, cols]. This is the size of the terminal, not
the window containing the terminal.
term_wait() *term_wait()*
Wait for screen to be updated. NOT IMPLEMENTED YET
{buf} must be the buffer number of a terminal window. If the
buffer does not exist or is not a terminal window, an empty
list is returned.
term_list(}) *term_list()*
Return a list with the buffer numbers of all buffers for
terminal windows.
term_scrape({buf}, {row}) *term_scrape()*
Get the contents of {row} of terminal screen of {buf}.
For {buf} see |term_getsize()|.
The first {row} is zero. When {row} is invalid an empty list
is returned.
Return a List containing a Dict for each screen cell:
"chars" character(s) at the cell
"fg" foreground color as #rrggbb
"bg" background color as #rrggbb
"attr" attributes of the cell, use term_getattr()
to get the individual flags
"width" cell width: 1 or 2
term_sendkeys({buf}, {keys}) *term_sendkeys()*
Send keystrokes {keys} to terminal {buf}.
{buf} is used as with |term_getsize()|.
{keys} are translated as key sequences. For example, "\<c-x>"
means the character CTRL-X.
term_start({cmd}, {options}) *term_start()*
Open a terminal window and run {cmd} in it.
Returns the buffer number of the terminal window.
When opening the window fails zero is returned.
{options} are not implemented yet.
term_wait({buf}) *term_wait()*
Wait for pending updates of {buf} to be handled.
{buf} is used as with |term_getsize()|.
test_alloc_fail({id}, {countdown}, {repeat}) *test_alloc_fail()*
This is for testing: If the memory allocation with {id} is

View File

@ -2256,6 +2256,7 @@ test_arglist \
test_tagjump \
test_taglist \
test_tcl \
test_terminal \
test_textobjects \
test_timers \
test_true_false \

View File

@ -830,6 +830,17 @@ static struct fst
{"tanh", 1, 1, f_tanh},
#endif
{"tempname", 0, 0, f_tempname},
#ifdef FEAT_TERMINAL
{"term_getattr", 2, 2, f_term_getattr},
{"term_getjob", 1, 1, f_term_getjob},
{"term_getline", 2, 2, f_term_getline},
{"term_getsize", 1, 1, f_term_getsize},
{"term_list", 0, 0, f_term_list},
{"term_scrape", 2, 2, f_term_scrape},
{"term_sendkeys", 2, 2, f_term_sendkeys},
{"term_start", 1, 2, f_term_start},
{"term_wait", 1, 1, f_term_wait},
#endif
{"test_alloc_fail", 3, 3, f_test_alloc_fail},
{"test_autochdir", 0, 0, f_test_autochdir},
{"test_garbagecollect_now", 0, 0, f_test_garbagecollect_now},
@ -1540,7 +1551,7 @@ buflist_find_by_name(char_u *name, int curtab_only)
/*
* Get buffer by number or pattern.
*/
static buf_T *
buf_T *
get_buf_tv(typval_T *tv, int curtab_only)
{
char_u *name = tv->vval.v_string;

View File

@ -1,4 +1,5 @@
/* evalfunc.c */
buf_T* get_buf_tv(typval_T *tv, int curtab_only);
char_u *get_function_name(expand_T *xp, int idx);
char_u *get_expr_name(expand_T *xp, int idx);
int find_internal_func(char_u *name);

View File

@ -3,6 +3,7 @@ void ex_terminal(exarg_T *eap);
void free_terminal(buf_T *buf);
void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel);
int terminal_loop(void);
void term_job_ended(job_T *job);
void term_channel_closed(channel_T *ch);
int term_update_window(win_T *wp);
int term_is_finished(buf_T *buf);
@ -10,4 +11,13 @@ void term_change_in_curbuf(void);
int term_get_attr(buf_T *buf, linenr_T lnum, int col);
char_u *term_get_status_text(term_T *term);
int set_ref_in_term(int copyID);
void f_term_getattr(typval_T *argvars, typval_T *rettv);
void f_term_getjob(typval_T *argvars, typval_T *rettv);
void f_term_getline(typval_T *argvars, typval_T *rettv);
void f_term_getsize(typval_T *argvars, typval_T *rettv);
void f_term_list(typval_T *argvars, typval_T *rettv);
void f_term_start(typval_T *argvars, typval_T *rettv);
void f_term_scrape(typval_T *argvars, typval_T *rettv);
void f_term_sendkeys(typval_T *argvars, typval_T *rettv);
void f_term_wait(typval_T *argvars, typval_T *rettv);
/* vim: set ft=c : */

View File

@ -54,14 +54,6 @@
* - support minimal size when 'termsize' is empty?
* - implement "term" for job_start(): more job options when starting a
* terminal.
* - implement term_list() list of buffers with a terminal
* - implement term_getsize(buf)
* - implement term_setsize(buf)
* - implement term_sendkeys(buf, keys) send keystrokes to a terminal
* - implement term_wait(buf) wait for screen to be updated
* - implement term_scrape(buf, row) inspect terminal screen
* - implement term_open(command, options) open terminal window
* - implement term_getjob(buf)
* - when 'encoding' is not utf-8, or the job is using another encoding, setup
* conversions.
* - In the GUI use a terminal emulator for :!cmd.
@ -69,7 +61,7 @@
#include "vim.h"
#ifdef FEAT_TERMINAL
#if defined(FEAT_TERMINAL) || defined(PROTO)
#ifdef WIN3264
# define MIN(x,y) (x < y ? x : y)
@ -110,6 +102,7 @@ struct terminal_S {
int tl_dirty_row_end; /* row below last one to update */
garray_T tl_scrollback;
int tl_scrollback_scrolled;
pos_T tl_cursor;
int tl_cursor_visible;
@ -384,9 +377,9 @@ write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
* Return the number of bytes in "buf".
*/
static int
term_convert_key(int c, char *buf)
term_convert_key(term_T *term, int c, char *buf)
{
VTerm *vterm = curbuf->b_term->tl_vterm;
VTerm *vterm = term->tl_vterm;
VTermKey key = VTERM_KEY_NONE;
VTermModifier mod = VTERM_MOD_NONE;
@ -516,6 +509,76 @@ term_vgetc()
return c;
}
/*
* Send keys to terminal.
*/
static int
send_keys_to_term(term_T *term, int c, int typed)
{
char msg[KEY_BUF_LEN];
size_t len;
static int mouse_was_outside = FALSE;
int dragging_outside = FALSE;
/* Catch keys that need to be handled as in Normal mode. */
switch (c)
{
case NUL:
case K_ZERO:
if (typed)
stuffcharReadbuff(c);
return FAIL;
case K_IGNORE:
return FAIL;
case K_LEFTDRAG:
case K_MIDDLEDRAG:
case K_RIGHTDRAG:
case K_X1DRAG:
case K_X2DRAG:
dragging_outside = mouse_was_outside;
/* FALLTHROUGH */
case K_LEFTMOUSE:
case K_LEFTMOUSE_NM:
case K_LEFTRELEASE:
case K_LEFTRELEASE_NM:
case K_MIDDLEMOUSE:
case K_MIDDLERELEASE:
case K_RIGHTMOUSE:
case K_RIGHTRELEASE:
case K_X1MOUSE:
case K_X1RELEASE:
case K_X2MOUSE:
case K_X2RELEASE:
if (mouse_row < W_WINROW(curwin)
|| mouse_row >= (W_WINROW(curwin) + curwin->w_height)
|| mouse_col < W_WINCOL(curwin)
|| mouse_col >= W_ENDCOL(curwin)
|| dragging_outside)
{
/* click outside the current window */
if (typed)
{
stuffcharReadbuff(c);
mouse_was_outside = TRUE;
}
return FAIL;
}
}
if (typed)
mouse_was_outside = FALSE;
/* Convert the typed key to a sequence of bytes for the job. */
len = term_convert_key(term, c, msg);
if (len > 0)
/* TODO: if FAIL is returned, stop? */
channel_send(term->tl_job->jv_channel, PART_IN,
(char_u *)msg, (int)len, NULL);
return OK;
}
/*
* Wait for input and send it to the job.
* Return when the start of a CTRL-W command is typed or anything else that
@ -526,11 +589,7 @@ term_vgetc()
int
terminal_loop(void)
{
char buf[KEY_BUF_LEN];
int c;
size_t len;
static int mouse_was_outside = FALSE;
int dragging_outside = FALSE;
int termkey = 0;
if (curbuf->b_term->tl_vterm == NULL || !term_job_running(curbuf->b_term))
@ -576,60 +635,41 @@ terminal_loop(void)
return OK;
}
}
/* Catch keys that need to be handled as in Normal mode. */
switch (c)
{
case NUL:
case K_ZERO:
stuffcharReadbuff(c);
return OK;
case K_IGNORE: continue;
case K_LEFTDRAG:
case K_MIDDLEDRAG:
case K_RIGHTDRAG:
case K_X1DRAG:
case K_X2DRAG:
dragging_outside = mouse_was_outside;
/* FALLTHROUGH */
case K_LEFTMOUSE:
case K_LEFTMOUSE_NM:
case K_LEFTRELEASE:
case K_LEFTRELEASE_NM:
case K_MIDDLEMOUSE:
case K_MIDDLERELEASE:
case K_RIGHTMOUSE:
case K_RIGHTRELEASE:
case K_X1MOUSE:
case K_X1RELEASE:
case K_X2MOUSE:
case K_X2RELEASE:
if (mouse_row < W_WINROW(curwin)
|| mouse_row >= (W_WINROW(curwin) + curwin->w_height)
|| mouse_col < W_WINCOL(curwin)
|| mouse_col >= W_ENDCOL(curwin)
|| dragging_outside)
{
/* click outside the current window */
stuffcharReadbuff(c);
mouse_was_outside = TRUE;
return OK;
}
}
mouse_was_outside = FALSE;
/* Convert the typed key to a sequence of bytes for the job. */
len = term_convert_key(c, buf);
if (len > 0)
/* TODO: if FAIL is returned, stop? */
channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
(char_u *)buf, (int)len, NULL);
if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
return OK;
}
return FAIL;
}
/*
* Called when a job has finished.
*/
void
term_job_ended(job_T *job)
{
term_T *term;
int did_one = FALSE;
for (term = first_term; term != NULL; term = term->tl_next)
if (term->tl_job == job)
{
vim_free(term->tl_title);
term->tl_title = NULL;
vim_free(term->tl_status_text);
term->tl_status_text = NULL;
redraw_buf_and_status_later(term->tl_buffer, VALID);
did_one = TRUE;
}
if (did_one)
redraw_statuslines();
if (curbuf->b_term != NULL)
{
if (curbuf->b_term->tl_job == job)
maketitle();
update_cursor(curbuf->b_term, TRUE);
}
}
static void
position_cursor(win_T *wp, VTermPos *pos)
{
@ -789,6 +829,7 @@ handle_pushline(int cols, const VTermScreenCell *cells, void *user)
line->sb_cols = len;
line->sb_cells = p;
++term->tl_scrollback.ga_len;
++term->tl_scrollback_scrolled;
}
return 0; /* ignored */
}
@ -916,6 +957,7 @@ static VTermScreenCallbacks screen_callbacks = {
/*
* Called when a channel has been closed.
* If this was a channel for a terminal window then finish it up.
*/
void
term_channel_closed(channel_T *ch)
@ -1080,8 +1122,6 @@ cell2attr(VTermScreenCell *cell)
attr |= HL_STANDOUT;
if (cell->attrs.reverse)
attr |= HL_INVERSE;
if (cell->attrs.strike)
attr |= HL_UNDERLINE;
#ifdef FEAT_GUI
if (gui.in_use)
@ -1384,8 +1424,315 @@ set_ref_in_term(int copyID)
return abort;
}
/*
* "term_getattr(attr, name)" function
*/
void
f_term_getattr(typval_T *argvars, typval_T *rettv)
{
int attr;
size_t i;
char_u *name;
static struct {
char *name;
int attr;
} attrs[] = {
{"bold", HL_BOLD},
{"italic", HL_ITALIC},
{"underline", HL_UNDERLINE},
{"strike", HL_STANDOUT},
{"reverse", HL_INVERSE},
};
attr = get_tv_number(&argvars[0]);
name = get_tv_string_chk(&argvars[1]);
if (name == NULL)
return;
for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
if (STRCMP(name, attrs[i].name) == 0)
{
rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
break;
}
}
/*
* Get the buffer from the first argument in "argvars".
* Returns NULL when the buffer is not for a terminal window.
*/
static buf_T *
term_get_buf(typval_T *argvars)
{
buf_T *buf;
(void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
++emsg_off;
buf = get_buf_tv(&argvars[0], FALSE);
--emsg_off;
if (buf->b_term == NULL)
return NULL;
return buf;
}
/*
* "term_getjob(buf)" function
*/
void
f_term_getjob(typval_T *argvars, typval_T *rettv)
{
buf_T *buf = term_get_buf(argvars);
rettv->v_type = VAR_JOB;
rettv->vval.v_job = NULL;
if (buf == NULL)
return;
rettv->vval.v_job = buf->b_term->tl_job;
if (rettv->vval.v_job != NULL)
++rettv->vval.v_job->jv_refcount;
}
/*
* "term_getline(buf, row)" function
*/
void
f_term_getline(typval_T *argvars, typval_T *rettv)
{
buf_T *buf = term_get_buf(argvars);
term_T *term;
int row;
rettv->v_type = VAR_STRING;
if (buf == NULL)
return;
term = buf->b_term;
row = (int)get_tv_number(&argvars[1]);
if (term->tl_vterm == NULL)
{
linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
/* vterm is finished, get the text from the buffer */
if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
}
else
{
VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
VTermRect rect;
int len;
char_u *p;
len = term->tl_cols * MB_MAXBYTES + 1;
p = alloc(len);
if (p == NULL)
return;
rettv->vval.v_string = p;
rect.start_col = 0;
rect.end_col = term->tl_cols;
rect.start_row = row;
rect.end_row = row + 1;
p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
}
}
/*
* "term_getsize(buf)" function
*/
void
f_term_getsize(typval_T *argvars, typval_T *rettv)
{
buf_T *buf = term_get_buf(argvars);
list_T *l;
if (rettv_list_alloc(rettv) == FAIL)
return;
if (buf == NULL)
return;
l = rettv->vval.v_list;
list_append_number(l, buf->b_term->tl_rows);
list_append_number(l, buf->b_term->tl_cols);
}
/*
* "term_list()" function
*/
void
f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
{
term_T *tp;
list_T *l;
if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
return;
l = rettv->vval.v_list;
for (tp = first_term; tp != NULL; tp = tp->tl_next)
if (tp != NULL && tp->tl_buffer != NULL)
if (list_append_number(l,
(varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
return;
}
/*
* "term_scrape(buf, row)" function
*/
void
f_term_scrape(typval_T *argvars, typval_T *rettv)
{
buf_T *buf = term_get_buf(argvars);
VTermScreen *screen = NULL;
VTermPos pos;
list_T *l;
term_T *term;
if (rettv_list_alloc(rettv) == FAIL)
return;
if (buf == NULL)
return;
term = buf->b_term;
if (term->tl_vterm != NULL)
screen = vterm_obtain_screen(term->tl_vterm);
l = rettv->vval.v_list;
pos.row = (int)get_tv_number(&argvars[1]);
for (pos.col = 0; pos.col < term->tl_cols; )
{
dict_T *dcell;
VTermScreenCell cell;
char_u rgb[8];
char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
int off = 0;
int i;
if (screen == NULL)
{
linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
sb_line_T *line;
/* vterm has finished, get the cell from scrollback */
if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
break;
line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
if (pos.col >= line->sb_cols)
break;
cell = line->sb_cells[pos.col];
}
else if (vterm_screen_get_cell(screen, pos, &cell) == 0)
break;
dcell = dict_alloc();
list_append_dict(l, dcell);
for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
{
if (cell.chars[i] == 0)
break;
off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
}
mbs[off] = NUL;
dict_add_nr_str(dcell, "chars", 0, mbs);
vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
cell.fg.red, cell.fg.green, cell.fg.blue);
dict_add_nr_str(dcell, "fg", 0, rgb);
vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
cell.bg.red, cell.bg.green, cell.bg.blue);
dict_add_nr_str(dcell, "bg", 0, rgb);
dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL);
dict_add_nr_str(dcell, "width", cell.width, NULL);
++pos.col;
if (cell.width == 2)
++pos.col;
}
}
/*
* "term_sendkeys(buf, keys)" function
*/
void
f_term_sendkeys(typval_T *argvars, typval_T *rettv)
{
buf_T *buf = term_get_buf(argvars);
char_u *msg;
term_T *term;
rettv->v_type = VAR_UNKNOWN;
if (buf == NULL)
return;
msg = get_tv_string_chk(&argvars[1]);
if (msg == NULL)
return;
term = buf->b_term;
if (term->tl_vterm == NULL)
return;
while (*msg != NUL)
{
send_keys_to_term(term, PTR2CHAR(msg), FALSE);
msg += MB_PTR2LEN(msg);
}
/* TODO: only update once in a while. */
update_screen(0);
if (buf == curbuf)
update_cursor(term, TRUE);
}
/*
* "term_start(command, options)" function
*/
void
f_term_start(typval_T *argvars, typval_T *rettv)
{
char_u *cmd = get_tv_string_chk(&argvars[0]);
exarg_T ea;
if (cmd == NULL)
return;
ea.arg = cmd;
ex_terminal(&ea);
if (curbuf->b_term != NULL)
rettv->vval.v_number = curbuf->b_fnum;
}
/*
* "term_wait" function
*/
void
f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
{
buf_T *buf = term_get_buf(argvars);
if (buf == NULL)
return;
/* Get the job status, this will detect a job that finished. */
if (buf->b_term->tl_job != NULL)
(void)job_status(buf->b_term->tl_job);
/* Check for any pending channel I/O. */
vpeekc_any();
ui_delay(10L, FALSE);
/* Flushing messages on channels is hopefully sufficient.
* TODO: is there a better way? */
parse_queued_messages();
}
# ifdef WIN3264
/**************************************
* 2. MS-Windows implementation.
*/
#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
@ -1404,10 +1751,6 @@ void (*winpty_error_free)(void*);
LPCWSTR (*winpty_error_msg)(void*);
BOOL (*winpty_set_size)(void*, int, int, void*);
/**************************************
* 2. MS-Windows implementation.
*/
#define WINPTY_DLL "winpty.dll"
static HINSTANCE hWinPtyDLL = NULL;

View File

@ -197,6 +197,7 @@ NEW_TESTS = test_arabic.res \
test_syntax.res \
test_system.res \
test_tcl.res \
test_terminal.res \
test_textobjects.res \
test_undo.res \
test_usercommands.res \

View File

@ -0,0 +1,67 @@
" Tests for the terminal window.
if !exists('*term_start')
finish
endif
source shared.vim
func Test_terminal_basic()
let buf = term_start(&shell)
let termlist = term_list()
call assert_equal(1, len(termlist))
call assert_equal(buf, termlist[0])
let g:job = term_getjob(buf)
call assert_equal(v:t_job, type(g:job))
call term_sendkeys(buf, "exit\r")
call WaitFor('job_status(g:job) == "dead"')
call assert_equal('dead', job_status(g:job))
exe buf . 'bwipe'
unlet g:job
endfunc
func Check_123(buf)
let l = term_scrape(a:buf, 0)
call assert_true(len(l) > 0)
call assert_equal('1', l[0].chars)
call assert_equal('2', l[1].chars)
call assert_equal('3', l[2].chars)
call assert_equal('#00e000', l[0].fg)
if &background == 'light'
call assert_equal('#ffffff', l[0].bg)
else
call assert_equal('#000000', l[0].bg)
endif
let l = term_getline(a:buf, 0)
call assert_equal('123', l)
endfunc
func Test_terminal_scrape()
if has('win32')
let cmd = 'cmd /c "cls && color 2 && echo 123"'
else
call writefile(["\<Esc>[32m123"], 'Xtext')
let cmd = "cat Xtext"
endif
let buf = term_start(cmd)
let termlist = term_list()
call assert_equal(1, len(termlist))
call assert_equal(buf, termlist[0])
call term_wait(buf)
call Check_123(buf)
" Must still work after the job ended.
let g:job = term_getjob(buf)
call WaitFor('job_status(g:job) == "dead"')
call term_wait(buf)
call Check_123(buf)
exe buf . 'bwipe'
endfunc

View File

@ -769,6 +769,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
803,
/**/
802,
/**/