changeset 11866:be40c8a9240d v8.0.0813

patch 8.0.0813: cannot use a terminal window while the job is running commit https://github.com/vim/vim/commit/423802d1a282df35078539970eabf559186e1ec8 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jul 30 16:52:24 2017 +0200 patch 8.0.0813: cannot use a terminal window while the job is running Problem: Cannot use Vim commands in a terminal window while the job is running. Solution: Implement Terminal Normal mode.
author Christian Brabandt <cb@256bit.org>
date Sun, 30 Jul 2017 17:00:04 +0200
parents 6bdac11ae12a
children f51e39d5bd7d
files runtime/doc/terminal.txt src/main.c src/normal.c src/option.c src/proto/terminal.pro src/screen.c src/terminal.c src/version.c
diffstat 8 files changed, 352 insertions(+), 138 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -1,4 +1,4 @@
-*terminal.txt*	For Vim version 8.0.  Last change: 2017 Jul 28
+*terminal.txt*	For Vim version 8.0.  Last change: 2017 Jul 30
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -33,24 +33,39 @@ Or to run a debugger: >
 The job runs asynchronously from Vim, the window will be updated to show
 output from the job, also  while editing in any other window.
 
+
 Typing ~
 
 When the keyboard focus is in the terminal window, typed keys will be send to
 the job.  This uses a pty when possible.  You can click outside of the
 terminal window to move keyboard focus elsewhere.
 
-Navigate between windows with CTRL-W commands.  E.g. CTRL-W CTRL-W moves focus
-to the next window.  Use "CTRL-W :" to edit an Ex command.  Use "CTRL-W ." to
-send a CTRL-W to the job in the terminal.
+CTRL-W can be used to navigate between windows and other CTRL-W commands, e.g.:
+	CTRL-W CTRL-W 	move focus to the next window
+	CTRL-W :	enter an Ex command
+See |CTRL-W| for more commands.
+
+Special in the terminal window:			*CTRL-W_.*  *CTRL-W_N* 
+	CTRL-W .	send a CTRL-W to the job in the terminal
+	CTRL-W N	go to Terminal Normal mode, see |Terminal-mode|
 
-See option 'termkey' for specifying another key that precedes a Vim command.
-Typing 'termkey' twice sends 'termkey' to the job.
+See option 'termkey' for specifying another key instead of CTRL-W that
+will work like CTRL-W.  However, typing 'termkey' twice sends 'termkey' to
+the job.  For example:
+	'termkey' CTRL-W    move focus to the next window
+	'termkey' :	    enter an Ex command
+	'termkey' 'termkey' send 'termkey' to the job in the terminal
+	'termkey' .	    send a CTRL-W to the job in the terminal
+	'termkey' N  	    go to terminal Normal mode, see below
+	'termkey' CTRL-N    same as CTRL-W N
+
 
 Size ~
 
 See option 'termsize' for controlling the size of the terminal window.
 (TODO: scrolling when the terminal is larger than the window)
 
+
 Syntax ~
 
 :ter[minal] [command]				*:ter* *:terminal*
@@ -99,6 +114,25 @@ terminal.  |term_setsize()| can be used 
 not when 'termsize' is "rowsXcols".
 
 
+Terminal Normal mode ~
+							*Terminal-mode*
+When the job is running the contents of the terminal is under control of the
+job.  That includes the cursor position.  The terminal contents can change at
+any time.
+
+Use CTRL-W N (or 'termkey' N) to go to Terminal Normal mode.  Now the contents
+of the terminal window is under control of Vim, the job output is suspended.
+							*E946*
+In this mode you can move the cursor around with the usual Vim commands,
+Visually mark text, yank text, etc.  But you cannot change the contents of the
+buffer.  The commands that would start insert mode, such as 'i' and 'a',
+return control of the window to the job.  Any pending output will now be
+displayed.
+
+In Terminal mode the statusline and window title show "(Terminal)".  If the
+job ends while in Terminal mode this changes to "(Terminal-finished)".
+
+
 Unix ~
 
 On Unix a pty is used to make it possible to run all kinds of commands.  You
