Merge #6142 from justinmk/term-modifiable

terminal: 'modifiable'; 'scrollback'; follow output only if cursor is on last line
This commit is contained in:
Justin M. Keyes 2017-02-27 09:59:58 +01:00 committed by GitHub
commit e502cca010
20 changed files with 549 additions and 451 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,28 +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:
- `{g,b}:terminal_scrollback_buffer_size`: Scrollback buffer size, between 1
and 100000 inclusive. The default is 1000.
- `{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:
@ -127,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,6 +4949,16 @@ 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'*
'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)
local to window

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

@ -36,7 +36,7 @@ typedef struct {
// for Map(K, V)
#include "nvim/map.h"
#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
#define MODIFIABLE(buf) (buf->b_p_ma)
/*
* Flags for w_valid.
@ -91,32 +91,22 @@ typedef struct frame_S frame_T;
// for struct memline (it needs memfile_T)
#include "nvim/memline_defs.h"
// for struct memfile, bhdr_T, blocknr_T... (it needs buf_T)
#include "nvim/memfile_defs.h"
/*
* This is here because regexp_defs.h needs win_T and buf_T. regprog_T is
* used below.
*/
// for regprog_T. Needs win_T and buf_T.
#include "nvim/regexp_defs.h"
// for synstate_T (needs reg_extmatch_T, win_T and buf_T)
// for synstate_T (needs reg_extmatch_T, win_T, buf_T)
#include "nvim/syntax_defs.h"
// for signlist_T
#include "nvim/sign_defs.h"
// for bufhl_*_T
#include "nvim/bufhl_defs.h"
typedef Map(linenr_T, bufhl_vec_T) bufhl_info_T;
// for FileID
#include "nvim/os/fs_defs.h"
// for Terminal
#include "nvim/terminal.h"
#include "nvim/os/fs_defs.h" // for FileID
#include "nvim/terminal.h" // for Terminal
/*
* The taggy struct is used to store the information about a :tag command.
@ -661,6 +651,7 @@ struct file_buffer {
char_u *b_p_qe; ///< 'quoteescape'
int b_p_ro; ///< 'readonly'
long b_p_sw; ///< 'shiftwidth'
long b_p_scbk; ///< 'scrollback'
int b_p_si; ///< 'smartindent'
long b_p_sts; ///< 'softtabstop'
long b_p_sts_nopaste; ///< b_p_sts saved for paste mode

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

@ -321,14 +321,15 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
if (eof) {
close_channel(channel);
call_set_error(channel, "Channel was closed by the client");
char buf[256];
snprintf(buf, sizeof(buf), "channel %" PRIu64 " was closed by the client",
channel->id);
call_set_error(channel, buf);
goto end;
}
size_t count = rbuffer_size(rbuf);
DLOG("Feeding the msgpack parser with %u bytes of data from Stream(%p)",
count,
stream);
DLOG("parsing %u bytes of msgpack data from Stream(%p)", count, stream);
// Feed the unpacker with data
msgpack_unpacker_reserve_buffer(channel->unpacker, count);
@ -350,11 +351,9 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
complete_call(&unpacked.data, channel);
} else {
char buf[256];
snprintf(buf,
sizeof(buf),
"Channel %" PRIu64 " returned a response that doesn't have "
"a matching request id. Ensure the client is properly "
"synchronized",
snprintf(buf, sizeof(buf),
"channel %" PRIu64 " sent a response without a matching "
"request id. Ensure the client is properly synchronized",
channel->id);
call_set_error(channel, buf);
}
@ -406,7 +405,7 @@ static void handle_request(Channel *channel, msgpack_object *request)
&out_buffer))) {
char buf[256];
snprintf(buf, sizeof(buf),
"Channel %" PRIu64 " sent an invalid message, closed.",
"channel %" PRIu64 " sent an invalid message, closed.",
channel->id);
call_set_error(channel, buf);
}
@ -716,7 +715,7 @@ static void complete_call(msgpack_object *obj, Channel *channel)
static void call_set_error(Channel *channel, char *msg)
{
ELOG("msgpack-rpc: %s", msg);
ELOG("RPC: %s", msg);
for (size_t i = 0; i < kv_size(channel->call_stack); i++) {
ChannelCallFrame *frame = kv_A(channel->call_stack, i);
frame->returned = true;
@ -791,7 +790,7 @@ static void decref(Channel *channel)
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
#define REQ "[request] "
#define RES "[response] "
#define NOT "[notification] "
#define NOT "[notify] "
#define ERR "[error] "
// Cannot define array with negative offsets, so this one is needed to be added
@ -810,7 +809,7 @@ static void log_server_msg(uint64_t channel_id,
{
msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked);
DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id);
DLOGN("RPC ->ch %" PRIu64 ": ", channel_id);
const msgpack_unpack_return result =
msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
switch (result) {
@ -847,7 +846,7 @@ static void log_client_msg(uint64_t channel_id,
bool is_request,
msgpack_object msg)
{
DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id);
DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
log_lock();
FILE *f = open_log_file();
fprintf(f, is_request ? REQ : RES);

View File

@ -7418,27 +7418,23 @@ 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) {
/* Only give this error when 'insertmode' is off. */
} else if (!curbuf->b_p_ma && !p_im && !curbuf->terminal) {
// Only give this error when 'insertmode' is off.
EMSG(_(e_modifiable));
clearop(cap->oap);
} else if (!checkclearopq(cap->oap)) {

View File

@ -1,24 +1,18 @@
/*
* Code to handle user-settable options. This is all pretty much table-
* driven. Checklist for adding a new option:
* - Put it in the options array below (copy an existing entry).
* - For a global option: Add a variable for it in option_defs.h.
* - For a buffer or window local option:
* - Add a PV_XX entry to the enum below.
* - Add a variable to the window or buffer struct in buffer_defs.h.
* - For a window option, add some code to copy_winopt().
* - For a buffer option, add some code to buf_copy_options().
* - For a buffer string option, add code to check_buf_options().
* - If it's a numeric option, add any necessary bounds checks to do_set().
* - If it's a list of flags, add some code in do_set(), search for WW_ALL.
* - When adding an option with expansion (P_EXPAND), but with a different
* default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP.
* - Add documentation! One line in doc/help.txt, full description in
* options.txt, and any other related places.
* - Add an entry in runtime/optwin.vim.
* When making changes:
* - Adjust the help for the option in doc/option.txt.
*/
// User-settable options. Checklist for adding a new option:
// - Put it in options.lua
// - For a global option: Add a variable for it in option_defs.h.
// - For a buffer or window local option:
// - Add a BV_XX or WV_XX entry to option_defs.h
// - Add a variable to the window or buffer struct in buffer_defs.h.
// - For a window option, add some code to copy_winopt().
// - For a buffer option, add some code to buf_copy_options().
// - For a buffer string option, add code to check_buf_options().
// - If it's a numeric option, add any necessary bounds checks to do_set().
// - If it's a list of flags, add some code in do_set(), search for WW_ALL.
// - When adding an option with expansion (P_EXPAND), but with a different
// default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP.
// - Add documentation! doc/options.txt, and any other related places.
// - Add an entry in runtime/optwin.vim.
#define IN_OPTION_C
#include <assert.h>
@ -161,6 +155,7 @@ static long p_ts;
static long p_tw;
static int p_udf;
static long p_wm;
static long p_scbk;
static char_u *p_keymap;
/* Saved values for when 'bin' is set. */
@ -4201,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);
}
}
/*
@ -5426,6 +5433,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_PI: return (char_u *)&(curbuf->b_p_pi);
case PV_QE: return (char_u *)&(curbuf->b_p_qe);
case PV_RO: return (char_u *)&(curbuf->b_p_ro);
case PV_SCBK: return (char_u *)&(curbuf->b_p_scbk);
case PV_SI: return (char_u *)&(curbuf->b_p_si);
case PV_STS: return (char_u *)&(curbuf->b_p_sts);
case PV_SUA: return (char_u *)&(curbuf->b_p_sua);
@ -5636,6 +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 = -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

@ -739,6 +739,7 @@ enum {
, BV_PI
, BV_QE
, BV_RO
, BV_SCBK
, BV_SI
, BV_SMC
, BV_SYN

View File

@ -1912,6 +1912,14 @@ return {
pv_name='p_scroll',
defaults={if_true={vi=12}}
},
{
full_name='scrollback', abbreviation='scbk',
type='number', scope={'buffer'},
vi_def=true,
varname='p_scbk',
redraw={'current_buffer'},
defaults={if_true={vi=-1}}
},
{
full_name='scrollbind', abbreviation='scb',
type='bool', scope={'window'},

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 = {
@ -237,25 +229,22 @@ Terminal *terminal_open(TerminalOptions opts)
rv->invalid_end = opts.height;
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) {
@ -334,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;
}
@ -381,8 +370,7 @@ void terminal_enter(void)
State = TERM_FOCUS;
mapped_ctrl_c |= TERM_FOCUS; // Always map CTRL-C to avoid interrupt.
RedrawingDisabled = false;
// go to the bottom when the terminal is focused
adjust_topline(s->term, buf, false);
adjust_topline(s->term, buf, 0); // scroll to end
// erase the unfocused cursor
invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
showmode();
@ -667,10 +655,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;
@ -682,10 +675,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);
}
@ -695,6 +690,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++;
@ -710,6 +706,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;
@ -722,24 +723,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);
@ -885,7 +886,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;
@ -958,23 +959,23 @@ static void refresh_terminal(Terminal *term)
buf_T *buf = handle_get_buffer(term->buf_handle);
bool valid = true;
if (!buf || !(valid = buf_valid(buf))) {
// destroyed by `close_buffer`. Dont do anything else
// Destroyed by `close_buffer`. Do not do anything else.
if (!valid) {
term->buf_handle = 0;
}
return;
}
bool pending_resize = term->pending_resize;
long ml_before = buf->b_ml.ml_line_count;
WITH_BUFFER(buf, {
refresh_size(term, buf);
refresh_scrollback(term, buf);
refresh_screen(term, buf);
redraw_buf_later(buf, NOT_VALID);
});
adjust_topline(term, buf, pending_resize);
long ml_added = buf->b_ml.ml_line_count - ml_before;
adjust_topline(term, buf, ml_added);
}
// 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.
@ -1008,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;
@ -1037,9 +1068,11 @@ 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
// Refresh the screen (visible part of the buffer when the terminal is
// focused) of a invalidated terminal
static void refresh_screen(Terminal *term, buf_T *buf)
{
@ -1048,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);
@ -1094,14 +1126,6 @@ static void redraw(bool restore_cursor)
update_screen(0);
}
redraw_statuslines();
if (need_maketitle) {
maketitle();
}
showruler(false);
if (term && is_focused(term)) {
curwin->w_wrow = term->cursor.row;
curwin->w_wcol = term->cursor.col + win_col_off(curwin);
@ -1121,21 +1145,21 @@ static void redraw(bool restore_cursor)
ui_flush();
}
static void adjust_topline(Terminal *term, buf_T *buf, bool force)
static void adjust_topline(Terminal *term, buf_T *buf, long added)
{
int height, width;
vterm_get_size(term->vt, &height, &width);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == buf) {
// for every window that displays a terminal, ensure the cursor is in a
// valid line
wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, buf->b_ml.ml_line_count);
if (force || curbuf != buf || is_focused(term)) {
// if the terminal is not in the current window or if it's focused,
// adjust topline/cursor so the window will "follow" the terminal
// output
wp->w_cursor.lnum = buf->b_ml.ml_line_count;
linenr_T ml_end = buf->b_ml.ml_line_count;
bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
if (following || (wp == curwin && is_focused(term))) {
// "Follow" the terminal output
wp->w_cursor.lnum = ml_end;
set_topline(wp, MAX(wp->w_cursor.lnum - height + 1, 1));
} else {
// Ensure valid cursor for each window displaying this terminal.
wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end);
}
}
}
@ -1178,17 +1202,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

@ -305,16 +305,12 @@ bool undo_allowed(void)
return true;
}
/*
* Get the undolevle value for the current buffer.
*/
/// Get the 'undolevels' value for the current buffer.
static long get_undolevel(void)
{
if (curbuf->terminal) {
return -1;
}
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,41 @@ 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)
..'|call feedkeys("G", "n")')
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,26 +20,34 @@ 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)
it("in normal-mode :split does not move cursor", function()
execute([[terminal while true; do echo foo; sleep .1; done]])
helpers.feed([[<C-\><C-N>M]]) -- move cursor away from last line
wait()
eq(3, eval("line('$')")) -- window height
eq(2, eval("line('.')")) -- cursor is in the middle
execute('vsplit')
eq(2, eval("line('.')")) -- cursor stays where we put it
execute('split')
eq(2, eval("line('.')")) -- cursor stays where we put it
end)
end)
describe(':terminal (with fake shell)', function()

View File

@ -1,7 +1,7 @@
local helpers = require('test.functional.helpers')(nil)
local Screen = require('test.functional.ui.screen')
local nvim_dir = helpers.nvim_dir
local execute, nvim, wait = helpers.execute, helpers.nvim, helpers.wait
local execute, nvim = helpers.execute, helpers.nvim
local function feed_data(data)
nvim('set_var', 'term_data', data)
@ -34,13 +34,15 @@ local function disable_mouse() feed_termcode('[?1002l') end
local default_command = '["'..nvim_dir..'/tty-test'..'"]'
local function screen_setup(extra_height, command)
local function screen_setup(extra_rows, command, cols)
extra_rows = extra_rows and extra_rows or 0
command = command and command or default_command
cols = cols and cols or 50
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)
local screen = Screen.new(cols, 7 + extra_rows)
screen:set_default_attr_ids({
[1] = {reverse = true}, -- focused cursor
[2] = {background = 11}, -- unfocused cursor
@ -55,31 +57,42 @@ local function screen_setup(extra_height, command)
})
screen:attach({rgb=false})
-- 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..')')
nvim('input', '<CR>')
local vim_errmsg = nvim('eval', 'v:errmsg')
if vim_errmsg and "" ~= vim_errmsg then
error(vim_errmsg)
end
execute('setlocal scrollback=10')
execute('startinsert')
-- tty-test puts the terminal into raw mode and echoes input. Tests work by
-- feeding termcodes to control the display and asserting by screen:expect.
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)
--
local empty_line = ' '
-- Wait for "tty ready" to be printed before each test or the terminal may
-- still be in canonical mode (will echo characters for example).
local empty_line = (' '):rep(cols + 1)
local expected = {
'tty ready ',
'{1: } ',
'tty ready'..(' '):rep(cols - 8),
'{1: }' ..(' '):rep(cols),
empty_line,
empty_line,
empty_line,
empty_line,
}
for _ = 1, extra_height do
for _ = 1, extra_rows do
table.insert(expected, empty_line)
end
table.insert(expected, '{3:-- TERMINAL --} ')
table.insert(expected, '{3:-- TERMINAL --}' .. ((' '):rep(cols - 13)))
screen:expect(table.concat(expected, '\n'))
else
wait()
-- This eval also acts as a wait().
if 0 == nvim('eval', "exists('b:terminal_job_id')") then
error("terminal job failed to start")
end
end
return screen
end

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
@ -13,14 +17,14 @@ describe('terminal scrollback', function()
before_each(function()
clear()
screen = thelpers.screen_setup()
screen = thelpers.screen_setup(nil, nil, 30)
end)
after_each(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
@ -141,7 +145,7 @@ describe('terminal scrollback', function()
line2 |
line3 |
line4 |
rows: 5, cols: 50 |
rows: 5, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -157,8 +161,8 @@ describe('terminal scrollback', function()
it('will hide the top 3 lines', function()
screen:expect([[
rows: 5, cols: 50 |
rows: 3, cols: 50 |
rows: 5, cols: 30 |
rows: 3, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -166,8 +170,8 @@ describe('terminal scrollback', function()
feed('<c-\\><c-n>3k')
screen:expect([[
^line4 |
rows: 5, cols: 50 |
rows: 3, cols: 50 |
rows: 5, cols: 30 |
rows: 3, cols: 30 |
|
]])
end)
@ -184,7 +188,7 @@ describe('terminal scrollback', function()
local function will_delete_last_two_lines()
screen:expect([[
tty ready |
rows: 4, cols: 50 |
rows: 4, cols: 30 |
{1: } |
|
{3:-- TERMINAL --} |
@ -202,8 +206,8 @@ describe('terminal scrollback', function()
it('will delete the last line and hide the first', function()
screen:expect([[
rows: 4, cols: 50 |
rows: 3, cols: 50 |
rows: 4, cols: 30 |
rows: 3, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -211,14 +215,14 @@ describe('terminal scrollback', function()
feed('<c-\\><c-n>gg')
screen:expect([[
^tty ready |
rows: 4, cols: 50 |
rows: 3, cols: 50 |
rows: 4, cols: 30 |
rows: 3, cols: 30 |
|
]])
feed('a')
screen:expect([[
rows: 4, cols: 50 |
rows: 3, cols: 50 |
rows: 4, cols: 30 |
rows: 3, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -242,7 +246,7 @@ describe('terminal scrollback', function()
screen:try_resize(screen._width, screen._height - 3)
screen:expect([[
line4 |
rows: 3, cols: 50 |
rows: 3, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -254,8 +258,8 @@ describe('terminal scrollback', function()
screen:try_resize(screen._width, screen._height + 1)
screen:expect([[
line4 |
rows: 3, cols: 50 |
rows: 4, cols: 50 |
rows: 3, cols: 30 |
rows: 4, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -275,9 +279,9 @@ describe('terminal scrollback', function()
line2 |
line3 |
line4 |
rows: 3, cols: 50 |
rows: 4, cols: 50 |
rows: 7, cols: 50 |
rows: 3, cols: 30 |
rows: 4, cols: 30 |
rows: 7, cols: 30 |
{1: } |
{3:-- TERMINAL --} |
]])
@ -289,8 +293,8 @@ describe('terminal scrollback', function()
line2 |
line3 |
line4 |
rows: 3, cols: 50 |
rows: 4, cols: 50 |
rows: 3, cols: 30 |
rows: 4, cols: 30 |
|
]])
end
@ -311,10 +315,10 @@ describe('terminal scrollback', function()
line2 |
line3 |
line4 |
rows: 3, cols: 50 |
rows: 4, cols: 50 |
rows: 7, cols: 50 |
rows: 11, cols: 50 |
rows: 3, cols: 30 |
rows: 4, cols: 30 |
rows: 7, cols: 30 |
rows: 11, cols: 30 |
{1: } |
|
{3:-- TERMINAL --} |
@ -332,7 +336,7 @@ end)
describe('terminal prints more lines than the screen height and exits', function()
it('will push extra lines to scrollback', function()
clear()
local screen = Screen.new(50, 7)
local screen = Screen.new(30, 7)
screen:attach({rgb=false})
execute('call termopen(["'..nvim_dir..'/tty-test", "10"]) | startinsert')
wait()
@ -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)

View File

@ -28,14 +28,14 @@ describe('terminal', function()
feed('<c-\\><c-n>')
execute('2split')
screen:expect([[
tty ready |
^rows: 2, cols: 50 |
rows: 2, cols: 50 |
{2:^ } |
========== |
tty ready |
rows: 2, cols: 50 |
{2: } |
{4:~ }|
{4:~ }|
{4:~ }|
========== |
:2split |
]])
@ -54,14 +54,14 @@ describe('terminal', function()
]])
execute('wincmd p')
screen:expect([[
rows: 5, cols: 50 |
^rows: 2, cols: 50 |
rows: 2, cols: 50 |
{2:^ } |
========== |
rows: 5, cols: 50 |
rows: 2, cols: 50 |
{2: } |
{4:~ }|
{4:~ }|
{4:~ }|
========== |
:wincmd p |
]])

View File

@ -207,7 +207,9 @@ local function check_cores(app)
out:write(('\nTests covered by this check: %u\n'):format(tests_skipped + 1))
end
tests_skipped = 0
assert(0 == found_cores)
if found_cores > 0 then
error("crash detected (see above)")
end
end
return {