To: vim_dev@googlegroups.com Subject: Patch 8.0.0797 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.0.0797 Problem: Finished job in terminal window is not handled. Solution: Add the scrollback buffer. Use it to fill the buffer when the job has ended. Files: src/terminal.c, src/screen.c, src/proto/terminal.pro, src/channel.c, src/os_unix.c, src/buffer.c *** ../vim-8.0.0796/src/terminal.c 2017-07-28 15:11:34.267537205 +0200 --- src/terminal.c 2017-07-28 21:45:01.385884157 +0200 *************** *** 33,54 **** * while, if the terminal window is visible, the screen contents is drawn. * * TODO: ! * - if 'term' starts witth "xterm" use it for $TERM. ! * - To set BS correctly, check get_stty(); Pass the fd of the pty. ! * - include functions from #1871 ! * - do not store terminal buffer in viminfo. Or prefix term:// ? ! * - Add a scrollback buffer (contains lines to scroll off the top). ! * Can use the buf_T lines, store attributes somewhere else? * - When the job ends: - * - Write "-- JOB ENDED --" in the terminal. - * - Put the terminal contents in the scrollback buffer. - * - Free the terminal emulator. * - Display the scrollback buffer (but with attributes). * Make the buffer not modifiable, drop attributes when making changes. * - Need an option or argument to drop the window+buffer right away, to be * used for a shell or Vim. * - add a character in :ls output * - when closing window and job has not ended, make terminal hidden? * - don't allow exiting Vim when a terminal is still running a job * - use win_del_lines() to make scroll-up efficient. * - add test for giving error for invalid 'termsize' value. --- 33,51 ---- * while, if the terminal window is visible, the screen contents is drawn. * * TODO: ! * - For the scrollback buffer store lines in the buffer, only attributes in ! * tl_scrollback. * - When the job ends: * - Display the scrollback buffer (but with attributes). * Make the buffer not modifiable, drop attributes when making changes. * - Need an option or argument to drop the window+buffer right away, to be * used for a shell or Vim. + * - To set BS correctly, check get_stty(); Pass the fd of the pty. + * - Patch for functions: Yasuhiro Matsumoto, #1871 + * - do not store terminal buffer in viminfo. Or prefix term:// ? * - add a character in :ls output * - when closing window and job has not ended, make terminal hidden? + * - when closing window and job has ended, make buffer hidden? * - don't allow exiting Vim when a terminal is still running a job * - use win_del_lines() to make scroll-up efficient. * - add test for giving error for invalid 'termsize' value. *************** *** 80,85 **** --- 77,87 ---- #include "libvterm/include/vterm.h" + typedef struct sb_line_S { + int sb_cols; /* can differ per line */ + VTermScreenCell *sb_cells; /* allocated */ + } sb_line_T; + /* typedef term_T in structs.h */ struct terminal_S { term_T *tl_next; *************** *** 106,111 **** --- 108,115 ---- int tl_dirty_row_start; /* -1 if nothing dirty */ int tl_dirty_row_end; /* row below last one to update */ + garray_T tl_scrollback; + pos_T tl_cursor; int tl_cursor_visible; }; *************** *** 124,130 **** */ static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd); static void term_report_winsize(term_T *term, int rows, int cols); ! static void term_free(term_T *term); /************************************** * 1. Generic code for all systems. --- 128,134 ---- */ static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd); static void term_report_winsize(term_T *term, int rows, int cols); ! static void term_free_vterm(term_T *term); /************************************** * 1. Generic code for all systems. *************** *** 179,184 **** --- 183,189 ---- return; term->tl_dirty_row_end = MAX_ROW; term->tl_cursor_visible = TRUE; + ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300); /* Open a new window or tab. */ vim_memset(&split_ea, 0, sizeof(split_ea)); *************** *** 238,245 **** } else { ! free_terminal(term); ! curbuf->b_term = NULL; /* Wiping out the buffer will also close the window and call * free_terminal(). */ --- 243,249 ---- } else { ! free_terminal(curbuf); /* Wiping out the buffer will also close the window and call * free_terminal(). */ *************** *** 255,263 **** * Called when wiping out a buffer. */ void ! free_terminal(term_T *term) { term_T *tp; if (term == NULL) return; --- 259,269 ---- * Called when wiping out a buffer. */ void ! free_terminal(buf_T *buf) { + term_T *term = buf->b_term; term_T *tp; + int i; if (term == NULL) return; *************** *** 279,288 **** job_unref(term->tl_job); } ! term_free(term); vim_free(term->tl_title); vim_free(term->tl_status_text); vim_free(term); } /* --- 285,299 ---- job_unref(term->tl_job); } ! 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); ! ! term_free_vterm(term); vim_free(term->tl_title); vim_free(term->tl_status_text); vim_free(term); + buf->b_term = NULL; } /* *************** *** 344,349 **** --- 355,365 ---- size_t len = STRLEN(msg); term_T *term = buffer->b_term; + if (term->tl_vterm == NULL) + { + ch_logn(channel, "NOT writing %d bytes to terminal", (int)len); + return; + } ch_logn(channel, "writing %d bytes to terminal", (int)len); term_write_job_output(term, msg, len); *************** *** 459,464 **** --- 475,489 ---- } /* + * Return TRUE if the job for "buf" is still running. + */ + static int + term_job_running(term_T *term) + { + return term->tl_job != NULL && term->tl_job->jv_status == JOB_STARTED; + } + + /* * Get a key from the user without mapping. * TODO: use terminal mode mappings. */ *************** *** 480,487 **** * 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. */ ! void terminal_loop(void) { char buf[KEY_BUF_LEN]; --- 505,514 ---- * 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. + * Returns OK if a typed character is to be handled in Normal mode, FAIL if + * the terminal was closed. */ ! int terminal_loop(void) { char buf[KEY_BUF_LEN]; *************** *** 491,496 **** --- 518,527 ---- int dragging_outside = FALSE; int termkey = 0; + if (curbuf->b_term->tl_vterm == NULL || !term_job_running(curbuf->b_term)) + /* job finished */ + return OK; + if (*curwin->w_p_tk != NUL) termkey = string_to_key(curwin->w_p_tk, TRUE); *************** *** 500,505 **** --- 531,540 ---- update_screen(0); update_cursor(curbuf->b_term, FALSE); c = term_vgetc(); + if (curbuf->b_term->tl_vterm == NULL + || !term_job_running(curbuf->b_term)) + /* job finished while waiting for a character */ + break; if (c == (termkey == 0 ? Ctrl_W : termkey)) { *************** *** 511,516 **** --- 546,555 ---- #ifdef FEAT_CMDL_INFO clear_showcmd(); #endif + if (curbuf->b_term->tl_vterm == NULL + || !term_job_running(curbuf->b_term)) + /* job finished while waiting for a character */ + break; if (termkey == 0 && c == '.') /* "CTRL-W .": send CTRL-W to the job */ *************** *** 519,525 **** { stuffcharReadbuff(Ctrl_W); stuffcharReadbuff(c); ! return; } } --- 558,564 ---- { stuffcharReadbuff(Ctrl_W); stuffcharReadbuff(c); ! return OK; } } *************** *** 529,535 **** case NUL: case K_ZERO: stuffcharReadbuff(c); ! return; case K_IGNORE: continue; --- 568,574 ---- case NUL: case K_ZERO: stuffcharReadbuff(c); ! return OK; case K_IGNORE: continue; *************** *** 561,567 **** /* click outside the current window */ stuffcharReadbuff(c); mouse_was_outside = TRUE; ! return; } } mouse_was_outside = FALSE; --- 600,606 ---- /* click outside the current window */ stuffcharReadbuff(c); mouse_was_outside = TRUE; ! return OK; } } mouse_was_outside = FALSE; *************** *** 571,616 **** 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); ! } ! } ! ! /* ! * 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); } ! } ! ! /* ! * Return TRUE if the job for "buf" is still running. ! */ ! static int ! term_job_running(term_T *term) ! { ! return term->tl_job != NULL && term->tl_job->jv_status == JOB_STARTED; } static void --- 610,618 ---- 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); } ! return FAIL; } static void *************** *** 740,745 **** --- 742,889 ---- return 1; } + /* + * Handle a line that is pushed off the top of the screen. + */ + static int + handle_pushline(int cols, const VTermScreenCell *cells, void *user) + { + term_T *term = (term_T *)user; + + /* TODO: Limit the number of lines that are stored. */ + /* TODO: put the text in the buffer. */ + if (ga_grow(&term->tl_scrollback, 1) == OK) + { + VTermScreenCell *p; + int len; + int i; + + /* do not store empty cells at the end */ + for (i = 0; i < cols; ++i) + if (cells[i].chars[0] != 0) + len = i + 1; + + p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len); + if (p != NULL) + { + sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + + term->tl_scrollback.ga_len; + + mch_memmove(p, cells, sizeof(VTermScreenCell) * len); + line->sb_cols = len; + line->sb_cells = p; + ++term->tl_scrollback.ga_len; + } + } + return 0; /* ignored */ + } + + /* + * Fill the buffer with the scrollback lines and current lines of the terminal. + * Called after the job has ended. + */ + static void + move_scrollback_to_buffer(term_T *term) + { + linenr_T lnum; + garray_T ga; + int c; + int col; + int i; + win_T *wp; + int len; + int lines_skipped = 0; + VTermPos pos; + VTermScreenCell cell; + VTermScreenCell *p; + VTermScreen *screen = vterm_obtain_screen(term->tl_vterm); + + /* Append the the visible lines to the scrollback. */ + 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; + + if (len > 0) + { + 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; + ++term->tl_scrollback.ga_len; + } + } + + p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len); + if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK) + { + sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + + term->tl_scrollback.ga_len; + + for (pos.col = 0; pos.col < len; ++pos.col) + { + if (vterm_screen_get_cell(screen, pos, &cell) == 0) + vim_memset(p + pos.col, 0, sizeof(cell)); + else + p[pos.col] = cell; + } + line->sb_cols = len; + line->sb_cells = p; + ++term->tl_scrollback.ga_len; + } + else + vim_free(p); + } + } + + /* Add the text to the buffer. */ + ga_init2(&ga, 1, 100); + for (lnum = 0; lnum < term->tl_scrollback.ga_len; ++lnum) + { + sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum; + + ga.ga_len = 0; + for (col = 0; col < line->sb_cols; ++col) + for (i = 0; (c = line->sb_cells[col].chars[i]) != 0 || i == 0; ++i) + { + if (ga_grow(&ga, MB_MAXBYTES) == FAIL) + goto failed; + ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c, + (char_u *)ga.ga_data + ga.ga_len); + } + *((char_u *)ga.ga_data + ga.ga_len) = NUL; + ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE); + } + + /* Delete the empty line that was in the empty buffer. */ + curbuf = term->tl_buffer; + ml_delete(lnum + 1, FALSE); + curbuf = curwin->w_buffer; + + failed: + ga_clear(&ga); + + 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; + } + } + } + static VTermScreenCallbacks screen_callbacks = { handle_damage, /* damage */ handle_moverect, /* moverect */ *************** *** 747,757 **** handle_settermprop, /* settermprop */ NULL, /* bell */ handle_resize, /* resize */ ! NULL, /* sb_pushline */ NULL /* sb_popline */ }; /* * Reverse engineer the RGB value into a cterm color index. * First color is 1. Return 0 if no match found. */ --- 891,941 ---- handle_settermprop, /* settermprop */ NULL, /* bell */ handle_resize, /* resize */ ! handle_pushline, /* sb_pushline */ NULL /* sb_popline */ }; /* + * Called when a channel has been closed. + */ + void + term_channel_closed(channel_T *ch) + { + term_T *term; + int did_one = FALSE; + + for (term = first_term; term != NULL; term = term->tl_next) + if (term->tl_job == ch->ch_job) + { + vim_free(term->tl_title); + term->tl_title = NULL; + vim_free(term->tl_status_text); + term->tl_status_text = NULL; + + /* move the lines into the buffer and free the vterm */ + move_scrollback_to_buffer(term); + term_free_vterm(term); + + redraw_buf_and_status_later(term->tl_buffer, NOT_VALID); + did_one = TRUE; + } + if (did_one) + { + redraw_statuslines(); + + /* Need to break out of vgetc(). */ + ins_char_typebuf(K_IGNORE); + + if (curbuf->b_term != NULL) + { + if (curbuf->b_term->tl_job == ch->ch_job) + maketitle(); + update_cursor(curbuf->b_term, TRUE); + } + } + } + + /* * Reverse engineer the RGB value into a cterm color index. * First color is 1. Return 0 if no match found. */ *************** *** 911,927 **** } /* ! * Called to update the window that contains the terminal. */ ! void term_update_window(win_T *wp) { term_T *term = wp->w_buffer->b_term; ! VTerm *vterm = term->tl_vterm; ! VTermScreen *screen = vterm_obtain_screen(vterm); ! VTermState *state = vterm_obtain_state(vterm); VTermPos pos; /* * If the window was resized a redraw will be triggered and we get here. * Adjust the size of the vterm unless 'termsize' specifies a fixed size. --- 1095,1118 ---- } /* ! * Called to update the window that contains a terminal. ! * Returns FAIL when there is no terminal running in this window. */ ! int term_update_window(win_T *wp) { term_T *term = wp->w_buffer->b_term; ! VTerm *vterm; ! VTermScreen *screen; ! VTermState *state; VTermPos pos; + if (term == NULL || term->tl_vterm == NULL) + return FAIL; + vterm = term->tl_vterm; + screen = vterm_obtain_screen(vterm); + state = vterm_obtain_state(vterm); + /* * If the window was resized a redraw will be triggered and we get here. * Adjust the size of the vterm unless 'termsize' specifies a fixed size. *************** *** 1022,1027 **** --- 1213,1220 ---- screen_line(wp->w_winrow + pos.row, wp->w_wincol, pos.col, wp->w_width, FALSE); } + + return OK; } /* *************** *** 1351,1364 **** * Free the terminal emulator part of "term". */ static void ! term_free(term_T *term) { if (term->tl_winpty != NULL) winpty_free(term->tl_winpty); if (term->tl_winpty_config != NULL) winpty_config_free(term->tl_winpty_config); if (term->tl_vterm != NULL) vterm_free(term->tl_vterm); } /* --- 1544,1560 ---- * Free the terminal emulator part of "term". */ static void ! term_free_vterm(term_T *term) { if (term->tl_winpty != NULL) winpty_free(term->tl_winpty); + term->tl_winpty = NULL; if (term->tl_winpty_config != NULL) winpty_config_free(term->tl_winpty_config); + term->tl_winpty_config = NULL; if (term->tl_vterm != NULL) vterm_free(term->tl_vterm); + term->tl_vterm = NULL } /* *************** *** 1406,1415 **** * Free the terminal emulator part of "term". */ static void ! term_free(term_T *term) { if (term->tl_vterm != NULL) vterm_free(term->tl_vterm); } /* --- 1602,1612 ---- * Free the terminal emulator part of "term". */ static void ! term_free_vterm(term_T *term) { if (term->tl_vterm != NULL) vterm_free(term->tl_vterm); + term->tl_vterm = NULL; } /* *** ../vim-8.0.0796/src/screen.c 2017-07-19 12:51:48.018228639 +0200 --- src/screen.c 2017-07-28 18:38:03.302769617 +0200 *************** *** 1200,1210 **** #endif #ifdef FEAT_TERMINAL ! if (wp->w_buffer->b_term != NULL) { - /* This window contains a terminal, redraw works completely - * differently. */ - term_update_window(wp); wp->w_redr_type = 0; return; } --- 1200,1209 ---- #endif #ifdef FEAT_TERMINAL ! /* If this window contains a terminal, redraw works completely differently. ! */ ! if (term_update_window(wp) == OK) { wp->w_redr_type = 0; return; } *************** *** 6849,6862 **** p = NameBuff; len = (int)STRLEN(p); ! if (wp->w_buffer->b_help #ifdef FEAT_QUICKFIX || wp->w_p_pvw #endif || bufIsChanged(wp->w_buffer) || wp->w_buffer->b_p_ro) *(p + len++) = ' '; ! if (wp->w_buffer->b_help) { STRCPY(p + len, _("[Help]")); len += (int)STRLEN(p + len); --- 6848,6861 ---- p = NameBuff; len = (int)STRLEN(p); ! if (bt_help(wp->w_buffer) #ifdef FEAT_QUICKFIX || wp->w_p_pvw #endif || bufIsChanged(wp->w_buffer) || wp->w_buffer->b_p_ro) *(p + len++) = ' '; ! if (bt_help(wp->w_buffer)) { STRCPY(p + len, _("[Help]")); len += (int)STRLEN(p + len); *** ../vim-8.0.0796/src/proto/terminal.pro 2017-07-27 22:14:55.305968217 +0200 --- src/proto/terminal.pro 2017-07-28 21:39:43.975980817 +0200 *************** *** 1,10 **** /* terminal.c */ void ex_terminal(exarg_T *eap); ! void free_terminal(term_T *term); void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel); ! void terminal_loop(void); ! void term_job_ended(job_T *job); ! void term_update_window(win_T *wp); char_u *term_get_status_text(term_T *term); int set_ref_in_term(int copyID); /* vim: set ft=c : */ --- 1,10 ---- /* terminal.c */ 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_channel_closed(channel_T *ch); ! int term_update_window(win_T *wp); char_u *term_get_status_text(term_T *term); int set_ref_in_term(int copyID); /* vim: set ft=c : */ *** ../vim-8.0.0796/src/channel.c 2017-07-23 19:50:39.036922744 +0200 --- src/channel.c 2017-07-28 20:24:44.785952120 +0200 *************** *** 2921,2926 **** --- 2921,2930 ---- } channel->ch_nb_close_cb = NULL; + + #ifdef FEAT_TERMINAL + term_channel_closed(channel); + #endif } /* *************** *** 4696,4705 **** * not use "job" after this! */ job_free(job); } - - #ifdef FEAT_TERMINAL - term_job_ended(job); - #endif } /* --- 4700,4705 ---- *** ../vim-8.0.0796/src/os_unix.c 2017-07-28 15:55:28.572158705 +0200 --- src/os_unix.c 2017-07-28 21:13:01.302365432 +0200 *************** *** 412,417 **** --- 412,420 ---- #ifdef MESSAGE_QUEUE parse_queued_messages(); + /* If input was put directly in typeahead buffer bail out here. */ + if (typebuf_changed(tb_change_cnt)) + return 0; #endif if (wtime < 0 && did_start_blocking) /* blocking and already waited for p_ut */ *** ../vim-8.0.0796/src/buffer.c 2017-07-27 22:03:45.546703088 +0200 --- src/buffer.c 2017-07-28 21:39:51.959928375 +0200 *************** *** 858,864 **** channel_buffer_free(buf); #endif #ifdef FEAT_TERMINAL ! free_terminal(buf->b_term); #endif buf_hashtab_remove(buf); --- 858,864 ---- channel_buffer_free(buf); #endif #ifdef FEAT_TERMINAL ! free_terminal(buf); #endif buf_hashtab_remove(buf); *** ../vim-8.0.0796/src/version.c 2017-07-28 18:01:54.042023391 +0200 --- src/version.c 2017-07-28 21:48:23.908535689 +0200 *************** *** 771,772 **** --- 771,774 ---- { /* Add new patch number below this line */ + /**/ + 797, /**/ -- Facepalm statement #4: "3000 year old graves? That's not possible, it's only 2014!" /// 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 ///