diff options
author | Bram Moolenaar <Bram@vim.org> | 2017-09-20 10:03:07 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2017-09-20 10:03:07 +0200 |
commit | 2e6ab18729a634f3223a92be318e98e87b572a7b (patch) | |
tree | 0362ae0628fa9a966c95343b90678b6e766bc133 | |
parent | a5a2be26febee62da480265ed9a52f782d5b4388 (diff) |
Add back terminal.c
-rw-r--r-- | src/terminal.c | 3673 |
1 files changed, 3673 insertions, 0 deletions
diff --git a/src/terminal.c b/src/terminal.c new file mode 100644 index 0000000000..0cdb43ab23 --- /dev/null +++ b/src/terminal.c @@ -0,0 +1,3673 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * Terminal window support, see ":help :terminal". + * + * There are three parts: + * 1. Generic code for all systems. + * Uses libvterm for the terminal emulator. + * 2. The MS-Windows implementation. + * Uses winpty. + * 3. The Unix-like implementation. + * Uses pseudo-tty's (pty's). + * + * For each terminal one VTerm is constructed. This uses libvterm. A copy of + * this library is in the libvterm directory. + * + * When a terminal window is opened, a job is started that will be connected to + * the terminal emulator. + * + * If the terminal window has keyboard focus, typed keys are converted to the + * terminal encoding and writing to the job over a channel. + * + * If the job produces output, it is written to the terminal emulator. The + * terminal emulator invokes callbacks when its screen content changes. The + * line range is stored in tl_dirty_row_start and tl_dirty_row_end. Once in a + * while, if the terminal window is visible, the screen contents is drawn. + * + * When the job ends the text is put in a buffer. Redrawing then happens from + * that buffer, attributes come from the scrollback buffer tl_scrollback. + * When the buffer is changed it is turned into a normal buffer, the attributes + * in tl_scrollback are no longer used. + * + * TODO: + * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito + * Higashi, 2017 Sep 19) + * - Shift-Tab does not work. + * - click in Window toolbar of other window: save/restore Insert and Visual + * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file() + * is disabled. + * - implement term_setsize() + * - MS-Windows GUI: still need to type a key after shell exits? #1924 + * - add test for giving error for invalid 'termsize' value. + * - support minimal size when 'termsize' is "rows*cols". + * - support minimal size when 'termsize' is empty? + * - GUI: when using tabs, focus in terminal, click on tab does not work. + * - GUI: when 'confirm' is set and trying to exit Vim, dialog offers to save + * changes to "!shell". + * (justrajdeep, 2017 Aug 22) + * - For the GUI fill termios with default values, perhaps like pangoterm: + * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134 + * - if the job in the terminal does not support the mouse, we can use the + * mouse in the Terminal window for copy/paste. + * - when 'encoding' is not utf-8, or the job is using another encoding, setup + * conversions. + * - In the GUI use a terminal emulator for :!cmd. Make the height the same as + * the window and position it higher up when it gets filled, so it looks like + * the text scrolls up. + * - Copy text in the vterm to the Vim buffer once in a while, so that + * completion works. + * - add an optional limit for the scrollback size. When reaching it remove + * 10% at the start. + */ + +#include "vim.h" + +#if defined(FEAT_TERMINAL) || defined(PROTO) + +#ifndef MIN +# define MIN(x,y) ((x) < (y) ? (x) : (y)) +#endif +#ifndef MAX +# define MAX(x,y) ((x) > (y) ? (x) : (y)) +#endif + +#include "libvterm/include/vterm.h" + +/* This is VTermScreenCell without the characters, thus much smaller. */ +typedef struct { + VTermScreenCellAttrs attrs; + char width; + VTermColor fg, bg; +} cellattr_T; + +typedef struct sb_line_S { + int sb_cols; /* can differ per line */ + cellattr_T *sb_cells; /* allocated */ + cellattr_T sb_fill_attr; /* for short line */ +} sb_line_T; + +/* typedef term_T in structs.h */ +struct terminal_S { + term_T *tl_next; + + VTerm *tl_vterm; + job_T *tl_job; + buf_T *tl_buffer; + + /* Set when setting the size of a vterm, reset after redrawing. */ + int tl_vterm_size_changed; + + /* used when tl_job is NULL and only a pty was created */ + int tl_tty_fd; + char_u *tl_tty_in; + char_u *tl_tty_out; + + int tl_normal_mode; /* TRUE: Terminal-Normal mode */ + int tl_channel_closed; + int tl_finish; /* 'c' for ++close, 'o' for ++open */ + char_u *tl_opencmd; + char_u *tl_eof_chars; + +#ifdef WIN3264 + void *tl_winpty_config; + void *tl_winpty; +#endif + + /* last known vterm size */ + int tl_rows; + int tl_cols; + /* vterm size does not follow window size */ + int tl_rows_fixed; + int tl_cols_fixed; + + char_u *tl_title; /* NULL or allocated */ + char_u *tl_status_text; /* NULL or allocated */ + + /* Range of screen rows to update. Zero based. */ + int tl_dirty_row_start; /* -1 if nothing dirty */ + int tl_dirty_row_end; /* row below last one to update */ + + garray_T tl_scrollback; + int tl_scrollback_scrolled; + cellattr_T tl_default_color; + + VTermPos tl_cursor_pos; + int tl_cursor_visible; + int tl_cursor_blink; + int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */ + char_u *tl_cursor_color; /* NULL or allocated */ + + int tl_using_altscreen; +}; + +#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */ +#define TMODE_LOOP 2 /* CTRL-W N used */ + +/* + * List of all active terminals. + */ +static term_T *first_term = NULL; + +/* Terminal active in terminal_loop(). */ +static term_T *in_terminal_loop = NULL; + +#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */ +#define KEY_BUF_LEN 200 + +/* + * Functions with separate implementation for MS-Windows and Unix-like systems. + */ +static int term_and_job_init(term_T *term, typval_T *argvar, jobopt_T *opt); +static int create_pty_only(term_T *term, jobopt_T *opt); +static void term_report_winsize(term_T *term, int rows, int cols); +static void term_free_vterm(term_T *term); + +/* The characters that we know (or assume) that the terminal expects for the + * backspace and enter keys. */ +static int term_backspace_char = BS; +static int term_enter_char = CAR; +static int term_nl_does_cr = FALSE; + + +/************************************** + * 1. Generic code for all systems. + */ + +/* + * Determine the terminal size from 'termsize' and the current window. + * Assumes term->tl_rows and term->tl_cols are zero. + */ + static void +set_term_and_win_size(term_T *term) +{ + if (*curwin->w_p_tms != NUL) + { + char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1; + + term->tl_rows = atoi((char *)curwin->w_p_tms); + term->tl_cols = atoi((char *)p); + } + if (term->tl_rows == 0) + term->tl_rows = curwin->w_height; + else + { + win_setheight_win(term->tl_rows, curwin); + term->tl_rows_fixed = TRUE; + } + if (term->tl_cols == 0) + term->tl_cols = curwin->w_width; + else + { + win_setwidth_win(term->tl_cols, curwin); + term->tl_cols_fixed = TRUE; + } +} + +/* + * Initialize job options for a terminal job. + * Caller may overrule some of them. + */ + static void +init_job_options(jobopt_T *opt) +{ + clear_job_options(opt); + + opt->jo_mode = MODE_RAW; + opt->jo_out_mode = MODE_RAW; + opt->jo_err_mode = MODE_RAW; + opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE; +} + +/* + * Set job options mandatory for a terminal job. + */ + static void +setup_job_options(jobopt_T *opt, int rows, int cols) +{ + if (!(opt->jo_set & JO_OUT_IO)) + { + /* Connect stdout to the terminal. */ + opt->jo_io[PART_OUT] = JIO_BUFFER; + opt->jo_io_buf[PART_OUT] = curbuf->b_fnum; + opt->jo_modifiable[PART_OUT] = 0; + opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE; + } + + if (!(opt->jo_set & JO_ERR_IO)) + { + /* Connect stderr to the terminal. */ + opt->jo_io[PART_ERR] = JIO_BUFFER; + opt->jo_io_buf[PART_ERR] = curbuf->b_fnum; + opt->jo_modifiable[PART_ERR] = 0; + opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE; + } + + opt->jo_pty = TRUE; + if ((opt->jo_set2 & JO2_TERM_ROWS) == 0) + opt->jo_term_rows = rows; + if ((opt->jo_set2 & JO2_TERM_COLS) == 0) + opt->jo_term_cols = cols; +} + +/* + * Start a terminal window and return its buffer. + * Returns NULL when failed. + */ + static buf_T * +term_start(typval_T *argvar, jobopt_T *opt, int forceit) +{ + exarg_T split_ea; + win_T *old_curwin = curwin; + term_T *term; + buf_T *old_curbuf = NULL; + int res; + buf_T *newbuf; + + if (check_restricted() || check_secure()) + return NULL; + + if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)) + == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO) + || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF)) + || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF))) + { + EMSG(_(e_invarg)); + return NULL; + } + + term = (term_T *)alloc_clear(sizeof(term_T)); + if (term == NULL) + return NULL; + term->tl_dirty_row_end = MAX_ROW; + term->tl_cursor_visible = TRUE; + term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; + term->tl_finish = opt->jo_term_finish; + ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300); + + vim_memset(&split_ea, 0, sizeof(split_ea)); + if (opt->jo_curwin) + { + /* Create a new buffer in the current window. */ + if (!can_abandon(curbuf, forceit)) + { + no_write_message(); + vim_free(term); + return NULL; + } + if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE, + ECMD_HIDE + (forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) + { + vim_free(term); + return NULL; + } + } + else if (opt->jo_hidden) + { + buf_T *buf; + + /* Create a new buffer without a window. Make it the current buffer for + * a moment to be able to do the initialisations. */ + buf = buflist_new((char_u *)"", NULL, (linenr_T)0, + BLN_NEW | BLN_LISTED); + if (buf == NULL || ml_open(buf) == FAIL) + { + vim_free(term); + return NULL; + } + old_curbuf = curbuf; + --curbuf->b_nwindows; + curbuf = buf; + curwin->w_buffer = buf; + ++curbuf->b_nwindows; + } + else + { + /* Open a new window or tab. */ + split_ea.cmdidx = CMD_new; + split_ea.cmd = (char_u *)"new"; + split_ea.arg = (char_u *)""; + if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT)) + { + split_ea.line2 = opt->jo_term_rows; + split_ea.addr_count = 1; + } + if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT)) + { + split_ea.line2 = opt->jo_term_cols; + split_ea.addr_count = 1; + } + + ex_splitview(&split_ea); + if (curwin == old_curwin) + { + /* split failed */ + vim_free(term); + return NULL; + } + } + term->tl_buffer = curbuf; + curbuf->b_term = term; + + if (!opt->jo_hidden) + { + /* only one size was taken care of with :new, do the other one */ + if (opt->jo_term_rows > 0 && (cmdmod.split & WSP_VERT)) + win_setheight(opt->jo_term_rows); + if (opt->jo_term_cols > 0 && !(cmdmod.split & WSP_VERT)) + win_setwidth(opt->jo_term_cols); + } + + /* Link the new terminal in the list of active terminals. */ + term->tl_next = first_term; + first_term = term; + + if (opt->jo_term_name != NULL) + curbuf->b_ffname = vim_strsave(opt->jo_term_name); + else + { + int i; + size_t len; + char_u *cmd, *p; + + if (argvar->v_type == VAR_STRING) + { + cmd = argvar->vval.v_string; + if (cmd == NULL) + cmd = (char_u *)""; + else if (STRCMP(cmd, "NONE") == 0) + cmd = (char_u *)"pty"; + } + else if (argvar->v_type != VAR_LIST + || argvar->vval.v_list == NULL + || argvar->vval.v_list->lv_len < 1 + || (cmd = get_tv_string_chk( + &argvar->vval.v_list->lv_first->li_tv)) == NULL) + cmd = (char_u*)""; + + len = STRLEN(cmd) + 10; + p = alloc((int)len); + + for (i = 0; p != NULL; ++i) + { + /* Prepend a ! to the command name to avoid the buffer name equals + * the executable, otherwise ":w!" would overwrite it. */ + if (i == 0) + vim_snprintf((char *)p, len, "!%s", cmd); + else + vim_snprintf((char *)p, len, "!%s (%d)", cmd, i); + if (buflist_findname(p) == NULL) + { + vim_free(curbuf->b_ffname); + curbuf->b_ffname = p; + break; + } + } + } + curbuf->b_fname = curbuf->b_ffname; + + if (opt->jo_term_opencmd != NULL) + term->tl_opencmd = vim_strsave(opt->jo_term_opencmd); + + if (opt->jo_eof_chars != NULL) + term->tl_eof_chars = vim_strsave(opt->jo_eof_chars); + + set_string_option_direct((char_u *)"buftype", -1, + (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0); + + /* Mark the buffer as not modifiable. It can only be made modifiable after + * the job finished. */ + curbuf->b_p_ma = FALSE; + + set_term_and_win_size(term); + setup_job_options(opt, term->tl_rows, term->tl_cols); + + /* System dependent: setup the vterm and maybe start the job in it. */ + if (argvar->v_type == VAR_STRING + && argvar->vval.v_string != NULL + && STRCMP(argvar->vval.v_string, "NONE") == 0) + res = create_pty_only(term, opt); + else + res = term_and_job_init(term, argvar, opt); + + newbuf = curbuf; + if (res == OK) + { + /* Get and remember the size we ended up with. Update the pty. */ + vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols); + term_report_winsize(term, term->tl_rows, term->tl_cols); + + /* Make sure we don't get stuck on sending keys to the job, it leads to + * a deadlock if the job is waiting for Vim to read. */ + channel_set_nonblock(term->tl_job->jv_channel, PART_IN); + + if (old_curbuf != NULL) + { + --curbuf->b_nwindows; + curbuf = old_curbuf; + curwin->w_buffer = curbuf; + ++curbuf->b_nwindows; + } + } + else + { + buf_T *buf = curbuf; + + free_terminal(curbuf); + if (old_curbuf != NULL) + { + --curbuf->b_nwindows; + curbuf = old_curbuf; + curwin->w_buffer = curbuf; + ++curbuf->b_nwindows; + } + + /* Wiping out the buffer will also close the window and call + * free_terminal(). */ + do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE); + return NULL; + } + return newbuf; +} + +/* + * ":terminal": open a terminal window and execute a job in it. + */ + void +ex_terminal(exarg_T *eap) +{ + typval_T argvar[2]; + jobopt_T opt; + char_u *cmd; + char_u *tofree = NULL; + + init_job_options(&opt); + + cmd = eap->arg; + while (*cmd && *cmd == '+' && *(cmd + 1) == '+') + { + char_u *p, *ep; + + cmd += 2; + p = skiptowhite(cmd); + ep = vim_strchr(cmd, '='); + if (ep != NULL && ep < p) + p = ep; + + if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0) + opt.jo_term_finish = 'c'; + else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0) + opt.jo_term_finish = 'o'; + else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0) + opt.jo_curwin = 1; + else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0) + opt.jo_hidden = 1; + else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0 + && 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])) + { + 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) + { + char_u *buf = NULL; + char_u *keys; + + p = skiptowhite(cmd); + *p = NUL; + keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE); + opt.jo_set2 |= JO2_EOF_CHARS; + opt.jo_eof_chars = vim_strsave(keys); + vim_free(buf); + *p = ' '; + } + else + { + if (*p) + *p = NUL; + EMSG2(_("E181: Invalid attribute: %s"), cmd); + return; + } + cmd = skipwhite(p); + } + if (*cmd == NUL) + /* Make a copy of 'shell', an autocommand may change the option. */ + tofree = cmd = vim_strsave(p_sh); + + if (eap->addr_count > 0) + { + /* Write lines from current buffer to the job. */ + opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT; + opt.jo_io[PART_IN] = JIO_BUFFER; + opt.jo_io_buf[PART_IN] = curbuf->b_fnum; + opt.jo_in_top = eap->line1; + opt.jo_in_bot = eap->line2; + } + + argvar[0].v_type = VAR_STRING; + argvar[0].vval.v_string = cmd; + argvar[1].v_type = VAR_UNKNOWN; + term_start(argvar, &opt, eap->forceit); + vim_free(tofree); + vim_free(opt.jo_eof_chars); +} + +/* + * Free the scrollback buffer for "term". + */ + static void +free_scrollback(term_T *term) +{ + int i; + + for (i = 0; i < term->tl_scrollback.ga_len; ++i) + vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells); + ga_clear(&term->tl_scrollback); +} + +/* + * Free a terminal and everything it refers to. + * Kills the job if there is one. + * Called when wiping out a buffer. + */ + void +free_terminal(buf_T *buf) +{ + term_T *term = buf->b_term; + term_T *tp; + + if (term == NULL) + return; + if (first_term == term) + first_term = term->tl_next; + else + for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next) + if (tp->tl_next == term) + { + tp->tl_next = term->tl_next; + break; + } + + if (term->tl_job != NULL) + { + if (term->tl_job->jv_status != JOB_ENDED + && term->tl_job->jv_status != JOB_FINISHED + && term->tl_job->jv_status != JOB_FAILED) + job_stop(term->tl_job, NULL, "kill"); + job_unref(term->tl_job); + } + + free_scrollback(term); + + term_free_vterm(term); + vim_free(term->tl_title); + vim_free(term->tl_status_text); + vim_free(term->tl_opencmd); + vim_free(term->tl_eof_chars); + vim_free(term->tl_cursor_color); + vim_free(term); + buf->b_term = NULL; + if (in_terminal_loop == term) + in_terminal_loop = NULL; +} + +/* + * Write job output "msg[len]" to the vterm. + */ + static void +term_write_job_output(term_T *term, char_u *msg, size_t len) +{ + VTerm *vterm = term->tl_vterm; + char_u *p; + size_t done; + size_t len_now; + + if (term_nl_does_cr) + vterm_input_write(vterm, (char *)msg, len); + else + /* need to convert NL to CR-NL */ + for (done = 0; done < len; done += len_now) + { + for (p = msg + done; p < msg + len; ) + { + if (*p == NL) + break; + p += utf_ptr2len_len(p, (int)(len - (p - msg))); + } + len_now = p - msg - done; + vterm_input_write(vterm, (char *)msg + done, len_now); + if (p < msg + len && *p == NL) + { + vterm_input_write(vterm, "\r\n", 2); + ++len_now; + } + } + + /* this invokes the damage callbacks */ + vterm_screen_flush_damage(vterm_obtain_screen(vterm)); +} + + static void +update_cursor(term_T *term, int redraw) +{ + if (term->tl_normal_mode) + return; + setcursor(); + if (redraw) + { + if (term->tl_buffer == curbuf && term->tl_cursor_visible) + cursor_on(); + out_flush(); +#ifdef FEAT_GUI + if (gui.in_use) + gui_update_cursor(FALSE, FALSE); +#endif + } +} + +/* + * Invoked when "msg" output from a job was received. Write it to the terminal + * of "buffer". + */ + void +write_to_term(buf_T *buffer, char_u *msg, channel_T *channel) +{ + size_t len = STRLEN(msg); + term_T *term = buffer->b_term; + + if (term->tl_vterm == NULL) + { + ch_log(channel, "NOT writing %d bytes to terminal", (int)len); + return; + } + ch_log(channel, "writing %d bytes to terminal", (int)len); + term_write_job_output(term, msg, len); + + /* In Terminal-Normal mode we are displaying the buffer, not the terminal + * contents, thus no screen update is needed. */ + if (!term->tl_normal_mode) + { + /* TODO: only update once in a while. */ + ch_log(term->tl_job->jv_channel, "updating screen"); + if (buffer == curbuf) + { + update_screen(0); + update_cursor(term, TRUE); + } + else + redraw_after_callback(TRUE); + } +} + +/* + * Send a mouse position and click to the vterm + */ + static int +term_send_mouse(VTerm *vterm, int button, int pressed) +{ + VTermModifier mod = VTERM_MOD_NONE; + + vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin), + mouse_col - W_WINCOL(curwin), mod); + vterm_mouse_button(vterm, button, pressed, mod); + return TRUE; +} + +/* + * Convert typed key "c" into bytes to send to the job. + * Return the number of bytes in "buf". + */ + static int +term_convert_key(term_T *term, int c, char *buf) +{ + VTerm *vterm = term->tl_vterm; + VTermKey key = VTERM_KEY_NONE; + VTermModifier mod = VTERM_MOD_NONE; + int mouse = FALSE; + + switch (c) + { + case CAR: c = term_enter_char; break; + /* don't use VTERM_KEY_BACKSPACE, it always + * becomes 0x7f DEL */ + case K_BS: c = term_backspace_char; break; + + case ESC: key = VTERM_KEY_ESCAPE; break; + case K_DEL: key = VTERM_KEY_DEL; break; + case K_DOWN: key = VTERM_KEY_DOWN; break; + case K_S_DOWN: mod = VTERM_MOD_SHIFT; + key = VTERM_KEY_DOWN; break; + case K_END: key = VTERM_KEY_END; break; + case K_S_END: mod = VTERM_MOD_SHIFT; + key = VTERM_KEY_END; break; + case K_C_END: mod = VTERM_MOD_CTRL; + key = VTERM_KEY_END; break; + case K_F10: key = VTERM_KEY_FUNCTION(10); break; + case K_F11: key = VTERM_KEY_FUNCTION(11); break; + case K_F12: key = VTERM_KEY_FUNCTION(12); break; + case K_F1: key = VTERM_KEY_FUNCTION(1); break; + case K_F2: key = VTERM_KEY_FUNCTION(2); break; + case K_F3: key = VTERM_KEY_FUNCTION(3); break; + case K_F4: key = VTERM_KEY_FUNCTION(4); break; + case K_F5: key = VTERM_KEY_FUNCTION(5); break; + case K_F6: key = VTERM_KEY_FUNCTION(6); break; + case K_F7: key = VTERM_KEY_FUNCTION(7); break; + case K_F8: key = VTERM_KEY_FUNCTION(8); break; + case K_F9: key = VTERM_KEY_FUNCTION(9); break; + case K_HOME: key = VTERM_KEY_HOME; break; + case K_S_HOME: mod = VTERM_MOD_SHIFT; + key = VTERM_KEY_HOME; break; + case K_C_HOME: mod = VTERM_MOD_CTRL; + key = VTERM_KEY_HOME; break; + case K_INS: key = VTERM_KEY_INS; break; + case K_K0: key = VTERM_KEY_KP_0; break; + case K_K1: key = VTERM_KEY_KP_1; break; + case K_K2: key = VTERM_KEY_KP_2; break; + case K_K3: key = VTERM_KEY_KP_3; break; + case K_K4: key = VTERM_KEY_KP_4; break; + case K_K5: key = VTERM_KEY_KP_5; break; + case K_K6: key = VTERM_KEY_KP_6; break; + case K_K7: key = VTERM_KEY_KP_7; break; + case K_K8: key = VTERM_KEY_KP_8; break; + case K_K9: key = VTERM_KEY_KP_9; break; + case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */ + case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break; + case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */ + case K_KENTER: key = VTERM_KEY_KP_ENTER; break; + case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */ + case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */ + case K_KMINUS: key = VTERM_KEY_KP_MINUS; break; + case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break; + case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */ + case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */ + case K_KPLUS: key = VTERM_KEY_KP_PLUS; break; + case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break; + case K_LEFT: key = VTERM_KEY_LEFT; break; + case K_S_LEFT: mod = VTERM_MOD_SHIFT; + key = VTERM_KEY_LEFT; break; + case K_C_LEFT: mod = VTERM_MOD_CTRL; + key = VTERM_KEY_LEFT; break; + case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break; + case K_PAGEUP: key = VTERM_KEY_PAGEUP; break; + case K_RIGHT: key = VTERM_KEY_RIGHT; break; + case K_S_RIGHT: mod = VTERM_MOD_SHIFT; + key = VTERM_KEY_RIGHT; break; + case K_C_RIGHT: mod = VTERM_MOD_CTRL; + key = VTERM_KEY_RIGHT; break; + case K_UP: key = VTERM_KEY_UP; break; + case K_S_UP: mod = VTERM_MOD_SHIFT; + key = VTERM_KEY_UP; break; + case TAB: key = VTERM_KEY_TAB; break; + + case K_MOUSEUP: mouse = term_send_mouse(vterm, 5, 1); break; + case K_MOUSEDOWN: mouse = term_send_mouse(vterm, 4, 1); break; + case K_MOUSELEFT: /* TODO */ return 0; + case K_MOUSERIGHT: /* TODO */ return 0; + + case K_LEFTMOUSE: + case K_LEFTMOUSE_NM: mouse = term_send_mouse(vterm, 1, 1); break; + case K_LEFTDRAG: mouse = term_send_mouse(vterm, 1, 1); break; + case K_LEFTRELEASE: + case K_LEFTRELEASE_NM: mouse = term_send_mouse(vterm, 1, 0); break; + case K_MIDDLEMOUSE: mouse = term_send_mouse(vterm, 2, 1); break; + case K_MIDDLEDRAG: mouse = term_send_mouse(vterm, 2, 1); break; + case K_MIDDLERELEASE: mouse = term_send_mouse(vterm, 2, 0); break; + case K_RIGHTMOUSE: mouse = term_send_mouse(vterm, 3, 1); break; + case K_RIGHTDRAG: mouse = term_send_mouse(vterm, 3, 1); break; + case K_RIGHTRELEASE: mouse = term_send_mouse(vterm, 3, 0); break; + case K_X1MOUSE: /* TODO */ return 0; + case K_X1DRAG: /* TODO */ return 0; + case K_X1RELEASE: /* TODO */ return 0; + case K_X2MOUSE: /* TODO */ return 0; + case K_X2DRAG: /* TODO */ return 0; + case K_X2RELEASE: /* TODO */ return 0; + + case K_IGNORE: return 0; + case K_NOP: return 0; + case K_UNDO: return 0; + case K_HELP: return 0; + case K_XF1: key = VTERM_KEY_FUNCTION(1); break; + case K_XF2: key = VTERM_KEY_FUNCTION(2); break; + case K_XF3: key = VTERM_KEY_FUNCTION(3); break; + case K_XF4: key = VTERM_KEY_FUNCTION(4); break; + case K_SELECT: return 0; +#ifdef FEAT_GUI + case K_VER_SCROLLBAR: return 0; + case K_HOR_SCROLLBAR: return 0; +#endif +#ifdef FEAT_GUI_TABLINE + case K_TABLINE: return 0; + case K_TABMENU: return 0; +#endif +#ifdef FEAT_NETBEANS_INTG + case K_F21: key = VTERM_KEY_FUNCTION(21); break; +#endif +#ifdef FEAT_DND + case K_DROP: return 0; +#endif +#ifdef FEAT_AUTOCMD + case K_CURSORHOLD: return 0; +#endif + case K_PS: vterm_keyboard_start_paste(vterm); return 0; + case K_PE: vterm_keyboard_end_paste(vterm); return 0; + } + + /* + * Convert special keys to vterm keys: + * - Write keys to vterm: vterm_keyboard_key() + * - Write output to channel. + * TODO: use mod_mask + */ + if (key != VTERM_KEY_NONE) + /* Special key, let vterm convert it. */ + vterm_keyboard_key(vterm, key, mod); + else if (!mouse) + /* Normal character, let vterm convert it. */ + vterm_keyboard_unichar(vterm, c, mod); + + /* Read back the converted escape sequence. */ + return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN); +} + +/* + * Return TRUE if the job for "term" is still running. + */ + int +term_job_running(term_T *term) +{ + /* Also consider the job finished when the channel is closed, to avoid a + * race condition when updating the title. */ + return term != NULL + && term->tl_job != NULL + && channel_is_open(term->tl_job->jv_channel) + && (term->tl_job->jv_status == JOB_STARTED + || term->tl_job->jv_channel->ch_keep_open); +} + +/* + * Return TRUE if "term" has an active channel and used ":term NONE". + */ + int +term_none_open(term_T *term) +{ + /* Also consider the job finished when the channel is closed, to avoid a + * race condition when updating the title. */ + return term != NULL + && term->tl_job != NULL + && channel_is_open(term->tl_job->jv_channel) + && term->tl_job->jv_channel->ch_keep_open; +} + +/* + * Add the last line of the scrollback buffer to the buffer in the window. + */ + static void +add_scrollback_line_to_buffer(term_T *term, char_u *text, int len) +{ + buf_T *buf = term->tl_buffer; + int empty = (buf->b_ml.ml_flags & ML_EMPTY); + linenr_T lnum = buf->b_ml.ml_line_count; + +#ifdef WIN3264 + if (!enc_utf8 && enc_codepage > 0) + { + WCHAR *ret = NULL; + int length = 0; + + MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1, + &ret, &length); + if (ret != NULL) + { + WideCharToMultiByte_alloc(enc_codepage, 0, + ret, length, (char **)&text, &len, 0, 0); + vim_free(ret); + ml_append_buf(term->tl_buffer, lnum, text, len, FALSE); + vim_free(text); + } + } + else +#endif + ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE); + if (empty) + { + /* Delete the empty line that was in the empty buffer. */ + curbuf = buf; + ml_delete(1, FALSE); + curbuf = curwin->w_buffer; + } +} + + static void +cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr) +{ + attr->width = cell->width; + attr->attrs = cell->attrs; + attr->fg = cell->fg; + attr->bg = cell->bg; +} + + static int +equal_celattr(cellattr_T *a, cellattr_T *b) +{ + /* Comparing the colors should be sufficient. */ + return a->fg.red == b->fg.red + && a->fg.green == b->fg.green + && a->fg.blue == b->fg.blue + && a->bg.red == b->bg.red + && a->bg.green == b->bg.green + && a->bg.blue == b->bg.blue; +} + + +/* + * Add the current lines of the terminal to scrollback and to the buffer. + * Called after the job has ended and when switching to Terminal-Normal mode. + */ + static void +move_terminal_to_buffer(term_T *term) +{ + win_T *wp; + int len; + int lines_skipped = 0; + VTermPos pos; + VTermScreenCell cell; + cellattr_T fill_attr, new_fill_attr; + cellattr_T *p; + VTermScreen *screen; + + if (term->tl_vterm == NULL) + return; + screen = vterm_obtain_screen(term->tl_vterm); + fill_attr = new_fill_attr = term->tl_default_color; + + for (pos.row = 0; pos.row < term->tl_rows; ++pos.row) + { + len = 0; + for (pos.col = 0; pos.col < term->tl_cols; ++pos.col) + if (vterm_screen_get_cell(screen, pos, &cell) != 0 + && cell.chars[0] != NUL) + { + len = pos.col + 1; + new_fill_attr = term->tl_default_color; + } + else + /* Assume the last attr is the filler attr. */ + cell2cellattr(&cell, &new_fill_attr); + + if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr)) + ++lines_skipped; + else + { + while (lines_skipped > 0) + { + /* Line was skipped, add an empty line. */ + --lines_skipped; + if (ga_grow(&term->tl_scrollback, 1) == OK) + { + sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + + term->tl_scrollback.ga_len; + + line->sb_cols = 0; + line->sb_cells = NULL; + line->sb_fill_attr = fill_attr; + ++term->tl_scrollback.ga_len; + + add_scrollback_line_to_buffer(term, (char_u *)"", 0); + } + } + + if (len == 0) + p = NULL; + else + p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len); + if ((p != NULL || len == 0) + && ga_grow(&term->tl_scrollback, 1) == OK) + { + garray_T ga; + int width; + sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + + term->tl_scrollback.ga_len; + + ga_init2(&ga, 1, 100); + for (pos.col = 0; pos.col < len; pos.col += width) + { + if (vterm_screen_get_cell(screen, pos, &cell) == 0) + { + width = 1; + vim_memset(p + pos.col, 0, sizeof(cellattr_T)); + if (ga_grow(&ga, 1) == OK) + ga.ga_len += utf_char2bytes(' ', + (char_u *)ga.ga_data + ga.ga_len); + } + else + { + width = cell.width; + + cell2cellattr(&cell, &p[pos.col]); + + if (ga_grow(&ga, MB_MAXBYTES) == OK) + { + int i; + int c; + + for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i) + ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c, + (char_u *)ga.ga_data + ga.ga_len); + } + } + } + line->sb_cols = len; + line->sb_cells = p; + line->sb_fill_attr = new_fill_attr; + fill_attr = new_fill_attr; + ++term->tl_scrollback.ga_len; + + if (ga_grow(&ga, 1) == FAIL) + add_scrollback_line_to_buffer(term, (char_u *)"", 0); + else + { + *((char_u *)ga.ga_data + ga.ga_len) = NUL; + add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len); + } + ga_clear(&ga); + } + else + vim_free(p); + } + } + + /* Obtain the current background color. */ + vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm), + &term->tl_default_color.fg, &term->tl_default_color.bg); + + FOR_ALL_WINDOWS(wp) + { + if (wp->w_buffer == term->tl_buffer) + { + wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count; + wp->w_cursor.col = 0; + wp->w_valid = 0; + if (wp->w_cursor.lnum >= wp->w_height) + { + linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1; + + if (wp->w_topline < min_topline) + wp->w_topline = min_topline; + } + redraw_win_later(wp, NOT_VALID); + } + } +} + + static void +set_terminal_mode(term_T *term, int normal_mode) +{ + term->tl_normal_mode = normal_mode; + vim_free(term->tl_status_text); + term->tl_status_text = NULL; + if (term->tl_buffer == curbuf) + maketitle(); +} + +/* + * Called after the job if finished and Terminal mode is not active: + * Move the vterm contents into the scrollback buffer and free the vterm. + */ + static void +cleanup_vterm(term_T *term) +{ + if (term->tl_finish != 'c') + move_terminal_to_buffer(term); + term_free_vterm(term); + set_terminal_mode(term, FALSE); +} + +/* + * Switch from Terminal-Job mode to Terminal-Normal mode. + * Suspends updating the termina |