From 2aa2513b8e023a0d7bd2071299f0ea59a4d4ce25 Mon Sep 17 00:00:00 2001 From: Thiago de Arruda Date: Wed, 25 Mar 2015 09:14:47 -0300 Subject: [PATCH] test: Add terminal tests - Modify tty-test to allow easier control over the terminal - Add a new directory with various terminal tests/specifications - Remove a pending job/pty test. - Flush stdout in Screen:snapshot_util() (avoid waiting for the test to finish) - Replace libuv sigwinch watcher by a sigaction handler. libuv randomly fails to deliver signals on OSX. Might be related to the problem fixed by @bbcddc55ee1e5605657592644be0102ed3a5f104 (under the hoods, libuv uses a pipe to deliver signals to the main thread, which might be blocking in some situations) --- test/functional/job/job_spec.lua | 14 +- test/functional/job/tty-test.c | 88 +++-- test/functional/terminal/altscreen_spec.lua | 158 ++++++++ test/functional/terminal/buffer_spec.lua | 159 ++++++++ test/functional/terminal/cursor_spec.lua | 174 +++++++++ test/functional/terminal/helpers.lua | 95 +++++ test/functional/terminal/highlight_spec.lua | 163 ++++++++ test/functional/terminal/mouse_spec.lua | 188 ++++++++++ test/functional/terminal/scrollback_spec.lua | 348 ++++++++++++++++++ test/functional/terminal/window_spec.lua | 64 ++++ .../terminal/window_split_tab_spec.lua | 138 +++++++ test/functional/ui/screen.lua | 8 +- 12 files changed, 1553 insertions(+), 44 deletions(-) create mode 100644 test/functional/terminal/altscreen_spec.lua create mode 100644 test/functional/terminal/buffer_spec.lua create mode 100644 test/functional/terminal/cursor_spec.lua create mode 100644 test/functional/terminal/helpers.lua create mode 100644 test/functional/terminal/highlight_spec.lua create mode 100644 test/functional/terminal/mouse_spec.lua create mode 100644 test/functional/terminal/scrollback_spec.lua create mode 100644 test/functional/terminal/window_spec.lua create mode 100644 test/functional/terminal/window_split_tab_spec.lua diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua index c74ae047c5..8981c49744 100644 --- a/test/functional/job/job_spec.lua +++ b/test/functional/job/job_spec.lua @@ -174,24 +174,14 @@ describe('jobs', function() it('echoing input', function() send('test') - -- the tty driver will echo input by default eq('test', next_chunk()) end) it('resizing window', function() nvim('command', 'call jobresize(j, 40, 10)') - eq('screen resized. rows: 10, columns: 40', next_chunk()) + eq('rows: 10, cols: 40', next_chunk()) nvim('command', 'call jobresize(j, 10, 40)') - eq('screen resized. rows: 40, columns: 10', next_chunk()) - end) - - -- FIXME This test is flawed because there is no telling when the OS will send chunks of data. - pending('preprocessing ctrl+c with terminal driver', function() - send('\\') - eq('^Cinterrupt received, press again to exit', next_chunk()) - send('\\') - eq('^Ctty done', next_chunk()) - eq({'notification', 'exit', {0}}, next_message()) + eq('rows: 40, cols: 10', next_chunk()) end) end) end) diff --git a/test/functional/job/tty-test.c b/test/functional/job/tty-test.c index b395c9c9a7..40ba131003 100644 --- a/test/functional/job/tty-test.c +++ b/test/functional/job/tty-test.c @@ -3,6 +3,8 @@ #include #include +uv_tty_t tty; + #ifdef _WIN32 #include bool owns_tty(void) @@ -13,10 +15,10 @@ bool owns_tty(void) return GetCurrentProcessId() == dwProcessId; } #else +#include bool owns_tty(void) { - // TODO: Check if the process is the session leader - return true; + return getsid(0) == getpid(); } #endif @@ -29,26 +31,20 @@ static void walk_cb(uv_handle_t *handle, void *arg) { } } -static void sigwinch_cb(uv_signal_t *handle, int signum) +static void sigwinch_handler(int signum) { int width, height; - uv_tty_t *tty = handle->data; - uv_tty_get_winsize(tty, &width, &height); - fprintf(stderr, "screen resized. rows: %d, columns: %d\n", height, width); + uv_tty_get_winsize(&tty, &width, &height); + fprintf(stderr, "rows: %d, cols: %d\n", height, width); } -static void sigint_cb(uv_signal_t *handle, int signum) -{ - bool *interrupted = handle->data; - - if (*interrupted) { - uv_walk(uv_default_loop(), walk_cb, NULL); - return; - } - - *interrupted = true; - fprintf(stderr, "interrupt received, press again to exit\n"); -} +// static void sigwinch_cb(uv_signal_t *handle, int signum) +// { +// int width, height; +// uv_tty_t *tty = handle->data; +// uv_tty_get_winsize(tty, &width, &height); +// fprintf(stderr, "rows: %d, cols: %d\n", height, width); +// } static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { @@ -63,13 +59,20 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) return; } - fprintf(stderr, "received data: "); + int *interrupted = stream->data; + + for (int i = 0; i < cnt; i++) { + if (buf->base[i] == 3) { + (*interrupted)++; + } + } + uv_loop_t write_loop; uv_loop_init(&write_loop); uv_tty_t out; uv_tty_init(&write_loop, &out, 1, 0); uv_write_t req; - uv_buf_t b = {.base = buf->base, .len = buf->len}; + uv_buf_t b = {.base = buf->base, .len = (size_t)cnt}; uv_write(&req, (uv_stream_t *)&out, &b, 1, NULL); uv_run(&write_loop, UV_RUN_DEFAULT); uv_close((uv_handle_t *)&out, NULL); @@ -78,6 +81,12 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf) abort(); } free(buf->base); + + if (*interrupted >= 2) { + uv_walk(uv_default_loop(), walk_cb, NULL); + } else if (*interrupted == 1) { + fprintf(stderr, "interrupt received, press again to exit\n"); + } } static void prepare_cb(uv_prepare_t *handle) @@ -88,6 +97,11 @@ static void prepare_cb(uv_prepare_t *handle) int main(int argc, char **argv) { + if (!owns_tty()) { + fprintf(stderr, "process does not own the terminal\n"); + exit(2); + } + if (!is_terminal(stdin)) { fprintf(stderr, "stdin is not a terminal\n"); exit(2); @@ -103,20 +117,34 @@ int main(int argc, char **argv) exit(2); } - bool interrupted = false; + if (argc > 1) { + int count = atoi(argv[1]); + for (int i = 0; i < count; ++i) { + printf("line%d\n", i); + } + fflush(stdout); + return 0; + } + + int interrupted = 0; uv_prepare_t prepare; uv_prepare_init(uv_default_loop(), &prepare); uv_prepare_start(&prepare, prepare_cb); - uv_tty_t tty; + // uv_tty_t tty; uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1); + uv_tty_set_mode(&tty, UV_TTY_MODE_RAW); + tty.data = &interrupted; uv_read_start((uv_stream_t *)&tty, alloc_cb, read_cb); - uv_signal_t sigwinch_watcher, sigint_watcher; - uv_signal_init(uv_default_loop(), &sigwinch_watcher); - sigwinch_watcher.data = &tty; - uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); - uv_signal_init(uv_default_loop(), &sigint_watcher); - sigint_watcher.data = &interrupted; - uv_signal_start(&sigint_watcher, sigint_cb, SIGINT); + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sigwinch_handler; + sigaction(SIGWINCH, &sa, NULL); + // uv_signal_t sigwinch_watcher; + // uv_signal_init(uv_default_loop(), &sigwinch_watcher); + // sigwinch_watcher.data = &tty; + // uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH); uv_run(uv_default_loop(), UV_RUN_DEFAULT); - fprintf(stderr, "tty done\n"); + + return 0; } diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua new file mode 100644 index 0000000000..9ec0fc7c5a --- /dev/null +++ b/test/functional/terminal/altscreen_spec.lua @@ -0,0 +1,158 @@ +local helpers = require('test.functional.helpers') +local thelpers = require('test.functional.terminal.helpers') +local Screen = require('test.functional.ui.screen') +local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf +local feed = helpers.feed +local feed_data = thelpers.feed_data +local enter_altscreen = thelpers.enter_altscreen +local exit_altscreen = thelpers.exit_altscreen + +describe('terminal altscreen', function() + local screen + + before_each(function() + clear() + screen = thelpers.screen_setup() + feed_data({'line1', 'line2', 'line3', 'line4', 'line5', 'line6', + 'line7', 'line8', ''}) + screen:expect([[ + line4 | + line5 | + line6 | + line7 | + line8 | + {1: } | + -- TERMINAL -- | + ]]) + enter_altscreen() + screen:expect([[ + | + | + | + | + | + {1: } | + -- TERMINAL -- | + ]]) + eq(10, curbuf('line_count')) + end) + + it('wont clear lines already in the scrollback', function() + feed('gg') + screen:expect([[ + ^tty ready | + line1 | + line2 | + line3 | + | + | + | + ]]) + end) + + describe('on exit', function() + before_each(exit_altscreen) + + it('restores buffer state', function() + screen:expect([[ + line4 | + line5 | + line6 | + line7 | + line8 | + {1: } | + -- TERMINAL -- | + ]]) + feed('gg') + screen:expect([[ + ^tty ready | + line1 | + line2 | + line3 | + line4 | + line5 | + | + ]]) + end) + end) + + describe('with lines printed after the screen height limit', function() + before_each(function() + feed_data({'line9', 'line10', 'line11', 'line12', 'line13', + 'line14', 'line15', 'line16', ''}) + screen:expect([[ + line12 | + line13 | + line14 | + line15 | + line16 | + {1: } | + -- TERMINAL -- | + ]]) + end) + + it('wont modify line count', function() + eq(10, curbuf('line_count')) + end) + + it('wont modify lines in the scrollback', function() + feed('gg') + screen:expect([[ + ^tty ready | + line1 | + line2 | + line3 | + line12 | + line13 | + | + ]]) + end) + end) + + describe('after height is decreased by 2', function() + local function wait_removal() + screen:try_resize(screen._width, screen._height - 2) + screen:expect([[ + | + | + rows: 4, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + end + + it('removes 2 lines from the bottom of the visible buffer', function() + wait_removal() + feed('4k') + screen:expect([[ + ^line3 | + | + | + rows: 4, cols: 50 | + | + ]]) + eq(8, curbuf('line_count')) + end) + + describe('and after exit', function() + before_each(function() + wait_removal() + exit_altscreen() + end) + + it('restore buffer state', function() + -- FIXME(tarruda): Note that the last line was lost after restoring the + -- screen. This is a libvterm bug: When the main screen is restored it + -- seems to "cut" lines that would have been left below the new visible + -- screen. + screen:expect([[ + line4 | + line5 | + line6 | + line7 | + -- TERMINAL -- | + ]]) + end) + end) + end) +end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua new file mode 100644 index 0000000000..857679c4b3 --- /dev/null +++ b/test/functional/terminal/buffer_spec.lua @@ -0,0 +1,159 @@ +local helpers = require('test.functional.helpers') +local Screen = require('test.functional.ui.screen') +local thelpers = require('test.functional.terminal.helpers') +local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim +local wait, execute, eq = helpers.wait, helpers.execute, helpers.eq + + +describe('terminal buffer', function() + local screen + + before_each(function() + clear() + execute('set modifiable swapfile undolevels=20') + wait() + screen = thelpers.screen_setup() + end) + + describe('when a new file is edited', function() + before_each(function() + feed(':set bufhidden=wipe:enew') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + :enew | + ]]) + end) + + it('will hide the buffer, ignoring the bufhidden option', function() + feed(':bnext:l') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) + end) + + describe('swap and undo', function() + before_each(function() + feed('') + screen:expect([[ + tty ready | + {2: } | + | + | + | + ^ | + | + ]]) + end) + + it('does not create swap files', function() + local swapfile = nvim('command_output', 'swapname'):gsub('\n', '') + eq(nil, io.open(swapfile)) + end) + + it('does not create undofiles files', function() + local undofile = nvim('eval', 'undofile(bufname("%"))') + eq(nil, io.open(undofile)) + end) + end) + + it('cannot be modified directly', function() + feed('dd') + screen:expect([[ + tty ready | + {2: } | + | + | + | + ^ | + E21: Cannot make changes, 'modifiable' is off | + ]]) + end) + + it('sends data to the terminal when the "put" operator is used', function() + feed('gg"ayj') + execute('let @a = "appended " . @a') + feed('"ap"ap') + screen:expect([[ + ^tty ready | + appended tty ready | + appended tty ready | + {2: } | + | + | + :let @a = "appended " . @a | + ]]) + -- operator count is also taken into consideration + feed('3"ap') + screen:expect([[ + ^tty ready | + appended tty ready | + appended tty ready | + appended tty ready | + appended tty ready | + appended tty ready | + :let @a = "appended " . @a | + ]]) + end) + + it('sends data to the terminal when the ":put" command is used', function() + feed('gg"ayj') + execute('let @a = "appended " . @a') + execute('put a') + screen:expect([[ + ^tty ready | + appended tty ready | + {2: } | + | + | + | + :put a | + ]]) + -- line argument is only used to move the cursor + execute('6put a') + screen:expect([[ + tty ready | + appended tty ready | + appended tty ready | + {2: } | + | + ^ | + :6put a | + ]]) + end) + + it('can be deleted', function() + feed(':bd!') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + :bd! | + ]]) + execute('bnext') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + :bnext | + ]]) + end) +end) + diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua new file mode 100644 index 0000000000..3e3f9cbf4f --- /dev/null +++ b/test/functional/terminal/cursor_spec.lua @@ -0,0 +1,174 @@ +local helpers = require('test.functional.helpers') +local Screen = require('test.functional.ui.screen') +local thelpers = require('test.functional.terminal.helpers') +local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim +local nvim_dir, execute, eq = helpers.nvim_dir, helpers.execute, helpers.eq +local hide_cursor = thelpers.hide_cursor +local show_cursor = thelpers.show_cursor + + +describe('terminal cursor', function() + local screen + + before_each(function() + clear() + screen = thelpers.screen_setup() + end) + + + it('moves the screen cursor when focused', function() + thelpers.feed_data('testing cursor') + screen:expect([[ + tty ready | + testing cursor{1: } | + | + | + | + | + -- TERMINAL -- | + ]]) + eq(2, screen._cursor.row) + eq(15, screen._cursor.col) + end) + + it('is highlighted when not focused', function() + feed('') + screen:expect([[ + tty ready | + {2: } | + | + | + | + ^ | + | + ]]) + end) + + describe('with number column', function() + before_each(function() + feed(':set number') + end) + + it('is positioned correctly when unfocused', function() + screen:expect([[ + 1 tty ready | + 2 {2: } | + 3 | + 4 | + 5 | + 6 ^ | + :set number | + ]]) + end) + + it('is positioned correctly when focused', function() + feed('i') + screen:expect([[ + 1 tty ready | + 2 {1: } | + 3 | + 4 | + 5 | + 6 | + -- TERMINAL -- | + ]]) + end) + end) + + describe('when invisible', function() + it('is not highlighted and is detached from screen cursor', function() + hide_cursor() + screen:expect([[ + tty ready | + | + | + | + | + | + -- TERMINAL -- | + ]]) + show_cursor() + screen:expect([[ + tty ready | + {1: } | + | + | + | + | + -- TERMINAL -- | + ]]) + -- same for when the terminal is unfocused + feed('') + hide_cursor() + screen:expect([[ + tty ready | + | + | + | + | + ^ | + | + ]]) + show_cursor() + screen:expect([[ + tty ready | + {2: } | + | + | + | + ^ | + | + ]]) + end) + end) +end) + + +describe('cursor with customized highlighting', function() + local screen + + before_each(function() + clear() + nvim('set_var', 'terminal_focused_cursor_highlight', 'CursorFocused') + nvim('set_var', 'terminal_unfocused_cursor_highlight', 'CursorUnfocused') + nvim('command', 'highlight CursorFocused ctermfg=45 ctermbg=46') + nvim('command', 'highlight CursorUnfocused ctermfg=55 ctermbg=56') + screen = Screen.new(50, 7) + screen:set_default_attr_ids({ + [1] = {foreground = 45, background = 46}, + [2] = {foreground = 55, background = 56} + }) + screen:set_default_attr_ignore({ + [1] = {bold = true}, + [2] = {foreground = 12}, + [3] = {bold = true, reverse = true}, + [5] = {background = 11}, + [6] = {foreground = 130}, + }) + screen:attach(false) + execute('term "' ..nvim_dir.. '/tty-test"') + end) + + it('overrides the default highlighting', function() + screen:expect([[ + tty ready | + {1: } | + | + | + | + | + -- TERMINAL -- | + ]]) + feed('') + screen:expect([[ + tty ready | + {2: } | + | + | + | + ^ | + | + ]]) + end) +end) + diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua new file mode 100644 index 0000000000..631dc579d3 --- /dev/null +++ b/test/functional/terminal/helpers.lua @@ -0,0 +1,95 @@ +local helpers = require('test.functional.helpers') +local Screen = require('test.functional.ui.screen') +local nvim_dir = helpers.nvim_dir +local execute, nvim = helpers.execute, helpers.nvim + +local function feed_data(data) + nvim('set_var', 'term_data', data) + nvim('command', 'call jobsend(b:terminal_job_id, term_data)') +end + +local function feed_termcode(data) + -- feed with the job API + nvim('command', 'call jobsend(b:terminal_job_id, "\\x1b'..data..'")') +end +-- some helpers for controlling the terminal. the codes were taken from +-- infocmp xterm-256color which is less what libvterm understands +-- civis/cnorm +local function hide_cursor() feed_termcode('[?25l') end +local function show_cursor() feed_termcode('[?25h') end +-- smcup/rmcup +local function enter_altscreen() feed_termcode('[?1049h') end +local function exit_altscreen() feed_termcode('[?1049l') end +-- character attributes +local function set_fg(num) feed_termcode('[38;5;'..num..'m') end +local function set_bg(num) feed_termcode('[48;5;'..num..'m') end +local function set_bold() feed_termcode('[1m') end +local function set_italic() feed_termcode('[3m') end +local function set_underline() feed_termcode('[4m') end +local function clear_attrs() feed_termcode('[0;10m') end +-- mouse +local function enable_mouse() feed_termcode('[?1002h') end +local function disable_mouse() feed_termcode('[?1002l') end + + + +local function screen_setup(extra_height) + nvim('set_var', 'terminal_scrollback_buffer_size', 10) + if not extra_height then extra_height = 0 end + local screen = Screen.new(50, 7 + extra_height) + screen:set_default_attr_ids({ + [1] = {reverse = true}, -- focused cursor + [2] = {background = 11}, -- unfocused cursor + }) + screen:set_default_attr_ignore({ + [1] = {bold = true}, + [2] = {foreground = 12}, + [3] = {bold = true, reverse = true}, + [5] = {background = 11}, + [6] = {foreground = 130}, + [7] = {foreground = 15, background = 1}, -- error message + }) + + screen:attach(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('term ' ..nvim_dir.. '/tty-test') + -- 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 = ' ' + local expected = { + 'tty ready ', + '{1: } ', + empty_line, + empty_line, + empty_line, + empty_line, + } + for i = 1, extra_height do + table.insert(expected, empty_line) + end + + table.insert(expected, '-- TERMINAL -- ') + screen:expect(table.concat(expected, '\n')) + return screen +end + +return { + feed_data = feed_data, + feed_termcode = feed_termcode, + hide_cursor = hide_cursor, + show_cursor = show_cursor, + enter_altscreen = enter_altscreen, + exit_altscreen = exit_altscreen, + set_fg = set_fg, + set_bg = set_bg, + set_bold = set_bold, + set_italic = set_italic, + set_underline = set_underline, + clear_attrs = clear_attrs, + enable_mouse = enable_mouse, + disable_mouse = disable_mouse, + screen_setup = screen_setup +} diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua new file mode 100644 index 0000000000..59b0d2c19d --- /dev/null +++ b/test/functional/terminal/highlight_spec.lua @@ -0,0 +1,163 @@ +local helpers = require('test.functional.helpers') +local Screen = require('test.functional.ui.screen') +local thelpers = require('test.functional.terminal.helpers') +local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim +local nvim_dir, execute = helpers.nvim_dir, helpers.execute + + +describe('terminal window highlighting', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 7) + screen:set_default_attr_ids({ + [1] = {foreground = 45}, + [2] = {background = 46}, + [3] = {foreground = 45, background = 46}, + [4] = {bold = true, italic = true, underline = true} + }) + screen:set_default_attr_ignore({ + [1] = {bold = true}, + [2] = {foreground = 12}, + [3] = {bold = true, reverse = true}, + [5] = {background = 11}, + [6] = {foreground = 130}, + [7] = {reverse = true}, + [8] = {background = 11} + }) + screen:attach(false) + execute('term "' ..nvim_dir.. '/tty-test"') + screen:expect([[ + tty ready | + | + | + | + | + | + -- TERMINAL -- | + ]]) + end) + + function descr(title, attr_num, set_attrs_fn) + local function sub(s) + return s:gsub('NUM', attr_num) + end + + describe(title, function() + before_each(function() + set_attrs_fn() + thelpers.feed_data('text') + thelpers.clear_attrs() + thelpers.feed_data('text') + end) + + local function pass_attrs() + local s = sub([[ + tty ready | + {NUM:text}text | + | + | + | + | + -- TERMINAL -- | + ]]) + screen:expect(s) + end + + it('will pass the corresponding attributes', pass_attrs) + + it('will pass the corresponding attributes on scrollback', function() + pass_attrs() + local lines = {} + for i = 1, 8 do + table.insert(lines, 'line'..tostring(i)) + end + table.insert(lines, '') + thelpers.feed_data(lines) + screen:expect([[ + line4 | + line5 | + line6 | + line7 | + line8 | + | + -- TERMINAL -- | + ]]) + feed('gg') + local s = sub([[ + ^tty ready | + {NUM:text}textline1 | + line2 | + line3 | + line4 | + line5 | + | + ]]) + screen:expect(s) + end) + end) + end + + descr('foreground', 1, function() thelpers.set_fg(45) end) + descr('background', 2, function() thelpers.set_bg(46) end) + descr('foreground and background', 3, function() + thelpers.set_fg(45) + thelpers.set_bg(46) + end) + descr('bold, italics and underline', 4, function() + thelpers.set_bold() + thelpers.set_italic() + thelpers.set_underline() + end) +end) + + +describe('terminal window highlighting with custom palette', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 7) + screen:set_default_attr_ids({ + [1] = {foreground = 1193046} + }) + screen:set_default_attr_ignore({ + [1] = {bold = true}, + [2] = {foreground = 12}, + [3] = {bold = true, reverse = true}, + [5] = {background = 11}, + [6] = {foreground = 130}, + [7] = {reverse = true}, + [8] = {background = 11} + }) + screen:attach(true) + nvim('set_var', 'terminal_color_3', '#123456') + execute('term "' ..nvim_dir.. '/tty-test"') + screen:expect([[ + tty ready | + | + | + | + | + | + -- TERMINAL -- | + ]]) + end) + + it('will use the custom color', function() + thelpers.set_fg(3) + thelpers.feed_data('text') + thelpers.clear_attrs() + thelpers.feed_data('text') + screen:expect([[ + tty ready | + {1:text}text | + | + | + | + | + -- TERMINAL -- | + ]]) + end) +end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua new file mode 100644 index 0000000000..b8f6214f8f --- /dev/null +++ b/test/functional/terminal/mouse_spec.lua @@ -0,0 +1,188 @@ +local Screen = require('test.functional.ui.screen') +local helpers = require('test.functional.helpers') +local thelpers = require('test.functional.terminal.helpers') +local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf +local feed, execute, nvim = helpers.feed, helpers.execute, helpers.nvim +local feed_data = thelpers.feed_data + +describe('terminal mouse', function() + local screen + + before_each(function() + clear() + nvim('set_option', 'statusline', '==========') + nvim('command', 'highlight StatusLine cterm=NONE') + nvim('command', 'highlight StatusLineNC cterm=NONE') + nvim('command', 'highlight VertSplit cterm=NONE') + screen = thelpers.screen_setup() + local lines = {} + for i = 1, 30 do + table.insert(lines, 'line'..tostring(i)) + end + table.insert(lines, '') + feed_data(lines) + screen:expect([[ + line26 | + line27 | + line28 | + line29 | + line30 | + {1: } | + -- TERMINAL -- | + ]]) + end) + + after_each(function() + screen:detach() + end) + + describe('when the terminal has focus', function() + it('will exit focus when scrolled', function() + feed('<0,0>') + screen:expect([[ + line23 | + line24 | + line25 | + line26 | + line27 | + ^line28 | + | + ]]) + end) + + describe('with mouse events enabled by the program', function() + before_each(function() + thelpers.enable_mouse() + thelpers.feed_data('mouse enabled\n') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + {1: } | + -- TERMINAL -- | + ]]) + end) + + it('will forward mouse clicks to the program', function() + feed('<1,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + "#{1: } | + -- TERMINAL -- | + ]]) + end) + + it('will forward mouse scroll to the program', function() + feed('<0,0>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + `!!{1: } | + -- TERMINAL -- | + ]]) + end) + end) + + describe('with a split window and other buffer', function() + before_each(function() + feed(':vsp') + screen:expect([[ + line21 |line21 | + line22 |line22 | + line23 |line23 | + line24 |line24 | + ^rows: 5, cols: 24 |rows: 5, cols: 24 | + ========== ========== | + | + ]]) + feed(':enew | set number') + screen:expect([[ + 1 ^ |line21 | + ~ |line22 | + ~ |line23 | + ~ |line24 | + ~ |rows: 5, cols: 24 | + ========== ========== | + :enew | set number | + ]]) + feed('30iline\n') + screen:expect([[ + 27 line |line21 | + 28 line |line22 | + 29 line |line23 | + 30 line |line24 | + 31 ^ |rows: 5, cols: 24 | + ========== ========== | + | + ]]) + feed('li') + screen:expect([[ + 27 line |line22 | + 28 line |line23 | + 29 line |line24 | + 30 line |rows: 5, cols: 24 | + 31 |{1: } | + ========== ========== | + -- TERMINAL -- | + ]]) + -- enabling mouse won't affect interaction with other windows + thelpers.enable_mouse() + thelpers.feed_data('mouse enabled\n') + screen:expect([[ + 27 line |line23 | + 28 line |line24 | + 29 line |rows: 5, cols: 24 | + 30 line |mouse enabled | + 31 |{1: } | + ========== ========== | + -- TERMINAL -- | + ]]) + end) + + it('wont lose focus if another window is scrolled', function() + feed('<0,0><0,0>') + screen:expect([[ + 21 line |line23 | + 22 line |line24 | + 23 line |rows: 5, cols: 24 | + 24 line |mouse enabled | + 25 line |{1: } | + ========== ========== | + -- TERMINAL -- | + ]]) + feed('<0,0>') + screen:expect([[ + 26 line |line23 | + 27 line |line24 | + 28 line |rows: 5, cols: 24 | + 29 line |mouse enabled | + 30 line |{1: } | + ========== ========== | + -- TERMINAL -- | + ]]) + end) + + it('will lose focus if another window is clicked', function() + feed('<5,1>') + screen:expect([[ + 27 line |line23 | + 28 l^ine |line24 | + 29 line |rows: 5, cols: 24 | + 30 line |mouse enabled | + 31 |{2: } | + ========== ========== | + | + ]]) + end) + end) + end) +end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua new file mode 100644 index 0000000000..d9b7534fac --- /dev/null +++ b/test/functional/terminal/scrollback_spec.lua @@ -0,0 +1,348 @@ +local Screen = require('test.functional.ui.screen') +local helpers = require('test.functional.helpers') +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 wait = helpers.wait +local feed_data = thelpers.feed_data + +describe('terminal scrollback', function() + local screen + + before_each(function() + clear() + screen = thelpers.screen_setup() + end) + + after_each(function() + screen:detach() + end) + + describe('when the limit is crossed', function() + before_each(function() + local lines = {} + for i = 1, 30 do + table.insert(lines, 'line'..tostring(i)) + end + table.insert(lines, '') + feed_data(lines) + screen:expect([[ + line26 | + line27 | + line28 | + line29 | + line30 | + {1: } | + -- TERMINAL -- | + ]]) + end) + + it('will delete extra lines at the top', function() + feed('gg') + screen:expect([[ + ^line16 | + line17 | + line18 | + line19 | + line20 | + line21 | + | + ]]) + end) + end) + + describe('with the cursor at the last row', function() + before_each(function() + feed_data({'line1', 'line2', 'line3', 'line4', ''}) + screen:expect([[ + tty ready | + line1 | + line2 | + line3 | + line4 | + {1: } | + -- TERMINAL -- | + ]]) + end) + + describe('and 1 line is printed', function() + before_each(function() feed_data({'line5', ''}) end) + + it('will hide the top line', function() + screen:expect([[ + line1 | + line2 | + line3 | + line4 | + line5 | + {1: } | + -- TERMINAL -- | + ]]) + eq(7, curbuf('line_count')) + end) + + describe('and then 3 more lines are printed', function() + before_each(function() feed_data({'line6', 'line7', 'line8'}) end) + + it('will hide the top 4 lines', function() + screen:expect([[ + line3 | + line4 | + line5 | + line6 | + line7 | + line8{1: } | + -- TERMINAL -- | + ]]) + + feed('6k') + screen:expect([[ + ^line2 | + line3 | + line4 | + line5 | + line6 | + line7 | + | + ]]) + + feed('gg') + screen:expect([[ + ^tty ready | + line1 | + line2 | + line3 | + line4 | + line5 | + | + ]]) + + feed('G') + screen:expect([[ + line3 | + line4 | + line5 | + line6 | + line7 | + ^line8{2: } | + | + ]]) + end) + end) + end) + + + describe('and the height is decreased by 1', function() + local function will_hide_top_line() + screen:try_resize(screen._width, screen._height - 1) + screen:expect([[ + line2 | + line3 | + line4 | + rows: 5, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + end + + it('will hide top line', will_hide_top_line) + + describe('and then decreased by 2', function() + before_each(function() + will_hide_top_line() + screen:try_resize(screen._width, screen._height - 2) + end) + + it('will hide the top 3 lines', function() + screen:expect([[ + rows: 5, cols: 50 | + rows: 3, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + eq(8, curbuf('line_count')) + feed('3k') + screen:expect([[ + ^line4 | + rows: 5, cols: 50 | + rows: 3, cols: 50 | + | + ]]) + end) + end) + end) + end) + + describe('with empty lines after the cursor', function() + describe('and the height is decreased by 2', function() + before_each(function() + screen:try_resize(screen._width, screen._height - 2) + end) + + local function will_delete_last_two_lines() + screen:expect([[ + tty ready | + rows: 4, cols: 50 | + {1: } | + | + -- TERMINAL -- | + ]]) + eq(4, curbuf('line_count')) + end + + it('will delete the last two empty lines', will_delete_last_two_lines) + + describe('and then decreased by 1', function() + before_each(function() + will_delete_last_two_lines() + screen:try_resize(screen._width, screen._height - 1) + end) + + it('will delete the last line and hide the first', function() + screen:expect([[ + rows: 4, cols: 50 | + rows: 3, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + eq(4, curbuf('line_count')) + feed('gg') + screen:expect([[ + ^tty ready | + rows: 4, cols: 50 | + rows: 3, cols: 50 | + | + ]]) + feed('a') + screen:expect([[ + rows: 4, cols: 50 | + rows: 3, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + end) + end) + end) + end) + + describe('with 4 lines hidden in the scrollback', function() + before_each(function() + feed_data({'line1', 'line2', 'line3', 'line4', ''}) + screen:expect([[ + tty ready | + line1 | + line2 | + line3 | + line4 | + {1: } | + -- TERMINAL -- | + ]]) + screen:try_resize(screen._width, screen._height - 3) + screen:expect([[ + line4 | + rows: 3, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + eq(7, curbuf('line_count')) + end) + + describe('and the height is increased by 1', function() + local function pop_then_push() + screen:try_resize(screen._width, screen._height + 1) + screen:expect([[ + line4 | + rows: 3, cols: 50 | + rows: 4, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + end + + it('will pop 1 line and then push it back', pop_then_push) + + describe('and then by 3', function() + before_each(function() + pop_then_push() + eq(8, curbuf('line_count')) + screen:try_resize(screen._width, screen._height + 3) + end) + + local function pop3_then_push1() + screen:expect([[ + line2 | + line3 | + line4 | + rows: 3, cols: 50 | + rows: 4, cols: 50 | + rows: 7, cols: 50 | + {1: } | + -- TERMINAL -- | + ]]) + eq(9, curbuf('line_count')) + feed('gg') + screen:expect([[ + ^tty ready | + line1 | + line2 | + line3 | + line4 | + rows: 3, cols: 50 | + rows: 4, cols: 50 | + | + ]]) + end + + it('will pop 3 lines and then push one back', pop3_then_push1) + + describe('and then by 4', function() + before_each(function() + pop3_then_push1() + feed('Gi') + screen:try_resize(screen._width, screen._height + 4) + end) + + it('will show all lines and leave a blank one at the end', function() + screen:expect([[ + tty ready | + line1 | + line2 | + line3 | + line4 | + rows: 3, cols: 50 | + rows: 4, cols: 50 | + rows: 7, cols: 50 | + rows: 11, cols: 50 | + {1: } | + | + -- TERMINAL -- | + ]]) + -- since there's an empty line after the cursor, the buffer line + -- count equals the terminal screen height + eq(11, curbuf('line_count')) + end) + end) + end) + end) + end) +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) + screen:attach(false) + execute('term ' ..nvim_dir.. '/tty-test 10') + wait() + screen:expect([[ + line6 | + line7 | + line8 | + line9 | + | + [Program exited, press any key to close] | + -- TERMINAL -- | + ]]) + end) +end) + diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua new file mode 100644 index 0000000000..234950638e --- /dev/null +++ b/test/functional/terminal/window_spec.lua @@ -0,0 +1,64 @@ +local helpers = require('test.functional.helpers') +local thelpers = require('test.functional.terminal.helpers') +local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim +local wait, eq = helpers.wait, helpers.eq + + +describe('terminal window', function() + local screen + + before_each(function() + clear() + screen = thelpers.screen_setup() + end) + + describe('with colorcolumn set', function() + before_each(function() + feed(':set colorcolumn=20i') + wait() + end) + + it('wont show the color column', function() + screen:expect([[ + tty ready | + {1: } | + | + | + | + | + -- TERMINAL -- | + ]]) + end) + end) + + describe('with fold set', function() + before_each(function() + feed(':set foldenable foldmethod=manuali') + thelpers.feed_data({'line1', 'line2', 'line3', 'line4', ''}) + screen:expect([[ + tty ready | + line1 | + line2 | + line3 | + line4 | + {1: } | + -- TERMINAL -- | + ]]) + end) + + it('wont show any folds', function() + feed('ggvGzf') + wait() + screen:expect([[ + ^tty ready | + line1 | + line2 | + line3 | + line4 | + {2: } | + | + ]]) + end) + end) +end) + diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua new file mode 100644 index 0000000000..c102b1f133 --- /dev/null +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -0,0 +1,138 @@ +local helpers = require('test.functional.helpers') +local thelpers = require('test.functional.terminal.helpers') +local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf +local feed, nvim = helpers.feed, helpers.nvim +local feed_data = thelpers.feed_data + +describe('terminal', function() + local screen + + before_each(function() + clear() + -- set the statusline to a constant value because of variables like pid + -- and current directory and to improve visibility of splits + nvim('set_option', 'statusline', '==========') + nvim('command', 'highlight StatusLine cterm=NONE') + nvim('command', 'highlight StatusLineNC cterm=NONE') + nvim('command', 'highlight VertSplit cterm=NONE') + screen = thelpers.screen_setup(3) + end) + + after_each(function() + screen:detach() + end) + + describe('when the screen is resized', function() + it('will forward a resize request to the program', function() + screen:try_resize(screen._width + 3, screen._height + 5) + screen:expect([[ + tty ready | + rows: 14, cols: 53 | + {1: } | + | + | + | + | + | + | + | + | + | + | + | + -- TERMINAL -- | + ]]) + screen:try_resize(screen._width - 6, screen._height - 10) + screen:expect([[ + tty ready | + rows: 14, cols: 53 | + rows: 4, cols: 47 | + {1: } | + -- TERMINAL -- | + ]]) + end) + end) + + describe('split horizontally', function() + before_each(function() + nvim('command', 'sp') + end) + + local function reduce_height() + screen:expect([[ + tty ready | + rows: 3, cols: 50 | + {1: } | + ~ | + ========== | + tty ready | + rows: 3, cols: 50 | + {2: } | + ========== | + -- TERMINAL -- | + ]]) + end + + it('uses the minimum height of all window displaying it', reduce_height) + + describe('and then vertically', function() + before_each(function() + reduce_height() + nvim('command', 'vsp') + end) + + local function reduce_width() + screen:expect([[ + rows: 3, cols: 50 |rows: 3, cols: 50 | + rows: 3, cols: 24 |rows: 3, cols: 24 | + {1: } |{2: } | + ~ |~ | + ========== ========== | + rows: 3, cols: 50 | + rows: 3, cols: 24 | + {2: } | + ========== | + -- TERMINAL -- | + ]]) + feed('gg') + screen:expect([[ + ^tty ready |rows: 3, cols: 50 | + rows: 3, cols: 50 |rows: 3, cols: 24 | + rows: 3, cols: 24 |{2: } | + {2: } |~ | + ========== ========== | + rows: 3, cols: 50 | + rows: 3, cols: 24 | + {2: } | + ========== | + | + ]]) + end + + it('uses the minimum width of all window displaying it', reduce_width) + + describe('and then closes one of the vertical splits with q:', function() + before_each(function() + reduce_width() + nvim('command', 'q') + feed('ja') + end) + + it('will restore the width', function() + screen:expect([[ + rows: 3, cols: 24 | + rows: 3, cols: 50 | + {2: } | + ~ | + ========== | + rows: 3, cols: 24 | + rows: 3, cols: 50 | + {1: } | + ========== | + -- TERMINAL -- | + ]]) + end) + end) + end) + end) +end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index c60944bbb0..174538df6e 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -190,8 +190,11 @@ function Screen:set_default_attr_ignore(attr_ignore) self._default_attr_ignore = attr_ignore end -function Screen:attach() - request('ui_attach', self._width, self._height, true) +function Screen:attach(rgb) + if rgb == nil then + rgb = true + end + request('ui_attach', self._width, self._height, rgb) end function Screen:detach() @@ -500,6 +503,7 @@ function Screen:snapshot_util(attrs, ignore) else print( "]], "..attrstr..")\n") end + io.stdout:flush() end function pprint_attrs(attrs)