terminal: 'scrollback'

Closes #2637
This commit is contained in:
Justin M. Keyes 2017-02-21 15:16:48 +01:00
parent 300eca3d30
commit e7bbd35c81
15 changed files with 282 additions and 208 deletions

View File

@ -4,28 +4,19 @@
NVIM REFERENCE MANUAL by Thiago de Arruda
Embedded terminal emulator *terminal-emulator*
Terminal emulator *terminal-emulator*
1. Introduction |terminal-emulator-intro|
2. Spawning |terminal-emulator-spawning|
3. Input |terminal-emulator-input|
4. Configuration |terminal-emulator-configuration|
5. Status Variables |terminal-emulator-status|
Nvim embeds a VT220/xterm terminal emulator based on libvterm. The terminal is
presented as a special buffer type, asynchronously updated from the virtual
terminal as data is received from the program connected to it.
Terminal buffers behave mostly like normal 'nomodifiable' buffers, except:
- Plugins can set 'modifiable' to modify text, but lines cannot be deleted.
- 'scrollback' controls how many off-screen lines are kept.
- Terminal output is followed if the cursor is on the last line.
==============================================================================
1. Introduction *terminal-emulator-intro*
Nvim offers a mostly complete VT220/xterm terminal emulator. The terminal is
presented as a special buffer type, asynchronously updated to mirror the
virtual terminal display as data is received from the program connected to it.
For most purposes, terminal buffers behave a lot like normal buffers with
'nomodifiable' set.
The implementation is powered by libvterm, a powerful abstract terminal
emulation library. http://www.leonerd.org.uk/code/libvterm/
==============================================================================
2. Spawning *terminal-emulator-spawning*
Spawning *terminal-emulator-spawning*
There are 3 ways to create a terminal buffer:
@ -40,34 +31,27 @@ There are 3 ways to create a terminal buffer:
Note: The "term://" pattern is handled by a BufReadCmd handler, so the
|autocmd-nested| modifier is required to use it in an autocmd. >
autocmd VimEnter * nested split term://sh
< This is only mentioned for reference; you should use the |:terminal|
command instead.
< This is only mentioned for reference; use |:terminal| instead.
When the terminal spawns the program, the buffer will start to mirror the
terminal display and change its name to `term://$CWD//$PID:$COMMAND`.
Note that |:mksession| will "save" the terminal buffers by restarting all
programs when the session is restored.
terminal display and change its name to `term://{cwd}//{pid}:{cmd}`.
The "term://..." scheme enables |:mksession| to "restore" a terminal buffer by
restarting the {cmd} when the session is loaded.
==============================================================================
3. Input *terminal-emulator-input*
Input *terminal-emulator-input*
Sending input is possible by entering terminal mode, which is achieved by
pressing any key that would enter insert mode in a normal buffer (|i| or |a|
for example). The |:terminal| ex command will automatically enter terminal
mode once it's spawned. While in terminal mode, Nvim will forward all keys to
the underlying program. The only exception is the <C-\><C-n> key combo,
which will exit back to normal mode.
To send input, enter terminal mode using any command that would enter "insert
mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys
except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return
to normal mode. |CTRL-\_CTRL-N|
Terminal mode has its own namespace for mappings, which is accessed with the
"t" prefix. It's possible to use terminal mappings to customize interaction
with the terminal. For example, here's how to map <Esc> to exit terminal mode:
>
Terminal mode has its own |:tnoremap| namespace for mappings, this can be used
to automate any terminal interaction. To map <Esc> to exit terminal mode: >
:tnoremap <Esc> <C-\><C-n>
<
Navigating to other windows is only possible by exiting to normal mode, which
can be cumbersome with <C-\><C-n> keys. To improve the navigation experience,
you could use the following mappings:
>
Navigating to other windows is only possible in normal mode. For convenience,
you could use these mappings: >
:tnoremap <A-h> <C-\><C-n><C-w>h
:tnoremap <A-j> <C-\><C-n><C-w>j
:tnoremap <A-k> <C-\><C-n><C-w>k
@ -77,11 +61,9 @@ you could use the following mappings:
:nnoremap <A-k> <C-w>k
:nnoremap <A-l> <C-w>l
<
This configuration allows using `Alt+{h,j,k,l}` to navigate between windows no
matter if they are displaying a normal buffer or a terminal buffer in terminal
mode.
Then you can use `Alt+{h,j,k,l}` to navigate between windows from any mode.
Mouse input is also fully supported, and has the following behavior:
Mouse input is supported, and has the following behavior:
- If the program has enabled mouse events, the corresponding events will be
forwarded to the program.
@ -93,27 +75,23 @@ Mouse input is also fully supported, and has the following behavior:
the terminal wont lose focus and the hovered window will be scrolled.
==============================================================================
4. Configuration *terminal-emulator-configuration*
Configuration *terminal-emulator-configuration*
Terminal buffers can be customized through the following global/buffer-local
variables (set via the |TermOpen| autocmd):
Options: 'scrollback'
Events: |TermOpen|, |TermClose|
Highlight groups: |hl-TermCursor|, |hl-TermCursorNC|
Terminal colors can be customized with these variables:
- 'scrollback' option: Scrollback lines (output history) limit.
- `{g,b}:terminal_color_$NUM`: The terminal color palette, where `$NUM` is the
color index, between 0 and 255 inclusive. This setting only affects UIs with
RGB capabilities; for normal terminals the color index is simply forwarded.
The configuration variables are only processed when the terminal starts, which
is why it needs to be done with the |TermOpen| autocmd or setting global
variables before the terminal is started.
There is also a corresponding |TermClose| event.
The terminal cursor can be highlighted via |hl-TermCursor| and
|hl-TermCursorNC|.
The `{g,b}:terminal_color_$NUM` variables are processed only when the terminal
starts (after |TermOpen|).
==============================================================================
5. Status Variables *terminal-emulator-status*
Status Variables *terminal-emulator-status*
Terminal buffers maintain some information about the terminal in buffer-local
variables:
@ -126,11 +104,8 @@ variables:
- *b:terminal_job_pid* The PID of the top-level process running in the
terminal.
These variables will have a value by the time the TermOpen autocmd runs, and
will continue to have a value for the lifetime of the terminal buffer, making
them suitable for use in 'statusline'. For example, to show the terminal title
as the status line:
>
These variables are initialized before TermOpen, so you can use them in
a local 'statusline'. Example: >
:autocmd TermOpen * setlocal statusline=%{b:term_title}
<
==============================================================================

