Merge pull request #2346 from splinterofchaos/fix-terminal

[RFC] terminal: Handle loss of focus in event loop.
This commit is contained in:
Scott Prager 2015-04-08 12:27:52 -04:00
commit e1bac3b840
4 changed files with 99 additions and 20 deletions

View File

@ -246,7 +246,7 @@ edit (
) )
{ {
if (curbuf->terminal) { if (curbuf->terminal) {
terminal_enter(curbuf->terminal, true); terminal_enter(true);
return false; return false;
} }

View File

@ -307,9 +307,12 @@ void terminal_close(Terminal *term, char *msg)
term->forward_mouse = false; term->forward_mouse = false;
term->closed = true; term->closed = true;
if (!msg || exiting) { if (!msg || exiting) {
// If no msg was given, this was called by close_buffer(buffer.c) so we // If no msg was given, this was called by close_buffer(buffer.c). Or if
// should not wait for the user to press a key. Also cannot wait if // exiting, we must inform the buffer the terminal no longer exists so that
// `exiting == true` // close_buffer() doesn't call this again.
term->buf->terminal = NULL;
term->buf = NULL;
// We should not wait for the user to press a key.
term->opts.close_cb(term->opts.data); term->opts.close_cb(term->opts.data);
} else { } else {
terminal_receive(term, msg, strlen(msg)); terminal_receive(term, msg, strlen(msg));
@ -338,7 +341,7 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height)
// terminal in the current tab. // terminal in the current tab.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (!wp->w_closing && wp->w_buffer == term->buf) { if (!wp->w_closing && wp->w_buffer == term->buf) {
width = (uint16_t)MIN(width, (uint16_t)wp->w_width); width = (uint16_t)MIN(width, (uint16_t)(wp->w_width - win_col_off(wp)));
height = (uint16_t)MIN(height, (uint16_t)wp->w_height); height = (uint16_t)MIN(height, (uint16_t)wp->w_height);
} }
} }
@ -353,8 +356,14 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height)
invalidate_terminal(term, -1, -1); invalidate_terminal(term, -1, -1);
} }
void terminal_enter(Terminal *term, bool process_deferred) void terminal_enter(bool process_deferred)
{ {
Terminal *term = curbuf->terminal;
assert(term && "should only be called when curbuf has a terminal");
// Ensure the terminal is properly sized.
terminal_resize(term, 0, 0);
checkpcmark(); checkpcmark();
setpcmark(); setpcmark();
int save_state = State; int save_state = State;
@ -373,7 +382,9 @@ void terminal_enter(Terminal *term, bool process_deferred)
int c; int c;
bool close = false; bool close = false;
for (;;) { bool got_bs = false; // True if the last input was <C-\>
while (term->buf == curbuf) {
if (process_deferred) { if (process_deferred) {
event_enable_deferred(); event_enable_deferred();
} }
@ -385,14 +396,6 @@ void terminal_enter(Terminal *term, bool process_deferred)
} }
switch (c) { switch (c) {
case Ctrl_BSL:
c = safe_vgetc();
if (c == Ctrl_N) {
goto end;
}
terminal_send_key(term, c);
break;
case K_LEFTMOUSE: case K_LEFTMOUSE:
case K_LEFTDRAG: case K_LEFTDRAG:
case K_LEFTRELEASE: case K_LEFTRELEASE:
@ -413,12 +416,22 @@ void terminal_enter(Terminal *term, bool process_deferred)
event_process(); event_process();
break; break;
case Ctrl_N:
if (got_bs) {
goto end;
}
default: default:
if (c == Ctrl_BSL && !got_bs) {
got_bs = true;
break;
}
if (term->closed) { if (term->closed) {
close = true; close = true;
goto end; goto end;
} }
got_bs = false;
terminal_send_key(term, c); terminal_send_key(term, c);
} }
} }
@ -431,7 +444,7 @@ end:
invalidate_terminal(term, term->cursor.row, term->cursor.row + 1); invalidate_terminal(term, term->cursor.row, term->cursor.row + 1);
mapped_ctrl_c = save_mapped_ctrl_c; mapped_ctrl_c = save_mapped_ctrl_c;
unshowmode(true); unshowmode(true);
redraw(false); redraw(term->buf != curbuf);
ui_busy_stop(); ui_busy_stop();
if (close) { if (close) {
term->opts.close_cb(term->opts.data); term->opts.close_cb(term->opts.data);
@ -441,7 +454,9 @@ end:
void terminal_destroy(Terminal *term) void terminal_destroy(Terminal *term)
{ {
term->buf->terminal = NULL; if (term->buf) {
term->buf->terminal = NULL;
}
term->buf = NULL; term->buf = NULL;
pmap_del(ptr_t)(invalidated_terminals, term); pmap_del(ptr_t)(invalidated_terminals, term);
for (size_t i = 0 ; i < term->sb_current; i++) { for (size_t i = 0 ; i < term->sb_current; i++) {
@ -920,8 +935,13 @@ static void on_refresh(Event event)
// be used act on terminal output. // be used act on terminal output.
block_autocmds(); block_autocmds();
map_foreach(invalidated_terminals, term, stub, { map_foreach(invalidated_terminals, term, stub, {
if (!term->buf) { // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid.
bool valid = true;
if (!term->buf || !(valid = buf_valid(term->buf))) {
// destroyed by `close_buffer`. Dont do anything else // destroyed by `close_buffer`. Dont do anything else
if (!valid) {
term->buf = NULL;
}
continue; continue;
} }
bool pending_resize = term->pending_resize; bool pending_resize = term->pending_resize;
@ -1018,6 +1038,11 @@ static void refresh_screen(Terminal *term)
static void redraw(bool restore_cursor) static void redraw(bool restore_cursor)
{ {
Terminal *term = curbuf->terminal;
if (!term) {
restore_cursor = true;
}
int save_row, save_col; int save_row, save_col;
if (restore_cursor) { if (restore_cursor) {
// save the current row/col to restore after updating screen when not // save the current row/col to restore after updating screen when not
@ -1040,7 +1065,6 @@ static void redraw(bool restore_cursor)
showruler(false); showruler(false);
Terminal *term = curbuf->terminal;
if (term && is_focused(term)) { if (term && is_focused(term)) {
curwin->w_wrow = term->cursor.row; curwin->w_wrow = term->cursor.row;
curwin->w_wcol = term->cursor.col + win_col_off(curwin); curwin->w_wcol = term->cursor.col + win_col_off(curwin);

View File

@ -2,7 +2,9 @@ local helpers = require('test.functional.helpers')
local Screen = require('test.functional.ui.screen') local Screen = require('test.functional.ui.screen')
local thelpers = require('test.functional.terminal.helpers') local thelpers = require('test.functional.terminal.helpers')
local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
local wait, execute, eq = helpers.wait, helpers.execute, helpers.eq local wait = helpers.wait
local eval, execute, source = helpers.eval, helpers.execute, helpers.source
local eq, neq = helpers.eq, helpers.neq
describe('terminal buffer', function() describe('terminal buffer', function()
@ -155,5 +157,44 @@ describe('terminal buffer', function()
:bnext | :bnext |
]]) ]])
end) end)
it('handles loss of focus gracefully', function()
-- Temporarily change the statusline to avoid printing the file name, which
-- varies be where the test is run.
nvim('set_option', 'statusline', '==========')
execute('set laststatus=0')
-- Save the buffer number of the terminal for later testing.
local tbuf = eval('bufnr("%")')
source([[
function! SplitWindow()
new
call feedkeys("iabc\<Esc>")
endfunction
startinsert
call jobstart(['sh', '-c', 'exit'], {'on_exit': function("SplitWindow")})
call feedkeys("\<C-\>", 't') " vim will expect <C-n>, but be exited out of
" the terminal before it can be entered.
]])
-- We should be in a new buffer now.
screen:expect([[
ab^c |
~ |
========== |
rows: 2, cols: 50 |
{2: } |
{1:========== }|
|
]])
neq(tbuf, eval('bufnr("%")'))
execute('quit!') -- Should exit the new window, not the terminal.
eq(tbuf, eval('bufnr("%")'))
execute('set laststatus=1') -- Restore laststatus to the default.
end)
end) end)

View File

@ -50,6 +50,20 @@ describe('terminal mouse', function()
]]) ]])
end) end)
it('will exit focus after <C-\\>, then scrolled', function()
feed('<C-\\>')
feed('<MouseDown><0,0>')
screen:expect([[
line23 |
line24 |
line25 |
line26 |
line27 |
^line28 |
|
]])
end)
describe('with mouse events enabled by the program', function() describe('with mouse events enabled by the program', function()
before_each(function() before_each(function()
thelpers.enable_mouse() thelpers.enable_mouse()