--- a/src/main.c
+++ b/src/main.c
@@ -1356,11 +1356,17 @@ main_loop(
 	else
 	{
 #ifdef FEAT_TERMINAL
-	    if (curbuf->b_term != NULL && oa.op_type == OP_NOP
-							  && oa.regname == NUL)
-		terminal_loop();
+	    if (term_use_loop() && oa.op_type == OP_NOP && oa.regname == NUL)
+	    {
+		/* If terminal_loop() returns OK we got a key that is handled
+		 * in Normal model.  With FAIL the terminal was closed and the
+		 * screen needs to be redrawn. */
+		if (terminal_loop() == OK)
+		    normal_cmd(&oa, TRUE);
+	    }
+	    else
 #endif
-	    normal_cmd(&oa, TRUE);
+		normal_cmd(&oa, TRUE);
 	}
     }
 }
--- a/src/normal.c
+++ b/src/normal.c
@@ -9037,6 +9037,14 @@ nv_esc(cmdarg_T *cap)
     static void
 nv_edit(cmdarg_T *cap)
 {
+#ifdef FEAT_TERMINAL
+    if (term_in_terminal_mode())
+    {
+	term_leave_terminal_mode();
+	return;
+    }
+#endif
+
     /* <Insert> is equal to "i" */
     if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS)
 	cap->cmdchar = 'i';
--- a/src/option.c
+++ b/src/option.c
@@ -8222,12 +8222,22 @@ set_bool_option(
     }
 #endif
 
-#ifdef FEAT_TITLE
     /* when 'modifiable' is changed, redraw the window title */
     else if ((int *)varp == &curbuf->b_p_ma)
     {
+# ifdef FEAT_TERMINAL
+	/* Cannot set 'modifiable' when in Terminal mode. */
+	if (term_in_terminal_mode())
+	{
+	    curbuf->b_p_ma = FALSE;
+	    return (char_u *)N_("E946: Cannot make a terminal with running job modifiable");
+	}
+# endif
+# ifdef FEAT_TITLE
 	redraw_titles();
-    }
+# endif
+    }
+#ifdef FEAT_TITLE
     /* when 'endofline' is changed, redraw the window title */
     else if ((int *)varp == &curbuf->b_p_eol)
     {
--- a/src/proto/terminal.pro
+++ b/src/proto/terminal.pro
@@ -2,11 +2,15 @@
 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 term_in_terminal_mode(void);
+void term_leave_terminal_mode(void);
+int term_use_loop(void);
 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);
+int term_show_buffer(buf_T *buf);
 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);
