summaryrefslogtreecommitdiffstats
path: root/src/terminal.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.c')
-rw-r--r--src/terminal.c398
1 files changed, 274 insertions, 124 deletions
diff --git a/src/terminal.c b/src/terminal.c
index 1863b59861..b0ba8c1902 100644
--- 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, int typed)
}
/*
+ * 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,118 +1127,10 @@ handle_pushline(int cols, const VTermScreenCell *cells, void *user)
line->sb_cells = p;
++term->tl_scrollback.ga_len;
++term->tl_scrollback_scrolled;
- }
- 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;
- }
+ add_scrollback_line_to_buffer(term);
}
+ return 0; /* ignored */
}
static VTermScreenCallbacks screen_callbacks = {
@@ -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");