Merge PR #3360 'More fixes for 0.1'

This commit is contained in:
Thiago de Arruda 2015-10-01 15:37:20 -03:00
commit 536c0ba27e
17 changed files with 375 additions and 83 deletions

View File

@ -1246,8 +1246,9 @@ do_shell (
// 1" command to the terminal.
ui_cursor_goto(msg_row, msg_col);
(void)call_shell(cmd, flags, NULL);
did_check_timestamps = FALSE;
need_check_timestamps = TRUE;
msg_didout = true;
did_check_timestamps = false;
need_check_timestamps = true;
// put the message cursor at the end of the screen, avoids wait_return()
// to overwrite the text that the external command showed

View File

@ -7624,7 +7624,7 @@ void update_topline_cursor(void)
*/
static void ex_normal(exarg_T *eap)
{
if (curbuf->terminal) {
if (curbuf->terminal && State & TERM_FOCUS) {
EMSG("Can't re-enter normal mode from terminal mode");
return;
}

View File

@ -302,7 +302,7 @@ getcmdline (
input_enable_events();
do {
c = safe_vgetc();
} while (c == K_IGNORE);
} while (c == K_IGNORE || c == K_PASTE);
input_disable_events();
if (c == K_EVENT) {

View File

@ -153,6 +153,7 @@ static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */
static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */
static int last_recorded_len = 0; /* number of last recorded chars */
static const uint8_t ui_toggle[] = { K_SPECIAL, KS_EXTRA, KE_PASTE, 0 };
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.c.generated.h"
@ -1873,14 +1874,15 @@ static int vgetorpeek(int advance)
}
}
/* Check for match with 'pastetoggle' */
if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) {
for (mlen = 0; mlen < typebuf.tb_len && p_pt[mlen];
++mlen)
if (p_pt[mlen] != typebuf.tb_buf[typebuf.tb_off
+ mlen])
break;
if (p_pt[mlen] == NUL) { /* match */
// Check for a key that can toggle the 'paste' option
if (mp == NULL && (State & (INSERT|NORMAL))) {
bool match = typebuf_match_len(ui_toggle, &mlen);
if (!match && mlen != typebuf.tb_len && *p_pt != NUL) {
// didn't match ui_toggle_key and didn't try the whole typebuf,
// check the 'pastetoggle'
match = typebuf_match_len(p_pt, &mlen);
}
if (match) {
/* write chars to script file(s) */
if (mlen > typebuf.tb_maplen)
gotchars(typebuf.tb_buf + typebuf.tb_off
@ -4238,3 +4240,14 @@ static char_u * translate_mapping (
ga_append(&ga, NUL);
return (char_u *)(ga.ga_data);
}
static bool typebuf_match_len(const uint8_t *str, int *mlen)
{
int i;
for (i = 0; i < typebuf.tb_len && str[i]; i++) {
if (str[i] != typebuf.tb_buf[typebuf.tb_off + i])
break;
}
*mlen = i;
return str[i] == NUL; // matched the whole string
}

View File

@ -284,6 +284,7 @@ static struct key_name_entry {
{K_SNR, (char_u *)"SNR"},
{K_PLUG, (char_u *)"Plug"},
{K_CURSORHOLD, (char_u *)"CursorHold"},
{K_PASTE, (char_u *)"Paste"},
{0, NULL}
};

View File

@ -247,6 +247,8 @@ enum key_extra {
, KE_FOCUSGAINED /* focus gained */
, KE_FOCUSLOST /* focus lost */
, KE_EVENT // event
, KE_PASTE // special key to toggle the 'paste' option.
// sent only by UIs
};
/*
@ -437,6 +439,7 @@ enum key_extra {
#define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
#define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE)
/* Bits for modifier mask */
/* 0x01 cannot be used, because the modifier must be 0x02 or higher */

View File

@ -171,10 +171,17 @@ size_t input_enqueue(String keys)
}
if (*ptr == '<') {
// Invalid key sequence, skip until the next '>' or until *end
char *old_ptr = ptr;
// Invalid or incomplete key sequence, skip until the next '>' or until
// *end
do {
ptr++;
} while (ptr < end && *ptr != '>');
if (*ptr != '>') {
// Incomplete key sequence, return without consuming.
ptr = old_ptr;
break;
}
ptr++;
continue;
}

View File

@ -24,11 +24,13 @@ RBuffer *rbuffer_new(size_t capacity)
rv->size = 0;
rv->write_ptr = rv->read_ptr = rv->start_ptr;
rv->end_ptr = rv->start_ptr + capacity;
rv->temp = NULL;
return rv;
}
void rbuffer_free(RBuffer *buf)
{
xfree(buf->temp);
xfree(buf);
}
@ -69,12 +71,20 @@ char *rbuffer_write_ptr(RBuffer *buf, size_t *write_count) FUNC_ATTR_NONNULL_ALL
return buf->write_ptr;
}
// Set read and write pointer for an empty RBuffer to the beginning of the
// buffer.
// Reset an RBuffer so read_ptr is at the beginning of the memory. If
// necessary, this moves existing data by allocating temporary memory.
void rbuffer_reset(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
{
if (buf->size == 0) {
buf->write_ptr = buf->read_ptr = buf->start_ptr;
size_t temp_size;
if ((temp_size = rbuffer_size(buf))) {
if (buf->temp == NULL) {
buf->temp = xmalloc(rbuffer_capacity(buf));
}
rbuffer_read(buf, buf->temp, buf->size);
}
buf->read_ptr = buf->write_ptr = buf->start_ptr;
if (temp_size) {
rbuffer_write(buf, buf->temp, temp_size);
}
}

View File

@ -72,6 +72,8 @@ struct rbuffer {
rbuffer_callback full_cb, nonfull_cb;
void *data;
size_t size;
// helper memory used to by rbuffer_reset if required
char *temp;
char *end_ptr, *read_ptr, *write_ptr;
char start_ptr[];
};

View File

@ -9,7 +9,8 @@
#include "nvim/os/input.h"
#include "nvim/event/rstream.h"
#define PASTETOGGLE_KEY "<f37>"
#define PASTETOGGLE_KEY "<Paste>"
#define KEY_BUFFER_SIZE 0xfff
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/input.c.generated.h"
@ -20,6 +21,9 @@ void term_input_init(TermInput *input, Loop *loop)
input->loop = loop;
input->paste_enabled = false;
input->in_fd = 0;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
uv_mutex_init(&input->key_buffer_mutex);
uv_cond_init(&input->key_buffer_cond);
const char *term = os_getenv("TERM");
if (!term) {
@ -34,15 +38,13 @@ void term_input_init(TermInput *input, Loop *loop)
rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff, input);
// initialize a timer handle for handling ESC with libtermkey
time_watcher_init(loop, &input->timer_handle, input);
// Set the pastetoggle option to a special key that will be sent when
// \e[20{0,1}~/ are received
Error err = ERROR_INIT;
vim_set_option(cstr_as_string("pastetoggle"),
STRING_OBJ(cstr_as_string(PASTETOGGLE_KEY)), &err);
}
void term_input_destroy(TermInput *input)
{
rbuffer_free(input->key_buffer);
uv_mutex_destroy(&input->key_buffer_mutex);
uv_cond_destroy(&input->key_buffer_cond);
time_watcher_close(&input->timer_handle, NULL);
stream_close(&input->read_stream, NULL);
termkey_destroy(input->tk);
@ -59,25 +61,56 @@ void term_input_stop(TermInput *input)
time_watcher_stop(&input->timer_handle);
}
static void input_enqueue_event(void **argv)
{
char *buf = argv[0];
input_enqueue(cstr_as_string(buf));
xfree(buf);
}
static void input_done_event(void **argv)
{
input_done();
}
static void enqueue_input(char *buf, size_t size)
static void wait_input_enqueue(void **argv)
{
loop_schedule(&loop, event_create(1, input_enqueue_event, 1,
xstrndup(buf, size)));
TermInput *input = argv[0];
RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) {
size_t consumed = input_enqueue((String){.data = buf, .size = len});
if (consumed) {
rbuffer_consumed(input->key_buffer, consumed);
}
rbuffer_reset(input->key_buffer);
if (consumed < len) {
break;
}
}
uv_mutex_lock(&input->key_buffer_mutex);
input->waiting = false;
uv_cond_signal(&input->key_buffer_cond);
uv_mutex_unlock(&input->key_buffer_mutex);
}
static void forward_simple_utf8(TermKeyKey *key)
static void flush_input(TermInput *input, bool wait_until_empty)
{
size_t drain_boundary = wait_until_empty ? 0 : 0xff;
do {
uv_mutex_lock(&input->key_buffer_mutex);
loop_schedule(&loop, event_create(1, wait_input_enqueue, 1, input));
input->waiting = true;
while (input->waiting) {
uv_cond_wait(&input->key_buffer_cond, &input->key_buffer_mutex);
}
uv_mutex_unlock(&input->key_buffer_mutex);
} while (rbuffer_size(input->key_buffer) > drain_boundary);
}
static void enqueue_input(TermInput *input, char *buf, size_t size)
{
if (rbuffer_size(input->key_buffer) >
rbuffer_capacity(input->key_buffer) - 0xff) {
// don't ever let the buffer get too full or we risk putting incomplete keys
// into it
flush_input(input, false);
}
rbuffer_write(input->key_buffer, buf, size);
}
static void forward_simple_utf8(TermInput *input, TermKeyKey *key)
{
size_t len = 0;
char buf[64];
@ -92,11 +125,10 @@ static void forward_simple_utf8(TermKeyKey *key)
ptr++;
}
buf[len] = 0;
enqueue_input(buf, len);
enqueue_input(input, buf, len);
}
static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
{
size_t len;
char buf[64];
@ -105,19 +137,19 @@ static void forward_modified_utf8(TermKey *tk, TermKeyKey *key)
&& key->code.sym == TERMKEY_SYM_ESCAPE) {
len = (size_t)snprintf(buf, sizeof(buf), "<Esc>");
} else {
len = termkey_strfkey(tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
}
enqueue_input(buf, len);
enqueue_input(input, buf, len);
}
static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
static void forward_mouse_event(TermInput *input, TermKeyKey *key)
{
char buf[64];
size_t len = 0;
int button, row, col;
TermKeyMouseEvent ev;
termkey_interpret_mouse(tk, key, &ev, &button, &row, &col);
termkey_interpret_mouse(input->tk, key, &ev, &button, &row, &col);
if (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG) {
return;
@ -159,7 +191,7 @@ static void forward_mouse_event(TermKey *tk, TermKeyKey *key)
}
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
enqueue_input(buf, len);
enqueue_input(input, buf, len);
}
static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
@ -189,17 +221,17 @@ static void tk_getkeys(TermInput *input, bool force)
while ((result = tk_getkey(input->tk, &key, force)) == TERMKEY_RES_KEY) {
if (key.type == TERMKEY_TYPE_UNICODE && !key.modifiers) {
forward_simple_utf8(&key);
forward_simple_utf8(input, &key);
} else if (key.type == TERMKEY_TYPE_UNICODE ||
key.type == TERMKEY_TYPE_FUNCTION ||
key.type == TERMKEY_TYPE_KEYSYM) {
forward_modified_utf8(input->tk, &key);
forward_modified_utf8(input, &key);
} else if (key.type == TERMKEY_TYPE_MOUSE) {
forward_mouse_event(input->tk, &key);
forward_mouse_event(input, &key);
}
}
if (result != TERMKEY_RES_AGAIN) {
if (result != TERMKEY_RES_AGAIN || input->paste_enabled) {
return;
}
@ -230,21 +262,7 @@ static bool handle_bracketed_paste(TermInput *input)
if (input->paste_enabled == enable) {
return true;
}
if (enable) {
// Get the current mode
int state = get_real_state();
if (state & NORMAL) {
// Enter insert mode
enqueue_input("i", 1);
} else if (state & VISUAL) {
// Remove the selected text and enter insert mode
enqueue_input("c", 1);
} else if (!(state & INSERT)) {
// Don't mess with the paste option
return true;
}
}
enqueue_input(PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
enqueue_input(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1);
input->paste_enabled = enable;
return true;
}
@ -326,7 +344,7 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
}
}
} while (rbuffer_size(input->read_stream.buffer));
flush_input(input, true);
// Make sure the next input escape sequence fits into the ring buffer
// without wrap around, otherwise it could be misinterpreted.
rbuffer_reset(input->read_stream.buffer);

