mirror of
https://github.com/vim/vim.git
synced 2025-09-27 04:14:06 -04:00
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.
This commit is contained in:
398
src/terminal.c
398
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;
|
||||
@@ -552,6 +568,205 @@ term_job_running(term_T *term)
|
||||
&& channel_is_open(term->tl_job->jv_channel);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
@@ -640,6 +855,21 @@ send_keys_to_term(term_T *term, int c, int typed)
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -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 VTermScreenCell *cells, void *user)
|
||||
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);
|
||||
@@ -1346,6 +1477,18 @@ term_is_finished(buf_T *buf)
|
||||
return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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");
|
||||
|
Reference in New Issue
Block a user