View File

@ -4949,9 +4949,15 @@ A jump table for the options with a short description can be found at |Q_op|.
be used as the new value for 'scroll'. Reset to half the window
height with ":set scroll=0".
*'scrollback'* *'scbk'* *'noscrollback'* *'noscbk'*
'scrollback' 'scbk' boolean (default: 1000)
global or local to buffer |global-local|
*'scrollback'* *'scbk'*
'scrollback' 'scbk' number (default: 1000
in normal buffers: -1)
local to buffer
Maximum number of lines kept beyond the visible screen. Lines at the
top are deleted if new lines exceed this limit.
Only in |terminal-emulator| buffers. 'buftype'
-1 means "unlimited" for normal buffers, 100000 otherwise.
Minimum is 1.
*'scrollbind'* *'scb'* *'noscrollbind'* *'noscb'*
'scrollbind' 'scb' boolean (default off)

View File

@ -207,23 +207,15 @@ g8 Print the hex values of the bytes used in the
:sh[ell] Removed. |vim-differences| {Nvim}
*:terminal* *:te*
:te[rminal][!] {cmd} Spawns {cmd} using the current value of 'shell' and
'shellcmdflag' in a new terminal buffer. This is
equivalent to: >
:te[rminal][!] {cmd} Execute {cmd} with 'shell' in a |terminal-emulator|
buffer. Equivalent to: >
:enew
:call termopen('{cmd}')
:startinsert
<
If no {cmd} is given, 'shellcmdflag' will not be sent
to |termopen()|.
See |jobstart()|.
Like |:enew|, it will fail if the current buffer is
modified, but can be forced with "!". See |termopen()|
and |terminal-emulator|.
To switch to terminal mode automatically:
>
To enter terminal mode automatically: >
autocmd BufEnter term://* startinsert
<
*:!cmd* *:!* *E34*