@@ -16,8 +20,8 @@ void f_term_getjob(typval_T *argvars, ty
 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_start(typval_T *argvars, typval_T *rettv);
 void f_term_wait(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
--- a/src/screen.c
+++ b/src/screen.c
@@ -3245,7 +3245,7 @@ win_line(
 #endif
 
 #ifdef FEAT_TERMINAL
-    if (term_is_finished(wp->w_buffer))
+    if (term_show_buffer(wp->w_buffer))
     {
 	extra_check = TRUE;
 	get_term_attr = TRUE;
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -36,13 +36,23 @@
  * that buffer, attributes come from the scrollback buffer tl_scrollback.
  *
  * TODO:
+ * - Problem with statusline (Zyx, Christian)
+ * - Make CTRL-W "" paste register content to the job?
+ * - in bash mouse clicks are inserting characters.
+ * - mouse scroll: when over other window, scroll that window.
  * - For the scrollback buffer store lines in the buffer, only attributes in
  *   tl_scrollback.
+ * - Add term_status(): "" if not a terminal, "running" if job running,
+ *   "finished" if finished, "running,vim" when job is running and in
+ *   Terminal mode, "running,vim,pending" when job output is pending.
  * - When the job ends:
  *   - Need an option or argument to drop the window+buffer right away, to be
- *     used for a shell or Vim.
+ *     used for a shell or Vim. 'termfinish'; "close", "open" (open window when
+ *     job finishes).
+ * - add option values to the command:
+ *      :term <24x80> <close> vim notes.txt
  * - To set BS correctly, check get_stty(); Pass the fd of the pty.
- * - do not store terminal buffer in viminfo.  Or prefix term:// ?
+ * - do not store terminal window 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?
@@ -53,6 +63,8 @@
  * - support minimal size when 'termsize' is empty?
  * - implement "term" for job_start(): more job options when starting a
  *   terminal.
+ * - 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.
@@ -78,13 +90,17 @@ typedef struct sb_line_S {
 struct terminal_S {
     term_T	*tl_next;
 
+    VTerm	*tl_vterm;
+    job_T	*tl_job;
+    buf_T	*tl_buffer;
+
+    int		tl_terminal_mode;
+    int		tl_channel_closed;
+
 #ifdef WIN3264
     void	*tl_winpty_config;
     void	*tl_winpty;
 #endif
-    VTerm	*tl_vterm;
-    job_T	*tl_job;
-    buf_T	*tl_buffer;
 
     /* last known vterm size */
     int		tl_rows;
@@ -553,6 +569,205 @@ term_job_running(term_T *term)
 }
 
 /*
+ * Add the last line of the scrollback buffer to the buffer in the window.
+ */
+    static void
+add_scrollback_line_to_buffer(term_T *term)
+{
+    linenr_T	    lnum = term->tl_scrollback.ga_len - 1;
+    sb_line_T	    *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
+    garray_T	    ga;
+    int		    c;
+    int		    col;
+    int		    i;
+
+    ga_init2(&ga, 1, 100);
+    for (col = 0; col < line->sb_cols; col += line->sb_cells[col].width)
+    {
+	if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
+	    goto failed;
+	for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
+	    ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
+					 (char_u *)ga.ga_data + ga.ga_len);
+    }
+    if (ga_grow(&ga, 1) == FAIL)
+	goto failed;
+    *((char_u *)ga.ga_data + ga.ga_len) = NUL;
+    ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
+
+    if (lnum == 0)
+    {
+	/* Delete the empty line that was in the empty buffer. */
+	curbuf = term->tl_buffer;
+	ml_delete(2, FALSE);
+	curbuf = curwin->w_buffer;
+    }
+
+failed:
+    ga_clear(&ga);
+}
+
+/*
+ * Add the current lines of the terminal to scrollback and to the buffer.
+ * Called after the job has ended and when switching to Terminal mode.
+ */
+    static void
+move_terminal_to_buffer(term_T *term)
+{
+    win_T	    *wp;
+    int		    len;
+    int		    lines_skipped = 0;
+    VTermPos	    pos;
+    VTermScreenCell cell;
+    VTermScreenCell *p;
+    VTermScreen	    *screen = vterm_obtain_screen(term->tl_vterm);
+
+    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)
+	    ++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;
+		    ++term->tl_scrollback.ga_len;
+
+		    add_scrollback_line_to_buffer(term);
+		}
+	    }
+
+	    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;
+
+		add_scrollback_line_to_buffer(term);
+	    }
+	    else
+		vim_free(p);
+	}
+    }
+
+    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;
+	    redraw_win_later(wp, NOT_VALID);
+	}
+    }
+}
+
+    static void
+set_terminal_mode(term_T *term, int on)
+{
+    term->tl_terminal_mode = on;
+    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)
+{
+    move_terminal_to_buffer(term);
+    term_free_vterm(term);
+    set_terminal_mode(term, FALSE);
+}
+
+/*
+ * Switch from sending keys to the job to Terminal-Normal mode.
+ * Suspends updating the terminal window.
+ */
+    static void
+term_enter_terminal_mode()
+{
+    term_T *term = curbuf->b_term;
+
+    /* Append the current terminal contents to the buffer. */
+    move_terminal_to_buffer(term);
+
+    set_terminal_mode(term, TRUE);
+}
+
+/*
+ * Returns TRUE if the current window contains a terminal and we are in
+ * Terminal-Normal mode.
+ */
+    int
+term_in_terminal_mode()
+{
+    term_T *term = curbuf->b_term;
+
+    return term != NULL && term->tl_terminal_mode;
+}
+
+/*
+ * Switch from Terminal-Normal mode to sending keys to the job.
+ * Restores updating the terminal window.
+ */
+    void
+term_leave_terminal_mode()
+{
+    term_T	*term = curbuf->b_term;
+    sb_line_T	*line;
+    garray_T	*gap;
+
+    /* Remove the terminal contents from the scrollback and the buffer. */
+    gap = &term->tl_scrollback;
+    while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled)
+    {
+	ml_delete(curbuf->b_ml.ml_line_count, FALSE);
+	line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
+	vim_free(line->sb_cells);
+	--gap->ga_len;
+	if (gap->ga_len == 0)
+	    break;
+    }
+    check_cursor();
+
+    set_terminal_mode(term, FALSE);
+
+    if (term->tl_channel_closed)
+	cleanup_vterm(term);
+    redraw_buf_and_status_later(curbuf, NOT_VALID);
+}
+
+/*
  * Get a key from the user without mapping.
  * TODO: use terminal mode mappings.
  */
