diff options
author | Bram Moolenaar <Bram@vim.org> | 2017-07-29 20:15:08 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2017-07-29 20:15:08 +0200 |
commit | c6df10e5d33ffab2c392626e285317ea8241ebff (patch) | |
tree | b8575224551ff6774d6eaaa2a716aede712967fb /src | |
parent | 70229f951f00cdcff790f2e70b0b0a601202e9e7 (diff) | |
download | vim-git-c6df10e5d33ffab2c392626e285317ea8241ebff.tar.gz |
patch 8.0.0804: terminal window functions not yet implementedv8.0.0803
Problem: Terminal window functions not yet implemented.
Solution: Implement several functions. Add a first test. (Yasuhiro
Matsumoto, closes #1871)
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 1 | ||||
-rw-r--r-- | src/evalfunc.c | 13 | ||||
-rw-r--r-- | src/proto/evalfunc.pro | 1 | ||||
-rw-r--r-- | src/proto/terminal.pro | 10 | ||||
-rw-r--r-- | src/terminal.c | 481 | ||||
-rw-r--r-- | src/testdir/Make_all.mak | 1 | ||||
-rw-r--r-- | src/testdir/test_terminal.vim | 67 | ||||
-rw-r--r-- | src/version.c | 2 |
8 files changed, 506 insertions, 70 deletions
diff --git a/src/Makefile b/src/Makefile index e522c78c4..e4ccf37e1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2256,6 +2256,7 @@ test_arglist \ test_tagjump \ test_taglist \ test_tcl \ + test_terminal \ test_textobjects \ test_timers \ test_true_false \ diff --git a/src/evalfunc.c b/src/evalfunc.c index 30006e3e2..422b94e99 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -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; diff --git a/src/proto/evalfunc.pro b/src/proto/evalfunc.pro index c673e27a5..174afb6f2 100644 --- a/src/proto/evalfunc.pro +++ b/src/proto/evalfunc.pro @@ -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); diff --git a/src/proto/terminal.pro b/src/proto/terminal.pro index fbc0d1e81..daa770648 100644 --- a/src/proto/terminal.pro +++ b/src/proto/terminal.pro @@ -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 : */ diff --git a/src/terminal.c b/src/terminal.c index 3972bcc83..fee87d589 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -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; @@ -517,6 +510,76 @@ term_vgetc() } /* + * 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 * should be handled as a Normal mode command. @@ -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,58 +635,39 @@ terminal_loop(void) return OK; } } + if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK) + return OK; + } + return FAIL; +} - /* Catch keys that need to be handled as in Normal mode. */ - switch (c) - { - case NUL: - case K_ZERO: - stuffcharReadbuff(c); - return OK; +/* + * Called when a job has finished. + */ + void +term_job_ended(job_T *job) +{ + term_T *term; + int did_one = FALSE; - 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; - } + 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; } - 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 (did_one) + redraw_statuslines(); + if (curbuf->b_term != NULL) + { + if (curbuf->b_term->tl_job == job) + maketitle(); + update_cursor(curbuf->b_term, TRUE); } - return FAIL; } static void @@ -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; diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 1f8d30729..4eb634c58 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -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 \ diff --git a/src/testdir/test_terminal.vim b/src/testdir/test_terminal.vim new file mode 100644 index 000000000..10fe4af61 --- /dev/null +++ b/src/testdir/test_terminal.vim @@ -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 diff --git a/src/version.c b/src/version.c index 44ae124a4..86e8cc6d6 100644 --- a/src/version.c +++ b/src/version.c @@ -770,6 +770,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 803, +/**/ 802, /**/ 801, |