View File

@ -10,7 +10,7 @@
Memcheck:Leak
fun:malloc
fun:uv_spawn
fun:pipe_process_spawn
fun:libuv_process_spawn
fun:process_spawn
fun:job_start
}

View File

@ -5845,8 +5845,8 @@ bool garbage_collect(bool testing)
garbage_collect_at_exit = false;
}
// We advance by two because we add one for items referenced through
// previous_funccal.
// We advance by two (COPYID_INC) because we add one for items referenced
// through previous_funccal.
const int copyID = get_copyID();
// 1. Go through all accessible variables and mark all lists and dicts

View File

@ -7420,22 +7420,20 @@ static void nv_esc(cmdarg_T *cap)
restart_edit = 'a';
}
/*
* Handle "A", "a", "I", "i" and <Insert> commands.
*/
/// Handle "A", "a", "I", "i" and <Insert> commands.
static void nv_edit(cmdarg_T *cap)
{
/* <Insert> is equal to "i" */
if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS)
// <Insert> is equal to "i"
if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS) {
cap->cmdchar = 'i';
}
/* in Visual mode "A" and "I" are an operator */
if (VIsual_active && (cap->cmdchar == 'A' || cap->cmdchar == 'I'))
// in Visual mode "A" and "I" are an operator
if (VIsual_active && (cap->cmdchar == 'A' || cap->cmdchar == 'I')) {
v_visop(cap);
/* in Visual mode and after an operator "a" and "i" are for text objects */
else if ((cap->cmdchar == 'a' || cap->cmdchar == 'i')
&& (cap->oap->op_type != OP_NOP || VIsual_active)) {
// in Visual mode and after an operator "a" and "i" are for text objects
} else if ((cap->cmdchar == 'a' || cap->cmdchar == 'i')
&& (cap->oap->op_type != OP_NOP || VIsual_active)) {
nv_object(cap);
} else if (!curbuf->b_p_ma && !p_im && !curbuf->terminal) {
// Only give this error when 'insertmode' is off.

View File

@ -3994,16 +3994,7 @@ set_num_option (
/*
* Number options that need some action when changed
*/
if (pp == &p_scbk) {
// 'scrollback'
if (p_scbk < 1) {
errmsg = e_invarg;
p_scbk = 0;
} else if (p_scbk > 100000) {
errmsg = e_invarg;
p_scbk = 100000;
}
} else if (pp == &p_wh || pp == &p_hh) {
if (pp == &p_wh || pp == &p_hh) {
if (p_wh < 1) {
errmsg = e_positive;
p_wh = 1;
@ -4205,7 +4196,19 @@ set_num_option (
FOR_ALL_TAB_WINDOWS(tp, wp) {
check_colorcolumn(wp);
}
} else if (pp == &curbuf->b_p_scbk) {
// 'scrollback'
if (!curbuf->terminal) {
errmsg = e_invarg;
curbuf->b_p_scbk = -1;
} else {
if (curbuf->b_p_scbk < -1 || curbuf->b_p_scbk > 100000) {
errmsg = e_invarg;
curbuf->b_p_scbk = 1000;
}
// Force the scrollback to take effect.
terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX);
}
}
/*
@ -5641,7 +5644,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_ai = p_ai;
buf->b_p_ai_nopaste = p_ai_nopaste;
buf->b_p_sw = p_sw;
buf->b_p_scbk = p_scbk;
buf->b_p_scbk = -1;
buf->b_p_tw = p_tw;
buf->b_p_tw_nopaste = p_tw_nopaste;
buf->b_p_tw_nobin = p_tw_nobin;

View File

@ -1918,7 +1918,7 @@ return {
vi_def=true,
varname='p_scbk',
redraw={'current_buffer'},
defaults={if_true={vi=1000}}
defaults={if_true={vi=-1}}
},
{
full_name='scrollbind', abbreviation='scb',

View File

@ -7113,8 +7113,9 @@ void showruler(int always)
}
if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) {
redraw_custom_statusline(curwin);
} else
} else {
win_redr_ruler(curwin, always);
}
if (need_maketitle
|| (p_icon && (stl_syntax & STL_IN_ICON))

View File

@ -1,18 +1,17 @@
// VT220/xterm-like terminal emulator implementation for nvim. Powered by
// libvterm (http://www.leonerd.org.uk/code/libvterm/).
// VT220/xterm-like terminal emulator.
// Powered by libvterm http://www.leonerd.org.uk/code/libvterm
//
// libvterm is a pure C99 terminal emulation library with abstract input and
// display. This means that the library needs to read data from the master fd
// and feed VTerm instances, which will invoke user callbacks with screen
// update instructions that must be mirrored to the real display.
//
// Keys are pressed in VTerm instances by calling
// Keys are sent to VTerm instances by calling
// vterm_keyboard_key/vterm_keyboard_unichar, which generates byte streams that
// must be fed back to the master fd.
//
// This implementation uses nvim buffers as the display mechanism for both
// the visible screen and the scrollback buffer. When focused, the window
// "pins" to the bottom of the buffer and mirrors libvterm screen state.
// Nvim buffers are used as the display mechanism for both the visible screen
// and the scrollback buffer.
//
// When a line becomes invisible due to a decrease in screen height or because
// a line was pushed up during normal terminal output, we store the line
@ -23,18 +22,17 @@
// scrollback buffer, which is mirrored in the nvim buffer displaying lines
// that were previously invisible.
//
// The vterm->nvim synchronization is performed in intervals of 10
// milliseconds. This is done to minimize screen updates when receiving large
// bursts of data.
// The vterm->nvim synchronization is performed in intervals of 10 milliseconds,
// to minimize screen updates when receiving large bursts of data.
//
// This module is decoupled from the processes that normally feed it data, so
// it's possible to use it as a general purpose console buffer (possibly as a
// log/display mechanism for nvim in the future)
//
// Inspired by vimshell (http://www.wana.at/vimshell/) and
// Conque (https://code.google.com/p/conque/). Libvterm usage instructions (plus
// some extra code) were taken from
// pangoterm (http://www.leonerd.org.uk/code/pangoterm/)
// Inspired by: vimshell http://www.wana.at/vimshell
// Conque https://code.google.com/p/conque
// Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
@ -87,10 +85,10 @@ typedef struct terminal_state {
# include "terminal.c.generated.h"
#endif
#define SCROLLBACK_BUFFER_DEFAULT_SIZE 1000
#define SB_MAX 100000 // Maximum 'scrollback' value.
// Delay for refreshing the terminal buffer after receiving updates from
// libvterm. This is greatly improves performance when receiving large bursts
// of data.
// libvterm. Improves performance when receiving large bursts of data.
#define REFRESH_DELAY 10
static TimeWatcher refresh_timer;
@ -102,27 +100,23 @@ typedef struct {
} ScrollbackLine;
struct terminal {
// options passed to terminal_open
TerminalOptions opts;
// libvterm structures
TerminalOptions opts; // options passed to terminal_open
VTerm *vt;
VTermScreen *vts;
// buffer used to:
// - convert VTermScreen cell arrays into utf8 strings
// - receive data from libvterm as a result of key presses.
char textbuf[0x1fff];
// Scrollback buffer storage for libvterm.
// TODO(tarruda): Use a doubly-linked list
ScrollbackLine **sb_buffer;
// number of rows pushed to sb_buffer
size_t sb_current;
// sb_buffer size;
size_t sb_size;
ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm
size_t sb_current; // number of rows pushed to sb_buffer
size_t sb_size; // sb_buffer size
// "virtual index" that points to the first sb_buffer row that we need to
// push to the terminal buffer when refreshing the scrollback. When negative,
// it actually points to entries that are no longer in sb_buffer (because the
// window height has increased) and must be deleted from the terminal buffer
int sb_pending;
// buf_T instance that acts as a "drawing surface" for libvterm
// we can't store a direct reference to the buffer because the
// refresh_timer_cb may be called after the buffer was freed, and there's
@ -130,20 +124,18 @@ struct terminal {
handle_T buf_handle;
// program exited
bool closed, destroy;
// some vterm properties
bool forward_mouse;
// invalid rows libvterm screen
int invalid_start, invalid_end;
int invalid_start, invalid_end; // invalid rows in libvterm screen
struct {
int row, col;
bool visible;
} cursor;
// which mouse button is pressed
int pressed_button;
// pending width/height
bool pending_resize;
// With a reference count of 0 the terminal can be freed.
size_t refcount;
int pressed_button; // which mouse button is pressed
bool pending_resize; // pending width/height
size_t refcount; // reference count
};
static VTermScreenCallbacks vterm_screen_callbacks = {
@ -238,28 +230,21 @@ Terminal *terminal_open(TerminalOptions opts)
refresh_screen(rv, curbuf);
set_option_value((uint8_t *)"buftype", 0, (uint8_t *)"terminal", OPT_LOCAL);
// some sane settings for terminal buffers
// Default settings for terminal buffers
curbuf->b_p_ma = false; // 'nomodifiable'
curbuf->b_p_ul = -1; // disable undo
curbuf->b_p_scbk = 1000; // 'scrollback'
set_option_value((uint8_t *)"wrap", false, NULL, OPT_LOCAL);
set_option_value((uint8_t *)"number", false, NULL, OPT_LOCAL);
set_option_value((uint8_t *)"relativenumber", false, NULL, OPT_LOCAL);
buf_set_term_title(curbuf, (char *)curbuf->b_ffname);
RESET_BINDING(curwin);
// Apply TermOpen autocmds so the user can configure the terminal
// Apply TermOpen autocmds _before_ configuring the scrollback buffer.
apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf);
// Configure the scrollback buffer. Try to get the size from:
//
// - b:terminal_scrollback_buffer_size
// - g:terminal_scrollback_buffer_size
// - SCROLLBACK_BUFFER_DEFAULT_SIZE
//
// but limit to 100k.
int size = get_config_int("terminal_scrollback_buffer_size");
rv->sb_size = size > 0 ? (size_t)size : SCROLLBACK_BUFFER_DEFAULT_SIZE;
rv->sb_size = MIN(rv->sb_size, 100000);
// Configure the scrollback buffer.
rv->sb_size = curbuf->b_p_scbk < 0 ? SB_MAX : (size_t)curbuf->b_p_scbk;;
rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
if (!true_color) {
@ -338,22 +323,22 @@ void terminal_close(Terminal *term, char *msg)
void terminal_resize(Terminal *term, uint16_t width, uint16_t height)
{
if (term->closed) {
// will be called after exited if two windows display the same terminal and
// one of the is closed as a consequence of pressing a key.
// If two windows display the same terminal and one is closed by keypress.
return;
}
bool force = width == UINT16_MAX || height == UINT16_MAX;
int curwidth, curheight;
vterm_get_size(term->vt, &curheight, &curwidth);
if (!width) {
if (force || !width) {
width = (uint16_t)curwidth;
}
if (!height) {
if (force || !height) {
height = (uint16_t)curheight;
}
if (curheight == height && curwidth == width) {
if (!force && curheight == height && curwidth == width) {
return;
}
@ -671,10 +656,15 @@ static int term_bell(void *data)
return 1;
}
// the scrollback push/pop handlers were copied almost verbatim from pangoterm
// Scrollback push handler (from pangoterm).
static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
{
Terminal *term = data;
if (!term->sb_size) {
return 0;
}
// copy vterm cells into sb_buffer
size_t c = (size_t)cols;
ScrollbackLine *sbrow = NULL;
@ -686,10 +676,12 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
xfree(term->sb_buffer[term->sb_current - 1]);
}
// Make room at the start by shifting to the right.
memmove(term->sb_buffer + 1, term->sb_buffer,
sizeof(term->sb_buffer[0]) * (term->sb_current - 1));
} else if (term->sb_current > 0) {
// Make room at the start by shifting to the right.
memmove(term->sb_buffer + 1, term->sb_buffer,
sizeof(term->sb_buffer[0]) * term->sb_current);
}
@ -699,6 +691,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
sbrow->cols = c;
}
// New row is added at the start of the storage buffer.
term->sb_buffer[0] = sbrow;
if (term->sb_current < term->sb_size) {
term->sb_current++;
@ -714,6 +707,11 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
return 1;
}
/// Scrollback pop handler (from pangoterm).
///
/// @param cols
/// @param cells VTerm state to update.
/// @param data Terminal
static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
{
Terminal *term = data;
@ -726,24 +724,24 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
term->sb_pending--;
}
// restore vterm state
size_t c = (size_t)cols;
ScrollbackLine *sbrow = term->sb_buffer[0];
term->sb_current--;
// Forget the "popped" row by shifting the rest onto it.
memmove(term->sb_buffer, term->sb_buffer + 1,
sizeof(term->sb_buffer[0]) * (term->sb_current));
size_t cols_to_copy = c;
size_t cols_to_copy = (size_t)cols;
if (cols_to_copy > sbrow->cols) {
cols_to_copy = sbrow->cols;
}
// copy to vterm state
memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
for (size_t col = cols_to_copy; col < c; col++) {
for (size_t col = cols_to_copy; col < (size_t)cols; col++) {
cells[col].chars[0] = 0;
cells[col].width = 1;
}
xfree(sbrow);
pmap_put(ptr_t)(invalidated_terminals, term, NULL);
@ -889,7 +887,7 @@ static bool send_mouse_event(Terminal *term, int c)
// terminal buffer refresh & misc {{{
void fetch_row(Terminal *term, int row, int end_col)
static void fetch_row(Terminal *term, int row, int end_col)
{
int col = 0;
size_t line_len = 0;
@ -977,8 +975,7 @@ static void refresh_terminal(Terminal *term)
});
adjust_topline(term, buf, pending_resize);
}
// libuv timer callback. This will enqueue on_refresh to be processed as an
// event.
// Calls refresh_terminal() on all invalidated_terminals.
static void refresh_timer_cb(TimeWatcher *watcher, void *data)
{
if (exiting) { // Cannot redraw (requires event loop) during teardown/exit.
@ -1012,7 +1009,37 @@ static void refresh_size(Terminal *term, buf_T *buf)
term->opts.resize_cb((uint16_t)width, (uint16_t)height, term->opts.data);
}
// Refresh the scrollback of a invalidated terminal
/// Adjusts scrollback storage after 'scrollback' option changed.
static void on_scrollback_option_changed(Terminal *term, buf_T *buf)
{
const size_t scbk = curbuf->b_p_scbk < 0
? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk);
assert(term->sb_current < SIZE_MAX);
if (term->sb_pending > 0) { // Pending rows must be processed first.
abort();
}
// Delete lines exceeding the new 'scrollback' limit.
if (scbk < term->sb_current) {
size_t diff = term->sb_current - scbk;
for (size_t i = 0; i < diff; i++) {
ml_delete(1, false);
term->sb_current--;
xfree(term->sb_buffer[term->sb_current]);
}
deleted_lines(1, (long)diff);
}
// Resize the scrollback storage.
size_t sb_region = sizeof(ScrollbackLine *) * scbk;
if (scbk != term->sb_size) {
term->sb_buffer = xrealloc(term->sb_buffer, sb_region);
}
term->sb_size = scbk;
}
// Refresh the scrollback of an invalidated terminal.
static void refresh_scrollback(Terminal *term, buf_T *buf)
{
int width, height;
@ -1041,6 +1068,8 @@ static void refresh_scrollback(Terminal *term, buf_T *buf)
ml_delete(buf->b_ml.ml_line_count, false);
deleted_lines(buf->b_ml.ml_line_count, 1);
}
on_scrollback_option_changed(term, buf);
}
// Refresh the screen (visible part of the buffer when the terminal is
@ -1052,8 +1081,7 @@ static void refresh_screen(Terminal *term, buf_T *buf)
int height;
int width;
vterm_get_size(term->vt, &height, &width);
// It's possible that the terminal height decreased and `term->invalid_end`
// doesn't reflect it yet
// Terminal height may have decreased before `invalid_end` reflects it.
term->invalid_end = MIN(term->invalid_end, height);
for (int r = term->invalid_start, linenr = row_to_linenr(term, r);
@ -1182,17 +1210,6 @@ static char *get_config_string(char *key)
return NULL;
}
static int get_config_int(char *key)
{
Object obj;
GET_CONFIG_VALUE(key, obj);
if (obj.type == kObjectTypeInteger) {
return (int)obj.data.integer;
}
api_free_object(obj);
return 0;
}
// }}}
// vim: foldmethod=marker

View File

@ -308,8 +308,9 @@ bool undo_allowed(void)
/// Get the 'undolevels' value for the current buffer.
static long get_undolevel(void)
{
if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL)
if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL) {
return p_ul;
}
return curbuf->b_p_ul;
}

View File

@ -31,45 +31,40 @@ describe(':edit term://*', function()
eq(termopen_runs[1], termopen_runs[1]:match('^term://.//%d+:$'))
end)
it('runs TermOpen early enough to respect terminal_scrollback_buffer_size', function()
it("runs TermOpen early enough to set buffer-local 'scrollback'", function()
local columns, lines = 20, 4
local scr = get_screen(columns, lines)
local rep = 'a'
meths.set_option('shellcmdflag', 'REP ' .. rep)
local rep_size = rep:byte()
local rep_size = rep:byte() -- 'a' => 97
local sb = 10
local gsb = 20
meths.set_var('terminal_scrollback_buffer_size', gsb)
command('autocmd TermOpen * :let b:terminal_scrollback_buffer_size = '
.. tostring(sb))
command('autocmd TermOpen * :setlocal scrollback='..tostring(sb))
command('edit term://foobar')
local bufcontents = {}
local winheight = curwinmeths.get_height()
-- I have no idea why there is + 4 needed. But otherwise it works fine with
-- different scrollbacks.
local shift = -4
local buf_cont_start = rep_size - 1 - sb - winheight - shift
local bufline = function(i) return ('%d: foobar'):format(i) end
local buf_cont_start = rep_size - sb - winheight + 2
local function bufline (i)
return ('%d: foobar'):format(i)
end
for i = buf_cont_start,(rep_size - 1) do
bufcontents[#bufcontents + 1] = bufline(i)
end
bufcontents[#bufcontents + 1] = ''
bufcontents[#bufcontents + 1] = '[Process exited 0]'
-- Do not ask me why displayed screen is one line *before* buffer
-- contents: buffer starts with 87:, screen with 86:.
local exp_screen = '\n'
local did_cursor = false
for i = 0,(winheight - 1) do
local line = bufline(buf_cont_start + i - 1)
for i = 1,(winheight - 1) do
local line = bufcontents[#bufcontents - winheight + i]
exp_screen = (exp_screen
.. (did_cursor and '' or '^')
.. line
.. (' '):rep(columns - #line)
.. '|\n')
did_cursor = true
end
exp_screen = exp_screen .. (' '):rep(columns) .. '|\n'
exp_screen = exp_screen..'^[Process exited 0] |\n'
exp_screen = exp_screen..(' '):rep(columns)..'|\n'
scr:expect(exp_screen)
eq(bufcontents, curbufmeths.get_lines(1, -1, true))
eq(bufcontents, curbufmeths.get_lines(0, -1, true))
end)
end)

View File

@ -20,22 +20,18 @@ describe(':terminal', function()
source([[
echomsg "msg1"
echomsg "msg2"
echomsg "msg3"
]])
-- Invoke a command that emits frequent terminal activity.
execute([[terminal while true; do echo X; done]])
helpers.feed([[<C-\><C-N>]])
screen:expect([[
X |
X |
^X |
|
]])
wait()
helpers.sleep(10) -- Let some terminal activity happen.
execute("messages")
screen:expect([[
X |
msg1 |
msg2 |
msg3 |
Press ENTER or type command to continue^ |
]])
end)

View File

@ -37,7 +37,6 @@ local default_command = '["'..nvim_dir..'/tty-test'..'"]'
local function screen_setup(extra_height, command)
nvim('command', 'highlight TermCursor cterm=reverse')
nvim('command', 'highlight TermCursorNC ctermbg=11')
nvim('set_var', 'terminal_scrollback_buffer_size', 10)
if not extra_height then extra_height = 0 end
if not command then command = default_command end
local screen = Screen.new(50, 7 + extra_height)
@ -58,7 +57,9 @@ local function screen_setup(extra_height, command)
-- tty-test puts the terminal into raw mode and echoes all input. tests are
-- done by feeding it with terminfo codes to control the display and
-- verifying output with screen:expect.
execute('enew | call termopen('..command..') | startinsert')
execute('enew | call termopen('..command..')')
execute('setlocal scrollback=10')
execute('startinsert')
if command == default_command then
-- wait for "tty ready" to be printed before each test or the terminal may
-- still be in canonical mode(will echo characters for example)

View File

@ -3,7 +3,11 @@ local helpers = require('test.functional.helpers')(after_each)
local thelpers = require('test.functional.terminal.helpers')
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
local feed, nvim_dir, execute = helpers.feed, helpers.nvim_dir, helpers.execute
local eval = helpers.eval
local command = helpers.command
local wait = helpers.wait
local retry = helpers.retry
local curbufmeths = helpers.curbufmeths
local feed_data = thelpers.feed_data
if helpers.pending_win32(pending) then return end
@ -20,7 +24,7 @@ describe('terminal scrollback', function()
screen:detach()
end)
describe('when the limit is crossed', function()
describe('when the limit is exceeded', function()
before_each(function()
local lines = {}
for i = 1, 30 do
@ -359,3 +363,88 @@ describe('terminal prints more lines than the screen height and exits', function
end)
end)
describe("'scrollback' option", function()
before_each(function()
clear()
end)
local function expect_lines(expected)
local actual = eval("line('$')")
if expected ~= actual then
error('expected: '..expected..', actual: '..tostring(actual))
end
end
it('set to 0 behaves as 1', function()
local screen = thelpers.screen_setup(nil, "['sh']", 30)
curbufmeths.set_option('scrollback', 0)
feed_data('for i in $(seq 1 30); do echo "line$i"; done\n')
screen:expect('line30 ', nil, nil, nil, true)
retry(nil, nil, function() expect_lines(7) end)
screen:detach()
end)
it('deletes lines (only) if necessary', function()
local screen = thelpers.screen_setup(nil, "['sh']", 30)
curbufmeths.set_option('scrollback', 200)
-- Wait for prompt.
screen:expect('$', nil, nil, nil, true)
wait()
feed_data('for i in $(seq 1 30); do echo "line$i"; done\n')
screen:expect('line30 ', nil, nil, nil, true)
retry(nil, nil, function() expect_lines(33) end)
curbufmeths.set_option('scrollback', 10)
wait()
retry(nil, nil, function() expect_lines(16) end)
curbufmeths.set_option('scrollback', 10000)
eq(16, eval("line('$')"))
-- Terminal job data is received asynchronously, may happen before the
-- 'scrollback' option is synchronized with the internal sb_buffer.
command('sleep 100m')
feed_data('for i in $(seq 1 40); do echo "line$i"; done\n')
screen:expect('line40 ', nil, nil, nil, true)
retry(nil, nil, function() expect_lines(58) end)
-- Verify off-screen state
eq('line35', eval("getline(line('w0') - 1)"))
eq('line26', eval("getline(line('w0') - 10)"))
screen:detach()
end)
it('defaults to 1000', function()
execute('terminal')
eq(1000, curbufmeths.get_option('scrollback'))
end)
it('error if set to invalid values', function()
local status, rv = pcall(command, 'set scrollback=-2')
eq(false, status) -- assert failure
eq('E474:', string.match(rv, "E%d*:"))
status, rv = pcall(command, 'set scrollback=100001')
eq(false, status) -- assert failure
eq('E474:', string.match(rv, "E%d*:"))
end)
it('defaults to -1 on normal buffers', function()
execute('new')
eq(-1, curbufmeths.get_option('scrollback'))
end)
it('error if set on a normal buffer', function()
command('new')
execute('set scrollback=42')
feed('<CR>')
eq('E474:', string.match(eval("v:errmsg"), "E%d*:"))
end)
end)