@@ -641,6 +856,21 @@ send_keys_to_term(term_T *term, int c, i
 }
 
 /*
+ * Returns TRUE if the current window contains a terminal and we are sending
+ * keys to the job.
+ */
+    int
+term_use_loop()
+{
+    term_T *term = curbuf->b_term;
+
+    return term != NULL
+	&& !term->tl_terminal_mode
+	&& term->tl_vterm != NULL
+	&& term_job_running(term);
+}
+
+/*
  * 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.
@@ -653,10 +883,6 @@ terminal_loop(void)
     int		c;
     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);
 
@@ -665,6 +891,7 @@ terminal_loop(void)
 	/* TODO: skip screen update when handling a sequence of keys. */
 	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))
@@ -687,8 +914,15 @@ terminal_loop(void)
 		break;
 
 	    if (termkey == 0 && c == '.')
+	    {
 		/* "CTRL-W .": send CTRL-W to the job */
 		c = Ctrl_W;
+	    }
+	    else if (termkey == 0 && c == 'N')
+	    {
+		term_enter_terminal_mode();
+		return FAIL;
+	    }
 	    else if (termkey == 0 || c != termkey)
 	    {
 		stuffcharReadbuff(Ctrl_W);
@@ -704,6 +938,8 @@ terminal_loop(void)
 
 /*
  * Called when a job has finished.
+ * This updates the title and status, but does not close the vter, because
+ * there might still be pending output in the channel.
  */
     void
 term_job_ended(job_T *job)
@@ -891,120 +1127,12 @@ handle_pushline(int cols, const VTermScr
 	line->sb_cells = p;
 	++term->tl_scrollback.ga_len;
 	++term->tl_scrollback_scrolled;
+
+	add_scrollback_line_to_buffer(term);
     }
     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)
-	    ++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;
-		    ++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)
-	{
-	    if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
-		goto failed;
-	    for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
-		ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
-					     (char_u *)ga.ga_data + ga.ga_len);
-	}
-	if (ga_grow(&ga, 1) == FAIL)
-	    goto failed;
-	*((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 */
@@ -1029,14 +1157,16 @@ term_channel_closed(channel_T *ch)
     for (term = first_term; term != NULL; term = term->tl_next)
 	if (term->tl_job == ch->ch_job)
 	{
+	    term->tl_channel_closed = TRUE;
+
 	    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);
+	    /* Unless in Terminal-Normal mode: clear the vterm. */
+	    if (!term->tl_terminal_mode)
+		cleanup_vterm(term);
 
 	    redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
 	    did_one = TRUE;
@@ -1227,8 +1357,9 @@ term_update_window(win_T *wp)
     VTermState	*state;
     VTermPos	pos;
 
-    if (term == NULL || term->tl_vterm == NULL)
+    if (term == NULL || term->tl_vterm == NULL || term->tl_terminal_mode)
 	return FAIL;
+
     vterm = term->tl_vterm;
     screen = vterm_obtain_screen(vterm);
     state = vterm_obtain_state(vterm);
@@ -1347,6 +1478,18 @@ term_is_finished(buf_T *buf)
 }
 
 /*
+ * Return TRUE if "wp" is a terminal window where the job has finished or we
+ * are in Terminal-Normal mode.
+ */
+    int
+term_show_buffer(buf_T *buf)
+{
+    term_T *term = buf->b_term;
+
+    return term != NULL && (term->tl_vterm == NULL || term->tl_terminal_mode);
+}
+
+/*
  * The current buffer is going to be changed.  If there is terminal
  * highlighting remove it now.
  */
@@ -1450,7 +1593,14 @@ term_get_status_text(term_T *term)
 	char_u *txt;
 	size_t len;
 
-	if (term->tl_title != NULL)
+	if (term->tl_terminal_mode)
+	{
+	    if (term_job_running(term))
+		txt = (char_u *)_("Terminal");
+	    else
+		txt = (char_u *)_("Terminal-finished");
+	}
+	else if (term->tl_title != NULL)
 	    txt = term->tl_title;
 	else if (term_job_running(term))
 	    txt = (char_u *)_("running");
--- 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 */
 /**/
+    813,
+/**/
     812,
 /**/
     811,