View File

@ -10,10 +10,14 @@
typedef struct term_input {
int in_fd;
bool paste_enabled;
bool waiting;
TermKey *tk;
TimeWatcher timer_handle;
Loop *loop;
Stream read_stream;
RBuffer *key_buffer;
uv_mutex_t key_buffer_mutex;
uv_cond_t key_buffer_cond;
} TermInput;
#ifdef INCLUDE_GENERATED_DECLARATIONS

View File

@ -372,5 +372,6 @@ return {
set_session = set_session,
write_file = write_file,
rmdir = rmdir,
mkdir = lfs.mkdir,
exc_exec = exc_exec,
}

View File

@ -0,0 +1,49 @@
-- Specs for bang/filter commands
local helpers = require('test.functional.helpers')
local feed, execute, clear = helpers.feed, helpers.execute, helpers.clear
local mkdir, write_file, rmdir = helpers.mkdir, helpers.write_file, helpers.rmdir
local Screen = require('test.functional.ui.screen')
describe('issues', function()
local screen
before_each(function()
clear()
rmdir('bang_filter_spec')
mkdir('bang_filter_spec')
write_file('bang_filter_spec/f1', 'f1')
write_file('bang_filter_spec/f2', 'f2')
write_file('bang_filter_spec/f3', 'f3')
screen = Screen.new()
screen:attach()
end)
after_each(function()
rmdir('bang_filter_spec')
end)
it('#3269 Last line of shell output is not truncated', function()
execute([[nnoremap <silent>\l :!ls bang_filter_spec<cr>]])
feed([[\l]])
screen:expect([[
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
:!ls bang_filter_spec |
|
f1 |
f2 |
f3 |
Press ENTER or type command to continue^ |
]])
end)
end)

View File

@ -1,7 +1,7 @@
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 execute, nvim, wait = helpers.execute, helpers.nvim, helpers.wait
local function feed_data(data)
nvim('set_var', 'term_data', data)
@ -31,13 +31,15 @@ local function clear_attrs() feed_termcode('[0;10m') end
local function enable_mouse() feed_termcode('[?1002h') end
local function disable_mouse() feed_termcode('[?1002l') end
local default_command = '["'..nvim_dir..'/tty-test'..'"]'
local function screen_setup(extra_height)
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)
screen:set_default_attr_ids({
[1] = {reverse = true}, -- focused cursor
@ -56,7 +58,8 @@ local function screen_setup(extra_height)
-- 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(["'..nvim_dir..'/tty-test"]) | startinsert')
execute('enew | call termopen('..command..') | 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)
--
@ -75,6 +78,9 @@ local function screen_setup(extra_height)
table.insert(expected, '-- TERMINAL -- ')
screen:expect(table.concat(expected, '\n'))
else
wait()
end
return screen
end

View File

@ -0,0 +1,106 @@
-- Some sanity checks for the TUI using the builtin terminal emulator
-- as a simple way to send keys and assert screen state.
local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')
local thelpers = require('test.functional.terminal.helpers')
local feed = thelpers.feed_data
local execute = helpers.execute
describe('tui', function()
local screen
before_each(function()
helpers.clear()
screen = thelpers.screen_setup(0, '["'..helpers.nvim_prog..'", "-u", "NONE", "--cmd", "set noswapfile"]')
screen.timeout = 30000 -- pasting can be really slow in the TUI
screen:expect([[
{1: } |
~ |
~ |
~ |
[No Name] |
|
-- TERMINAL -- |
]])
end)
after_each(function()
screen:detach()
end)
it('accepts basic utf-8 input', function()
feed('iabc\ntest1\ntest2')
screen:expect([[
abc |
test1 |
test2{1: } |
~ |
[No Name] [+] |
-- INSERT -- |
-- TERMINAL -- |
]])
feed('\x1b')
screen:expect([[
abc |
test1 |
test{1:2} |
~ |
[No Name] [+] |
|
-- TERMINAL -- |
]])
end)
it('automatically sends <Paste> for bracketed paste sequences', function()
feed('i\x1b[200~')
screen:expect([[
{1: } |
~ |
~ |
~ |
[No Name] |
-- INSERT (paste) -- |
-- TERMINAL -- |
]])
feed('pasted from terminal')
screen:expect([[
pasted from terminal{1: } |
~ |
~ |
~ |
[No Name] [+] |
-- INSERT (paste) -- |
-- TERMINAL -- |
]])
feed('\x1b[201~')
screen:expect([[
pasted from terminal{1: } |
~ |
~ |
~ |
[No Name] [+] |
-- INSERT -- |
-- TERMINAL -- |
]])
end)
it('can handle arbitrarily long bursts of input', function()
execute('set ruler')
local t = {}
for i = 1, 3000 do
t[i] = 'item ' .. tostring(i)
end
feed('i\x1b[200~')
feed(table.concat(t, '\n'))
feed('\x1b[201~')
screen:expect([[
item 2997 |
item 2998 |
item 2999 |
item 3000{1: } |
[No Name] [+] 3000,10 Bot|
-- INSERT -- |
-- TERMINAL -- |
]])
end)
end)

View File

@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')
local clear, execute, nvim = helpers.clear, helpers.execute, helpers.nvim
local feed, next_message, eq = helpers.feed, helpers.next_message, helpers.eq
local expect = helpers.expect
local Screen = require('test.functional.ui.screen')
describe('mappings', function()
local cid
@ -40,7 +41,76 @@ describe('mappings', function()
end)
end)
describe('feeding large chunks of input with <Paste>', function()
local screen
before_each(function()
clear()
screen = Screen.new()
screen:attach()
execute('set ruler')
end)
it('ok', function()
local t = {}
for i = 1, 20000 do
t[i] = 'item ' .. tostring(i)
end
feed('i<Paste>')
screen:expect([[
^ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
~ |
-- INSERT (paste) -- |
]])
feed(table.concat(t, '<Enter>'))
screen:expect([[
item 19988 |
item 19989 |
item 19990 |
item 19991 |
item 19992 |
item 19993 |
item 19994 |
item 19995 |
item 19996 |
item 19997 |
item 19998 |
item 19999 |
item 20000^ |
-- INSERT (paste) -- |
]])
feed('<Paste>')
screen:expect([[
item 19988 |
item 19989 |
item 19990 |
item 19991 |
item 19992 |
item 19993 |
item 19994 |
item 19995 |
item 19996 |
item 19997 |
item 19998 |
item 19999 |
item 20000^ |
-- INSERT -- 20000,11 Bot |
]])
end)
end)
describe('input utf sequences that contain CSI/K_SPECIAL', function()
before_each(clear)
it('ok', function()
feed('i…<esc>')
expect('')

View File

@ -163,6 +163,7 @@ function Screen.new(width, height)
height = 14
end
local self = setmetatable({
timeout = default_screen_timeout,
title = '',
icon = '',
bell = false,
@ -248,7 +249,7 @@ function Screen:wait(check, timeout)
return true
end
run(nil, notification_cb, nil, timeout or default_screen_timeout)
run(nil, notification_cb, nil, timeout or self.timeout)
if not checked then
err = check()
end