From ff819d8ad72b6a7404d28707f1f9ef460c35c472 Mon Sep 17 00:00:00 2001 From: Lech Lorens Date: Sun, 1 Oct 2017 15:37:27 +0200 Subject: quickfix: fix location list updates. Fix quickfix performance optimization which prevented quickfix items from being updated when there were multiple windows with location lists but the buffer with errors only in one of the lists. --- src/nvim/mark.c | 14 ++++++++++---- src/nvim/quickfix.c | 14 +++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 7889fabd45..1ba400972c 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -951,11 +951,17 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, one_adjust_nodel(&(curbuf->b_visual.vi_start.lnum)); one_adjust_nodel(&(curbuf->b_visual.vi_end.lnum)); - /* quickfix marks */ - qf_mark_adjust(NULL, line1, line2, amount, amount_after); - /* location lists */ + // quickfix marks + if (!qf_mark_adjust(NULL, line1, line2, amount, amount_after)) { + curbuf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY; + } + // location lists + bool found_one = false; FOR_ALL_TAB_WINDOWS(tab, win) { - qf_mark_adjust(win, line1, line2, amount, amount_after); + found_one |= qf_mark_adjust(win, line1, line2, amount, amount_after); + } + if (!found_one) { + curbuf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY; } sign_mark_adjust(line1, line2, amount, amount_after); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index e6b1e7b95a..b9228e15b9 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2379,7 +2379,8 @@ static void qf_free(qf_info_T *qi, int idx) /* * qf_mark_adjust: adjust marks */ -void qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long amount_after) +bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, + long amount_after) { int i; qfline_T *qfp; @@ -2389,11 +2390,12 @@ void qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long int buf_has_flag = wp == NULL ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; if (!(curbuf->b_has_qf_entry & buf_has_flag)) { - return; + return false; } if (wp != NULL) { - if (wp->w_llist == NULL) - return; + if (wp->w_llist == NULL) { + return false; + } qi = wp->w_llist; } @@ -2414,9 +2416,7 @@ void qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long } } - if (!found_one) { - curbuf->b_has_qf_entry &= ~buf_has_flag; - } + return found_one; } /* -- cgit From df107149913f82e8b3b6b2060d6dbed3d90e68fe Mon Sep 17 00:00:00 2001 From: Phlosioneer Date: Sun, 19 Nov 2017 19:55:28 -0500 Subject: server.c: Fix bug in release mode (#7594) When compiling with CMAKE_BUILD_TYPE=RelWithDebInfo, several -Wmaybe-uninitialized warnings are printed. These were thought to be false positives (#5061); there are no control paths that lead to an uninitialized value. However, when gcc is run in -O2 mode, it makes a mistake while generating the necessary logic. Specifically, for the code: ... int = 0; // Index of the server whose address equals addr. for (; i < watchers.ga_len; i++) { watcher = ((SocketWatcher **)watchers.ga_data)[i]; // } if (i >= watchers.ga_len) { ELOG("Not listening on %s", addr); return; } ... Gcc generates: ... <+98>: cmp %ebx, %ebp <+100>: jg 0x530f13 <+102>: cmp %ebp, ebx <+104>: jl 0x530f7e ... Normally, the if statement should catch the only control path where watcher is not assigned: watchers.ga_len <= 0. When compiled, the assembly lines 98 and 100 correspond to checking if i < watchers.ga_len, and the lines 102 and 104 correspond to checking if i >= watchers.ga_len. The assembly seems to compare ebp (which is watchers.ga_len) with ebx (which is i), and jump if greater; then do the same comparison and jump if less. This is where gcc makes a mistake: it flips the order of the cmp instruction. This means that the REAL behavior is first check if i < watchers.ga_len and then check if i < watchers.ga_len. Which means the code inside the if statement is NEVER executed; no combination of i and watchers.ga_len will ever trigger ELOG(). So not only is this a use of an uninitialized value if watchers.ga_len == 0 (or technically, if it's less than zero too), it also clobbers any error detection if the for loop reaches the last entry (which would normally cause i == watchers.ga_len too). This commit fixes this issue by adding a bool to keep track of whether a watcher was found during the loop. This makes gcc generate the correct code, avoiding both bugs. --- src/nvim/msgpack_rpc/server.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 1e0cc27886..9bf122f4db 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -180,6 +180,7 @@ int server_start(const char *endpoint) void server_stop(char *endpoint) { SocketWatcher *watcher; + bool watcher_found = false; char addr[ADDRESS_MAX_SIZE]; // Trim to `ADDRESS_MAX_SIZE` @@ -189,11 +190,12 @@ void server_stop(char *endpoint) for (; i < watchers.ga_len; i++) { watcher = ((SocketWatcher **)watchers.ga_data)[i]; if (strcmp(addr, watcher->addr) == 0) { + watcher_found = true; break; } } - if (i >= watchers.ga_len) { + if (!watcher_found) { ELOG("Not listening on %s", addr); return; } -- cgit From 7d24a95b450c0d97706e2d92a19424d84d51015e Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Mon, 20 Nov 2017 09:02:15 +0800 Subject: vim-patch:8.0.0287 (#7590) Problem: Cannot access the arguments of the current function in debug mode. (Luc Hermitte) Solution: use get_funccal(). (Lemonboy, closes vim/vim#1432, closes vim/vim#1352) https://github.com/vim/vim/commit/c7d9eacefa319e5ac3b3b2334fda5acb126b8716 --- src/nvim/eval.c | 2 +- src/nvim/version.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 33bea8ef87..3c58f81d4b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18732,7 +18732,7 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht, case 'l': return (current_funccal == NULL ? NULL : (dictitem_T *)¤t_funccal->l_vars_var); case 'a': return (current_funccal == NULL - ? NULL : (dictitem_T *)¤t_funccal->l_avars_var); + ? NULL : (dictitem_T *)&get_funccal()->l_avars_var); } return NULL; } diff --git a/src/nvim/version.c b/src/nvim/version.c index c73cc0d3ee..997db1726b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -817,7 +817,7 @@ static const int included_patches[] = { 290, // 289, // 288 NA - // 287, + 287, // 286, // 285 NA // 284 NA -- cgit From c39140164877ce51d5785405205a570b45cc5f82 Mon Sep 17 00:00:00 2001 From: Hannu Hartikainen Date: Mon, 20 Nov 2017 22:20:01 +0200 Subject: helptags: fix double-free (#7600) closes #7599 Helped-by: oni-link Freeing `dirname` was first introduced by a code refactoring from `ex_helptags()` to `do_helptags()` (`vim-patch:7.4.1551`)(#4648) and later removed by `vim-patch:7.4.1562`(#4660). Only problem with that is, that the patches were not applied in order so the fixing patch was declared `N/A`. So `do_helptags()` should have never freed `dirname`. --- src/nvim/ex_cmds.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 39059a2c80..8616508d88 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5292,7 +5292,6 @@ static void do_helptags(char_u *dirname, bool add_help_tags) if (!add_pathsep((char *)NameBuff) || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { EMSG(_(e_fnametoolong)); - xfree(dirname); return; } @@ -5303,7 +5302,6 @@ static void do_helptags(char_u *dirname, bool add_help_tags) EW_FILE|EW_SILENT) == FAIL || filecount == 0) { EMSG2(_("E151: No match: %s"), NameBuff); - xfree(dirname); return; } -- cgit From 8674b0c3d161bd367ff63c6a8fb290fec0122d05 Mon Sep 17 00:00:00 2001 From: Phlosioneer Date: Mon, 20 Nov 2017 18:04:49 -0500 Subject: syntax.c: Fix maybe-uninitialized warning (#7596) When building in release mode, gcc generated a maybe-initialized warning in get_syn_options. The warning is both right and wrong; there is an execution path where the len variable is not initialized in the code: ... int len; ... for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0; ) { p = flagtab[fidx].name; int i; for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) if (arg[len] != p[i] && arg[len] != p[i + 1]) break; // } ... arg = skipwhite(arg + len); ... The initial for loop will not execute if ARRAY_SIZE(flagtab) == 0, and thus len will never be initialized. flagtab is a local-static variable, initialized to a long array of structured data, so ARRAY_SIZE(flagtab) can't be 0. However, gcc doesn't recognize ARRAY_SIZE(flagtab) as a constant. There are any number of reasons this could happen. In any case, the message can be fixed with a len=0 before the first for loop. In addition to the above warning, I've labeled flagtab and first_letters as const. They should never change. --- src/nvim/syntax.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 65490768c4..d28e996581 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -4009,10 +4009,10 @@ get_syn_options ( { char_u *gname_start, *gname; int syn_id; - int len; + int len = 0; char *p; int fidx; - static struct flag { + static const struct flag { char *name; int argtype; int flags; @@ -4035,7 +4035,7 @@ get_syn_options ( {"cCoOnNtTaAiInNsS", 1, 0}, {"cCoOnNtTaAiInNeEdDiInN", 2, 0}, {"nNeExXtTgGrRoOuUpP", 3, 0},}; - static char *first_letters = "cCoOkKeEtTsSgGdDfFnN"; + static const char *const first_letters = "cCoOkKeEtTsSgGdDfFnN"; if (arg == NULL) /* already detected error */ return NULL; @@ -4055,9 +4055,10 @@ get_syn_options ( for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0; ) { p = flagtab[fidx].name; int i; - for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) + for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) { if (arg[len] != p[i] && arg[len] != p[i + 1]) break; + } if (p[i] == NUL && (ascii_iswhite(arg[len]) || (flagtab[fidx].argtype > 0 ? arg[len] == '=' -- cgit From 7b686881a16a637e4e0a9e202c4b64bafc4eb8e8 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 21 Nov 2017 18:35:51 -0500 Subject: win: default grepprg to findstr.exe (#7611) --- src/nvim/options.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 52e478e78a..cb3e5ad856 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -993,11 +993,11 @@ return { expand=true, varname='p_gp', defaults={ - condition='UNIX', + condition='WIN32', -- Add an extra file name so that grep will always -- insert a file name in the match line. */ - if_true={vi="grep -n $* /dev/null"}, - if_false={vi="grep -n "}, + if_true={vi="findstr /n $* nul"}, + if_false={vi="grep -n $* /dev/null"} } }, { -- cgit From bb7795e82065ef2e8028064c62cb542362568e19 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Wed, 22 Nov 2017 18:57:56 +0800 Subject: vim-patch:8.0.0292 (#7592) Problem: The stat test is a bit slow. Solution: Remove a couple of sleep comments and reduce another. https://github.com/vim/vim/commit/a2f28859bfb3fa52bde14c9d2ca3ab7196a9154a --- src/nvim/testdir/test_stat.vim | 35 +++++++++++++++++++++-------------- src/nvim/version.c | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index 89ca9ef379..dee0d13e84 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -1,24 +1,24 @@ " Tests for stat functions and checktime func Test_existent_file() - let fname='Xtest.tmp' + let fname = 'Xtest.tmp' - let ts=localtime() - sleep 1 - let fl=['Hello World!'] + let ts = localtime() + let fl = ['Hello World!'] call writefile(fl, fname) - let tf=getftime(fname) - sleep 1 - let te=localtime() + let tf = getftime(fname) + let te = localtime() call assert_true(ts <= tf && tf <= te) call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) call assert_equal('file', getftype(fname)) call assert_equal('rw-', getfperm(fname)[0:2]) + + call delete(fname) endfunc func Test_existent_directory() - let dname='.' + let dname = '.' call assert_equal(0, getfsize(dname)) call assert_equal('dir', getftype(dname)) @@ -26,22 +26,29 @@ func Test_existent_directory() endfunc func Test_checktime() - let fname='Xtest.tmp' + let fname = 'Xtest.tmp' - let fl=['Hello World!'] + let fl = ['Hello World!'] call writefile(fl, fname) set autoread exec 'e' fname - sleep 2 - let fl=readfile(fname) + " FAT has a granularity of 2 seconds, otherwise it's usually 1 second + if has('win32') + sleep 2 + else + sleep 2 + endif + let fl = readfile(fname) let fl[0] .= ' - checktime' call writefile(fl, fname) checktime call assert_equal(fl[0], getline(1)) + + call delete(fname) endfunc func Test_nonexistent_file() - let fname='Xtest.tmp' + let fname = 'Xtest.tmp' call delete(fname) call assert_equal(-1, getftime(fname)) @@ -55,7 +62,7 @@ func Test_win32_symlink_dir() " So we use an existing symlink for this test. if has('win32') " Check if 'C:\Users\All Users' is a symlink to a directory. - let res=system('dir C:\Users /a') + let res = system('dir C:\Users /a') if match(res, '\C *All Users') >= 0 " Get the filetype of the symlink. call assert_equal('dir', getftype('C:\Users\All Users')) diff --git a/src/nvim/version.c b/src/nvim/version.c index 997db1726b..61cfd99c5d 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -812,7 +812,7 @@ static const int included_patches[] = { // 295, 294, // 293, - // 292, + 292, 291, 290, // 289, -- cgit From 9393be477a733183c2a992cc8728e3ebc65a3118 Mon Sep 17 00:00:00 2001 From: KunMing Xie Date: Wed, 22 Nov 2017 18:59:30 +0800 Subject: vim-patch:8.0.0289 (#7591) Problem: No test for "ga" and :ascii. Solution: Add a test. (Dominique Pelle, closes vim/vim#1429) https://github.com/vim/vim/commit/21d7c9b601f3048e1293ecd6c09b8325a15503cd --- src/nvim/testdir/Makefile | 1 + src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_ga.vim | 37 +++++++++++++++++++++++++++++++++++++ src/nvim/version.c | 2 +- 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/nvim/testdir/test_ga.vim (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index ccf999c7ee..111bd172ef 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -42,6 +42,7 @@ NEW_TESTS ?= \ test_filter_map.res \ test_fnameescape.res \ test_fold.res \ + test_ga.res \ test_glob2regpat.res \ test_gf.res \ test_gn.res \ diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 535e290a34..c1f6405579 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -12,6 +12,7 @@ source test_filter_cmd.vim source test_filter_map.vim source test_float_func.vim source test_functions.vim +source test_ga.vim source test_goto.vim source test_jumps.vim source test_fileformat.vim diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim new file mode 100644 index 0000000000..f9357ddc87 --- /dev/null +++ b/src/nvim/testdir/test_ga.vim @@ -0,0 +1,37 @@ +" Test ga normal command, and :ascii Ex command. +func Do_ga(c) + call setline(1, a:c) + let l:a = execute("norm 1goga") + let l:b = execute("ascii") + call assert_equal(l:a, l:b) + return l:a +endfunc + +func Test_ga_command() + new + set display=uhex + call assert_equal("\nNUL", Do_ga('')) + call assert_equal("\n<<01>> 1, Hex 01, Octal 001", Do_ga("\x01")) + call assert_equal("\n<<09>> 9, Hex 09, Octal 011", Do_ga("\t")) + + set display= + call assert_equal("\nNUL", Do_ga('')) + call assert_equal("\n<^A> 1, Hex 01, Octal 001", Do_ga("\x01")) + call assert_equal("\n<^I> 9, Hex 09, Octal 011", Do_ga("\t")) + + call assert_equal("\n 101, Hex 65, Octal 145", Do_ga('e')) + + if !has('multi_byte') + return + endif + + " Test a few multi-bytes characters. + call assert_equal("\n<é> 233, Hex 00e9, Octal 351", Do_ga('é')) + call assert_equal("\n<ẻ> 7867, Hex 1ebb, Octal 17273", Do_ga('ẻ')) + + " Test with combining characters. + call assert_equal("\n 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401", Do_ga("e\u0301")) + call assert_equal("\n 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461", Do_ga("e\u0301\u0331")) + call assert_equal("\n 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461 < ̸> 824, Hex 0338, Octal 1470", Do_ga("e\u0301\u0331\u0338")) + bwipe! +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 61cfd99c5d..1ceda55cf1 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -815,7 +815,7 @@ static const int included_patches[] = { 292, 291, 290, - // 289, + 289, // 288 NA 287, // 286, -- cgit From dddc609859c3e6e72f091dc25f996e2ca0084c0d Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Sun, 19 Nov 2017 11:19:41 -0800 Subject: menu.c: remove conditional expression that is always true --- src/nvim/menu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 88d968704b..2876ba82a7 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -1417,7 +1417,8 @@ void ex_emenu(exarg_T *eap) idx = MENU_INDEX_NORMAL; } - if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL) { + assert(idx != MENU_INDEX_INVALID); + if (menu->strings[idx] != NULL) { /* When executing a script or function execute the commands right now. * Otherwise put them in the typeahead buffer. */ if (current_SID != 0) -- cgit From fdcde7dba34b193e8fdafdf3b5b9c3ce4d430505 Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Sun, 19 Nov 2017 21:25:02 -0800 Subject: input.c: replace if/else with switch --- src/nvim/tui/input.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 8e08b77b00..b76a11cc71 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -199,18 +199,24 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Right"); } - if (ev == TERMKEY_MOUSE_PRESS) { - if (button == 4) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp"); - } else if (button == 5) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown"); - } else { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); - } - } else if (ev == TERMKEY_MOUSE_DRAG) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag"); - } else if (ev == TERMKEY_MOUSE_RELEASE) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release"); + switch (ev) { + case TERMKEY_MOUSE_PRESS: + if (button == 4) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp"); + } else if (button == 5) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown"); + } else { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); + } + break; + case TERMKEY_MOUSE_DRAG: + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag"); + break; + case TERMKEY_MOUSE_RELEASE: + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release"); + break; + case TERMKEY_MOUSE_UNKNOWN: + assert(false); } len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); -- cgit From c24b74c2291b3949f53abf6cee5b308320283642 Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Sun, 19 Nov 2017 23:19:41 -0800 Subject: Fix for pvs V782, pointer access to first element of array --- src/nvim/digraph.c | 2 +- src/nvim/tui/tui.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index bfb1b94738..dbcc8db109 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1696,7 +1696,7 @@ static void printdigraph(digr_T *dp) } } - p = buf; + p = &buf[0]; *p++ = dp->char1; *p++ = dp->char2; *p++ = ' '; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f35e99840d..b447b4aae2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1644,7 +1644,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) { uv_write_t req; uv_buf_t bufs[3]; - uv_buf_t *bufp = bufs; + uv_buf_t *bufp = &bufs[0]; TUIData *data = ui->data; if (data->bufpos <= 0 && data->busy == data->is_invisible) { -- cgit From c030a381684f3491decf5597b16dfa9a21f29500 Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Mon, 20 Nov 2017 11:27:06 -0800 Subject: helpers.c: statically assert integer falls within range --- src/nvim/api/private/helpers.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 2944925a9c..6808048ac8 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -12,6 +12,7 @@ #include "nvim/api/private/handle.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/ascii.h" +#include "nvim/assert.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" @@ -760,12 +761,9 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeWindow: case kObjectTypeTabpage: case kObjectTypeInteger: - if (obj.data.integer > VARNUMBER_MAX - || obj.data.integer < VARNUMBER_MIN) { - api_set_error(err, kErrorTypeValidation, "Integer value outside range"); - return false; - } - + STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T), + "Expected integer size to be less than or equal to VimL " + "number size"); tv->v_type = VAR_NUMBER; tv->vval.v_number = (varnumber_T)obj.data.integer; break; -- cgit From 1b94f24d6e5d96937062f02d084249c9054f25f2 Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Tue, 21 Nov 2017 10:36:06 -0800 Subject: eval.c: remove nonnullret deadcode The following calls can't return null: * xmalloc * xcalloc * get_buffer_info * get_tabpage_info * get_vim_var_str * get_win_info * tv_get_string * tv_list_alloc * tv_list_alloc_ret * vim_strnsave --- src/nvim/eval.c | 135 ++++++++++++++++++-------------------------------------- 1 file changed, 44 insertions(+), 91 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3c58f81d4b..39ce1ddcaa 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2206,10 +2206,6 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (len == -1) { // "[key]": get key from "var1" key = (char_u *)tv_get_string(&var1); // is number or string - if (key == NULL) { - tv_clear(&var1); - return NULL; - } } lp->ll_list = NULL; lp->ll_dict = lp->ll_tv->vval.v_dict; @@ -5706,10 +5702,6 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs, c = *p; *p = NUL; arg = vim_strsave(arg); - if (arg == NULL) { - *p = c; - goto err_ret; - } // Check for duplicate argument name. for (i = 0; i < newargs->ga_len; i++) { @@ -5833,10 +5825,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) fp = (ufunc_T *)xcalloc(1, sizeof(ufunc_T) + STRLEN(name)); pt = (partial_T *)xcalloc(1, sizeof(partial_T)); - if (pt == NULL) { - xfree(fp); - goto errret; - } ga_init(&newlines, (int)sizeof(char_u *), 1); ga_grow(&newlines, 1); @@ -6222,13 +6210,9 @@ static char_u *fname_trans_sid(const char_u *const name, fname = fname_buf; } else { fname = xmalloc(i + STRLEN(name + llen) + 1); - if (fname == NULL) { - *error = ERROR_OTHER; - } else { - *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); - } + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); } } else { fname = (char_u *)name; @@ -6309,9 +6293,6 @@ call_func( // Make a copy of the name, if it comes from a funcref variable it could // be changed or deleted in the called function. name = vim_strnsave(funcname, len); - if (name == NULL) { - return ret; - } fname = fname_trans_sid(name, fname_buf, &tofree, &error); @@ -7184,8 +7165,7 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) && argvars[1].v_type != VAR_UNKNOWN && tv_get_number_chk(&argvars[1], &error) != 0 && !error - && (name = tv_get_string_chk(&argvars[0])) != NULL - && !error) { + && (name = tv_get_string_chk(&argvars[0])) != NULL) { buf = buflist_new((char_u *)name, NULL, 1, 0); } @@ -7733,7 +7713,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } const char *const name = tv_get_string(&argvars[0]); - if (name == NULL || *name == NUL) { + if (*name == NUL) { EMSG(_(e_invarg)); return; } @@ -8748,8 +8728,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); dashes = get_vim_var_str(VV_FOLDDASHES); - if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count - && dashes != NULL) { + if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { /* Find first non-empty line in the fold. */ for (lnum = foldstart; lnum < foldend; ++lnum) { if (!linewhite(lnum)) { @@ -8874,10 +8853,8 @@ static void common_function(typval_T *argvars, typval_T *rettv, snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", (int64_t)current_SID); name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); - if (name != NULL) { - STRCPY(name, sid_buf); - STRCAT(name, s + off); - } + STRCPY(name, sid_buf); + STRCAT(name, s + off); } else { name = vim_strsave(s); } @@ -8927,11 +8904,6 @@ static void common_function(typval_T *argvars, typval_T *rettv, pt->pt_argc = arg_len + lv_len; pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); - if (pt->pt_argv == NULL) { - xfree(pt); - xfree(name); - goto theend; - } int i = 0; for (; i < arg_len; i++) { tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); @@ -9197,9 +9169,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } dict_T *const d = get_buffer_info(buf); - if (d != NULL) { - tv_list_append_dict(rettv->vval.v_list, d); - } + tv_list_append_dict(rettv->vval.v_list, d); if (argbuf != NULL) { return; } @@ -9568,13 +9538,11 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) theend: pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); tv_list_alloc_ret(rettv); - if (pat != NULL) { - ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } + for (int i = 0; i < xpc.xp_numfiles; i++) { + tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], + -1); } xfree(pat); ExpandCleanup(&xpc); @@ -10136,9 +10104,7 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) continue; } dict_T *const d = get_tabpage_info(tp, tpnr); - if (d != NULL) { - tv_list_append_dict(rettv->vval.v_list, d); - } + tv_list_append_dict(rettv->vval.v_list, d); if (tparg != NULL) { return; } @@ -10240,9 +10206,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } winnr++; dict_T *const d = get_win_info(wp, tabnr, winnr); - if (d != NULL) { - tv_list_append_dict(rettv->vval.v_list, d); - } + tv_list_append_dict(rettv->vval.v_list, d); if (wparg != NULL) { // found information about a specific window return; @@ -14803,9 +14767,6 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (di == NULL) { if (s == NULL) { s = tv_list_alloc(); - if (s == NULL) { - return; - } } // match from matchaddpos() @@ -18628,47 +18589,39 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict) // Turn "dict.Func" into a partial for "Func" with "dict". if (fp != NULL && (fp->uf_flags & FC_DICT)) { partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T)); - - if (pt != NULL) { - pt->pt_refcount = 1; - pt->pt_dict = selfdict; - (selfdict->dv_refcount)++; - pt->pt_auto = true; - if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { - // Just a function: Take over the function name and use selfdict. - pt->pt_name = rettv->vval.v_string; + pt->pt_refcount = 1; + pt->pt_dict = selfdict; + (selfdict->dv_refcount)++; + pt->pt_auto = true; + if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { + // Just a function: Take over the function name and use selfdict. + pt->pt_name = rettv->vval.v_string; + } else { + partial_T *ret_pt = rettv->vval.v_partial; + int i; + + // Partial: copy the function name, use selfdict and copy + // args. Can't take over name or args, the partial might + // be referenced elsewhere. + if (ret_pt->pt_name != NULL) { + pt->pt_name = vim_strsave(ret_pt->pt_name); + func_ref(pt->pt_name); } else { - partial_T *ret_pt = rettv->vval.v_partial; - int i; - - // Partial: copy the function name, use selfdict and copy - // args. Can't take over name or args, the partial might - // be referenced elsewhere. - if (ret_pt->pt_name != NULL) { - pt->pt_name = vim_strsave(ret_pt->pt_name); - func_ref(pt->pt_name); - } else { - pt->pt_func = ret_pt->pt_func; - func_ptr_ref(pt->pt_func); - } - if (ret_pt->pt_argc > 0) { - size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; - pt->pt_argv = (typval_T *)xmalloc(arg_size); - if (pt->pt_argv == NULL) { - // out of memory: drop the arguments - pt->pt_argc = 0; - } else { - pt->pt_argc = ret_pt->pt_argc; - for (i = 0; i < pt->pt_argc; i++) { - tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); - } - } + pt->pt_func = ret_pt->pt_func; + func_ptr_ref(pt->pt_func); + } + if (ret_pt->pt_argc > 0) { + size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc; + pt->pt_argv = (typval_T *)xmalloc(arg_size); + pt->pt_argc = ret_pt->pt_argc; + for (i = 0; i < pt->pt_argc; i++) { + tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]); } - partial_unref(ret_pt); } - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = pt; + partial_unref(ret_pt); } + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; } } -- cgit From fe2546c81a8a7c0be5bbf0737d1169f6cd49bba0 Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Sun, 19 Nov 2017 12:19:09 -0800 Subject: move.c: remove unreachable break statement n > 0 verified by while condition, (--n < 0) always false --- src/nvim/move.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/move.c b/src/nvim/move.c index 9693132846..2debd90337 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1989,8 +1989,7 @@ void halfpage(bool flag, linenr_T Prenum) while (n > 0 && curwin->w_botline <= curbuf->b_ml.ml_line_count) { if (curwin->w_topfill > 0) { i = 1; - if (--n < 0 && scrolled > 0) - break; + n--; --curwin->w_topfill; } else { i = plines_nofill(curwin->w_topline); @@ -2067,8 +2066,7 @@ void halfpage(bool flag, linenr_T Prenum) while (n > 0 && curwin->w_topline > 1) { if (curwin->w_topfill < diff_check_fill(curwin, curwin->w_topline)) { i = 1; - if (--n < 0 && scrolled > 0) - break; + n--; ++curwin->w_topfill; } else { i = plines_nofill(curwin->w_topline - 1); -- cgit From a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 22 Nov 2017 22:35:20 +0100 Subject: cmdline: CmdlineEnter and CmdlineLeave autocommands (#7422) vim-patch:fafcf0dd59fd patch 8.0.1206: no autocmd for entering or leaving the command line Problem: No autocmd for entering or leaving the command line. Solution: Add CmdlineEnter and CmdlineLeave. https://github.com/vim/vim/commit/fafcf0dd59fd9c4ef743bb333ae40d1d322b6079 --- src/nvim/auevents.lua | 2 ++ src/nvim/eval/typval.c | 23 ++++++++++++++++ src/nvim/ex_getln.c | 57 ++++++++++++++++++++++++++++++++++++++- src/nvim/globals.h | 2 ++ src/nvim/testdir/test_autocmd.vim | 22 +++++++++++++++ src/nvim/version.c | 1 + 6 files changed, 106 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 68a47c244f..7dfaf54ff0 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -19,6 +19,8 @@ return { 'BufWriteCmd', -- write buffer using command 'BufWritePost', -- after writing a buffer 'BufWritePre', -- before writing a buffer + 'CmdLineEnter', -- after entering cmdline mode + 'CmdLineLeave', -- before leaving cmdline mode 'CmdUndefined', -- command undefined 'CmdWinEnter', -- after entering the cmdline window 'CmdWinLeave', -- before leaving the cmdline window diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index c339a5cdd2..262ea922ef 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1374,6 +1374,29 @@ int tv_dict_add_nr(dict_T *const d, const char *const key, return OK; } +/// Add a special entry to dictionary +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] val SpecialVarValue to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_special(dict_T *const d, const char *const key, + const size_t key_len, SpecialVarValue val) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_lock = VAR_UNLOCKED; + item->di_tv.v_type = VAR_SPECIAL; + item->di_tv.vval.v_special = val; + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + /// Add a string entry to dictionary /// /// @param[out] d Dictionary to add entry to. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9c9ccbca4d..c1500e3121 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -348,8 +348,57 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) got_int = false; s->state.check = command_line_check; s->state.execute = command_line_execute; + + TryState tstate; + Error err = ERROR_INIT; + bool tl_ret = true; + dict_T *dict = get_vim_var_dict(VV_EVENT); + char firstcbuf[2]; + firstcbuf[0] = firstc > 0 ? firstc : '-'; + firstcbuf[1] = 0; + + if (has_event(EVENT_CMDLINEENTER)) { + // set v:event to a dictionary with information about the commandline + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + try_enter(&tstate); + + apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf, + false, curbuf); + tv_dict_clear(dict); + + + tl_ret = try_leave(&tstate, &err); + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + redrawcmd(); + } + tl_ret = true; + } + state_enter(&s->state); + if (has_event(EVENT_CMDLINELEAVE)) { + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + // not readonly: + tv_dict_add_special(dict, S_LEN("abort"), + s->gotesc ? kSpecialVarTrue : kSpecialVarFalse); + try_enter(&tstate); + apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf, + false, curbuf); + // error printed below, to avoid redraw issues + tl_ret = try_leave(&tstate, &err); + if (tv_dict_get_number(dict, "abort") != 0) { + s->gotesc = 1; + } + tv_dict_clear(dict); + } + cmdmsg_rl = false; cmd_fkmap = 0; @@ -410,8 +459,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) msg_scroll = s->save_msg_scroll; redir_off = false; + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + } + // When the command line was typed, no need for a wait-return prompt. - if (s->some_key_typed) { + if (s->some_key_typed && tl_ret) { need_wait_return = false; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 8f804ff888..2b025c2c50 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1167,6 +1167,8 @@ EXTERN char_u e_dirnotf[] INIT(= N_( EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); +EXTERN char_u e_autocmd_err[] INIT(=N_( + "E920: autocmd has thrown an exception: %s")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 835df42a10..82c04abf5b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -420,3 +420,25 @@ function Test_autocmd_bufwipe_in_SessLoadPost2() call delete(file) endfor endfunc + +func Test_Cmdline() + au! CmdlineEnter : let g:entered = expand('') + au! CmdlineLeave : let g:left = expand('') + let g:entered = 0 + let g:left = 0 + call feedkeys(":echo 'hello'\", 'xt') + call assert_equal(':', g:entered) + call assert_equal(':', g:left) + au! CmdlineEnter + au! CmdlineLeave + + au! CmdlineEnter / let g:entered = expand('') + au! CmdlineLeave / let g:left = expand('') + let g:entered = 0 + let g:left = 0 + call feedkeys("/hello", 'xt') + call assert_equal('/', g:entered) + call assert_equal('/', g:left) + au! CmdlineEnter + au! CmdlineLeave +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 1ceda55cf1..205c25b6cd 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -78,6 +78,7 @@ static char *features[] = { // clang-format off static const int included_patches[] = { + 1206, // 1026, 1025, 1024, -- cgit From 51637f4256353e513f262572bcb786b54d250e11 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 22 Nov 2017 22:47:12 +0100 Subject: tui: move terminfo_is_term_family() --- src/nvim/tui/terminfo.c | 18 +++++++++++++++++- src/nvim/tui/tui.c | 14 -------------- 2 files changed, 17 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 586fafba97..6b0be50917 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -3,10 +3,12 @@ // Built-in fallback terminfo entries. +#include +#include + #include #include "nvim/tui/terminfo.h" -#include "nvim/tui/tui.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.c.generated.h" @@ -75,6 +77,20 @@ static const signed char ansi_terminfo[] = { 26, 1, 40, 0, 23, 0, 16, 0, 125, 1, 68, 2, 97, 110, 115, 105, 124, 97, 110, 115, 105, 47, 112, 99, 45, 116, 101, 114, 109, 32, 99, 111, 109, 112, 97, 116, 105, 98, 108, 101, 32, 119, 105, 116, 104, 32, 99, 111, 108, 111, 114, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 80, 0, 8, 0, 24, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 0, 64, 0, 3, 0, 0, 0, 4, 0, 6, 0, -1, -1, 8, 0, 13, 0, 20, 0, 24, 0, 28, 0, -1, -1, 39, 0, 56, 0, 60, 0, -1, -1, 64, 0, -1, -1, -1, -1, 68, 0, -1, -1, 72, 0, -1, -1, 76, 0, 80, 0, -1, -1, -1, -1, 84, 0, 90, 0, 95, 0, -1, -1, -1, -1, -1, -1, -1, -1, 100, 0, -1, -1, 105, 0, 110, 0, 115, 0, 120, 0,-127, 0,-121, 0, -1, -1, -1, -1, -1, -1,-113, 0,-109, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,-105, 0, -1, -1,-101, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -99, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -95, 0, -91, 0, -1, -1, -87, 0, -1, -1, -1, -1, -1, -1, -83, 0, -1, -1, -1, -1, -1, -1, -79, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -75, 0, -1, -1, -70, 0, -61, 0, -52, 0, -43, 0, -34, 0, -25, 0, -16, 0, -7, 0, 2, 1, 11, 1, -1, -1, -1, -1, -1, -1, -1, -1, 20, 1, 25, 1, 30, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 50, 1, -1, -1, 61, 1, -1, -1, 63, 1,-107, 1, -1, -1,-104, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,-100, 1, -1, -1, -37, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -33, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -28, 1, -17, 1, -12, 1, 7, 2, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 20, 2, 30, 2, -1, -1, -1, -1, -1, -1, 40, 2, 44, 2, 48, 2, 52, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56, 2, 62, 2, 27, 91, 90, 0, 7, 0, 13, 0, 27, 91, 51, 103, 0, 27, 91, 72, 27, 91, 74, 0, 27, 91, 75, 0, 27, 91, 74, 0, 27, 91, 37, 105, 37, 112, 49, 37, 100, 71, 0, 27, 91, 37, 105, 37, 112, 49, 37, 100, 59, 37, 112, 50, 37, 100, 72, 0, 27, 91, 66, 0, 27, 91, 72, 0, 27, 91, 68, 0, 27, 91, 67, 0, 27, 91, 65, 0, 27, 91, 80, 0, 27, 91, 77, 0, 27, 91, 49, 49, 109, 0, 27, 91, 53, 109, 0, 27, 91, 49, 109, 0, 27, 91, 56, 109, 0, 27, 91, 55, 109, 0, 27, 91, 55, 109, 0, 27, 91, 52, 109, 0, 27, 91, 37, 112, 49, 37, 100, 88, 0, 27, 91, 49, 48, 109, 0, 27, 91, 48, 59, 49, 48, 109, 0, 27, 91, 109, 0, 27, 91, 109, 0, 27, 91, 76, 0, 8, 0, 27, 91, 66, 0, 27, 91, 72, 0, 27, 91, 76, 0, 27, 91, 68, 0, 27, 91, 67, 0, 27, 91, 65, 0, 13, 27, 91, 83, 0, 27, 91, 37, 112, 49, 37, 100, 80, 0, 27, 91, 37, 112, 49, 37, 100, 77, 0, 27, 91, 37, 112, 49, 37, 100, 66, 0, 27, 91, 37, 112, 49, 37, 100, 64, 0, 27, 91, 37, 112, 49, 37, 100, 83, 0, 27, 91, 37, 112, 49, 37, 100, 76, 0, 27, 91, 37, 112, 49, 37, 100, 68, 0, 27, 91, 37, 112, 49, 37, 100, 67, 0, 27, 91, 37, 112, 49, 37, 100, 84, 0, 27, 91, 37, 112, 49, 37, 100, 65, 0, 27, 91, 52, 105, 0, 27, 91, 53, 105, 0, 37, 112, 49, 37, 99, 27, 91, 37, 112, 50, 37, 123, 49, 125, 37, 45, 37, 100, 98, 0, 27, 91, 37, 105, 37, 112, 49, 37, 100, 100, 0, 10, 0, 27, 91, 48, 59, 49, 48, 37, 63, 37, 112, 49, 37, 116, 59, 55, 37, 59, 37, 63, 37, 112, 50, 37, 116, 59, 52, 37, 59, 37, 63, 37, 112, 51, 37, 116, 59, 55, 37, 59, 37, 63, 37, 112, 52, 37, 116, 59, 53, 37, 59, 37, 63, 37, 112, 54, 37, 116, 59, 49, 37, 59, 37, 63, 37, 112, 55, 37, 116, 59, 56, 37, 59, 37, 63, 37, 112, 57, 37, 116, 59, 49, 49, 37, 59, 109, 0, 27, 72, 0, 27, 91, 73, 0, 43, 16, 44, 17, 45, 24, 46, 25, 48, -37, 96, 4, 97, -79, 102, -8, 103, -15, 104, -80, 106, -39, 107, -65, 108, -38, 109, -64, 110, -59, 111, 126, 112, -60, 113, -60, 114, -60, 115, 95, 116, -61, 117, -76, 118, -63, 119, -62, 120, -77, 121, -13, 122, -14, 123, -29, 124, -40, 125,-100, 126, -2, 0, 27, 91, 90, 0, 27, 91, 49, 75, 0, 27, 91, 37, 105, 37, 100, 59, 37, 100, 82, 0, 27, 91, 54, 110, 0, 27, 91, 63, 37, 91, 59, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 93, 99, 0, 27, 91, 99, 0, 27, 91, 51, 57, 59, 52, 57, 109, 0, 27, 91, 51, 37, 112, 49, 37, 100, 109, 0, 27, 91, 52, 37, 112, 49, 37, 100, 109, 0, 27, 40, 66, 0, 27, 41, 66, 0, 27, 42, 66, 0, 27, 43, 66, 0, 27, 91, 49, 49, 109, 0, 27, 91, 49, 48, 109, 0 }; +// Per the commentary in terminfo, only a minus sign is a true suffix +// separator. +bool terminfo_is_term_family(const char *term, const char *family) +{ + if (!term) { + return false; + } + size_t tlen = strlen(term); + size_t flen = strlen(family); + return tlen >= flen + && 0 == memcmp(term, family, flen) \ + && ('\0' == term[flen] || '-' == term[flen]); +} + /// Load one of the built-in terminfo entries when unibilium has failed to /// load a terminfo record from an external database, as it does on termcap- /// -only systems. We do not do any fancy recognition of xterm pretenders diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b447b4aae2..7ff426239a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -61,20 +61,6 @@ #define UNIBI_SET_NUM_VAR(var, num) (var).i = (num); #endif -// Per the commentary in terminfo, only a minus sign is a true suffix -// separator. -bool terminfo_is_term_family(const char *term, const char *family) -{ - if (!term) { - return false; - } - size_t tlen = strlen(term); - size_t flen = strlen(family); - return tlen >= flen - && 0 == memcmp(term, family, flen) \ - && ('\0' == term[flen] || '-' == term[flen]); -} - typedef struct { int top, bot, left, right; } Rect; -- cgit From d9b3ebfede6439aab4148c4bbf117b8950ed6aa0 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 23 Nov 2017 07:36:35 +0100 Subject: FIXUP: duplicate error number in #7422 --- src/nvim/globals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 2b025c2c50..1ff0f7eb89 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1168,7 +1168,7 @@ EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN char_u e_autocmd_err[] INIT(=N_( - "E920: autocmd has thrown an exception: %s")); + "E5500: autocmd has thrown an exception: %s")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); -- cgit From 9888a54f15d8b737bdc8c18406d7fccb0186a0b1 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 23 Nov 2017 02:21:04 +0100 Subject: tui: Disable BCE by default. #7624 133ae5eeeff3 implemented BCE (background color erase). That's fine if the system terminfo claims to support it; but our built-in fallback should not assume it. Per https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545 terminal support for BCE seems to be (1) optional and (2) inconsistent. So the built-in terminfos should disable it by default. ref #4210 #4421 #7035 #7337 #7381 #7425 #7618 --- src/nvim/tui/terminfo.c | 23 +++++++++++++++-------- src/nvim/tui/tui.c | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 6b0be50917..75e9a2d8da 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -8,6 +8,7 @@ #include +#include "nvim/log.h" #include "nvim/tui/terminfo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -77,8 +78,6 @@ static const signed char ansi_terminfo[] = { 26, 1, 40, 0, 23, 0, 16, 0, 125, 1, 68, 2, 97, 110, 115, 105, 124, 97, 110, 115, 105, 47, 112, 99, 45, 116, 101, 114, 109, 32, 99, 111, 109, 112, 97, 116, 105, 98, 108, 101, 32, 119, 105, 116, 104, 32, 99, 111, 108, 111, 114, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 80, 0, 8, 0, 24, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 0, 64, 0, 3, 0, 0, 0, 4, 0, 6, 0, -1, -1, 8, 0, 13, 0, 20, 0, 24, 0, 28, 0, -1, -1, 39, 0, 56, 0, 60, 0, -1, -1, 64, 0, -1, -1, -1, -1, 68, 0, -1, -1, 72, 0, -1, -1, 76, 0, 80, 0, -1, -1, -1, -1, 84, 0, 90, 0, 95, 0, -1, -1, -1, -1, -1, -1, -1, -1, 100, 0, -1, -1, 105, 0, 110, 0, 115, 0, 120, 0,-127, 0,-121, 0, -1, -1, -1, -1, -1, -1,-113, 0,-109, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,-105, 0, -1, -1,-101, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -99, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -95, 0, -91, 0, -1, -1, -87, 0, -1, -1, -1, -1, -1, -1, -83, 0, -1, -1, -1, -1, -1, -1, -79, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -75, 0, -1, -1, -70, 0, -61, 0, -52, 0, -43, 0, -34, 0, -25, 0, -16, 0, -7, 0, 2, 1, 11, 1, -1, -1, -1, -1, -1, -1, -1, -1, 20, 1, 25, 1, 30, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 50, 1, -1, -1, 61, 1, -1, -1, 63, 1,-107, 1, -1, -1,-104, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,-100, 1, -1, -1, -37, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -33, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -28, 1, -17, 1, -12, 1, 7, 2, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 20, 2, 30, 2, -1, -1, -1, -1, -1, -1, 40, 2, 44, 2, 48, 2, 52, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56, 2, 62, 2, 27, 91, 90, 0, 7, 0, 13, 0, 27, 91, 51, 103, 0, 27, 91, 72, 27, 91, 74, 0, 27, 91, 75, 0, 27, 91, 74, 0, 27, 91, 37, 105, 37, 112, 49, 37, 100, 71, 0, 27, 91, 37, 105, 37, 112, 49, 37, 100, 59, 37, 112, 50, 37, 100, 72, 0, 27, 91, 66, 0, 27, 91, 72, 0, 27, 91, 68, 0, 27, 91, 67, 0, 27, 91, 65, 0, 27, 91, 80, 0, 27, 91, 77, 0, 27, 91, 49, 49, 109, 0, 27, 91, 53, 109, 0, 27, 91, 49, 109, 0, 27, 91, 56, 109, 0, 27, 91, 55, 109, 0, 27, 91, 55, 109, 0, 27, 91, 52, 109, 0, 27, 91, 37, 112, 49, 37, 100, 88, 0, 27, 91, 49, 48, 109, 0, 27, 91, 48, 59, 49, 48, 109, 0, 27, 91, 109, 0, 27, 91, 109, 0, 27, 91, 76, 0, 8, 0, 27, 91, 66, 0, 27, 91, 72, 0, 27, 91, 76, 0, 27, 91, 68, 0, 27, 91, 67, 0, 27, 91, 65, 0, 13, 27, 91, 83, 0, 27, 91, 37, 112, 49, 37, 100, 80, 0, 27, 91, 37, 112, 49, 37, 100, 77, 0, 27, 91, 37, 112, 49, 37, 100, 66, 0, 27, 91, 37, 112, 49, 37, 100, 64, 0, 27, 91, 37, 112, 49, 37, 100, 83, 0, 27, 91, 37, 112, 49, 37, 100, 76, 0, 27, 91, 37, 112, 49, 37, 100, 68, 0, 27, 91, 37, 112, 49, 37, 100, 67, 0, 27, 91, 37, 112, 49, 37, 100, 84, 0, 27, 91, 37, 112, 49, 37, 100, 65, 0, 27, 91, 52, 105, 0, 27, 91, 53, 105, 0, 37, 112, 49, 37, 99, 27, 91, 37, 112, 50, 37, 123, 49, 125, 37, 45, 37, 100, 98, 0, 27, 91, 37, 105, 37, 112, 49, 37, 100, 100, 0, 10, 0, 27, 91, 48, 59, 49, 48, 37, 63, 37, 112, 49, 37, 116, 59, 55, 37, 59, 37, 63, 37, 112, 50, 37, 116, 59, 52, 37, 59, 37, 63, 37, 112, 51, 37, 116, 59, 55, 37, 59, 37, 63, 37, 112, 52, 37, 116, 59, 53, 37, 59, 37, 63, 37, 112, 54, 37, 116, 59, 49, 37, 59, 37, 63, 37, 112, 55, 37, 116, 59, 56, 37, 59, 37, 63, 37, 112, 57, 37, 116, 59, 49, 49, 37, 59, 109, 0, 27, 72, 0, 27, 91, 73, 0, 43, 16, 44, 17, 45, 24, 46, 25, 48, -37, 96, 4, 97, -79, 102, -8, 103, -15, 104, -80, 106, -39, 107, -65, 108, -38, 109, -64, 110, -59, 111, 126, 112, -60, 113, -60, 114, -60, 115, 95, 116, -61, 117, -76, 118, -63, 119, -62, 120, -77, 121, -13, 122, -14, 123, -29, 124, -40, 125,-100, 126, -2, 0, 27, 91, 90, 0, 27, 91, 49, 75, 0, 27, 91, 37, 105, 37, 100, 59, 37, 100, 82, 0, 27, 91, 54, 110, 0, 27, 91, 63, 37, 91, 59, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 93, 99, 0, 27, 91, 99, 0, 27, 91, 51, 57, 59, 52, 57, 109, 0, 27, 91, 51, 37, 112, 49, 37, 100, 109, 0, 27, 91, 52, 37, 112, 49, 37, 100, 109, 0, 27, 40, 66, 0, 27, 41, 66, 0, 27, 42, 66, 0, 27, 43, 66, 0, 27, 91, 49, 49, 109, 0, 27, 91, 49, 48, 109, 0 }; -// Per the commentary in terminfo, only a minus sign is a true suffix -// separator. bool terminfo_is_term_family(const char *term, const char *family) { if (!term) { @@ -88,15 +87,14 @@ bool terminfo_is_term_family(const char *term, const char *family) size_t flen = strlen(family); return tlen >= flen && 0 == memcmp(term, family, flen) \ + // Per the commentary in terminfo, minus sign is the suffix separator. && ('\0' == term[flen] || '-' == term[flen]); } -/// Load one of the built-in terminfo entries when unibilium has failed to -/// load a terminfo record from an external database, as it does on termcap- -/// -only systems. We do not do any fancy recognition of xterm pretenders -/// here. An external terminfo database would not do that, and we want to -/// behave as much like an external terminfo database as possible. -unibi_term *load_builtin_terminfo(const char * term) +/// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo +/// record from the environment (termcap systems, unrecognized $TERM, …). +/// We do not attempt to detect xterm pretenders here. +static unibi_term *terminfo_builtin(const char *term) { if (terminfo_is_term_family(term, "xterm")) { return unibi_from_mem((const char *)xterm_256colour_terminfo, @@ -137,3 +135,12 @@ unibi_term *load_builtin_terminfo(const char * term) sizeof ansi_terminfo); } } + +unibi_term *terminfo_from_builtin(const char *term) +{ + unibi_term *ut = terminfo_builtin(term); + // Disable BCE by default (for built-in terminfos). #7624 + // https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545 + unibi_set_bool(ut, unibi_back_color_erase, false); + return ut; +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 7ff426239a..3945d400f3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -194,7 +194,7 @@ static void terminfo_start(UI *ui) const char *term = os_getenv("TERM"); data->ut = unibi_from_env(); if (!data->ut) { - data->ut = load_builtin_terminfo(term); + data->ut = terminfo_from_builtin(term); } // None of the following work over SSH; see :help TERM . const char *colorterm = os_getenv("COLORTERM"); -- cgit From b838ad5b7a2cfeb72a34a534eb4f10787c4c6c8f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 23 Nov 2017 21:21:05 +0100 Subject: tui: Disable BCE almost always. #7624 133ae5eeeff3 implemented BCE (background color erase). But we can't trust terminfo, so it is safer disable BCE if we are not certain. Per https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545 terminal support for BCE seems to be (1) optional and (2) inconsistent. ref #4210 #4421 #7035 #7337 #7381 #7425 #7618 --- src/nvim/tui/tui.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 3945d400f3..302354e826 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1234,11 +1234,9 @@ static int unibi_find_ext_str(unibi_term *ut, const char *name) return -1; } -/// Several entries in terminfo are known to be deficient or outright wrong, -/// unfortunately; and several terminal emulators falsely announce incorrect -/// terminal types. So patch the terminfo records after loading from an -/// external or a built-in database. In an ideal world, the real terminfo data -/// would be correct and complete, and this function would be almost empty. +/// Patches the terminfo records after loading from system or built-in db. +/// Several entries in terminfo are known to be deficient or outright wrong; +/// and several terminal emulators falsely announce incorrect terminal types. static void patch_terminfo_bugs(TUIData *data, const char *term, const char *colorterm, long vte_version, bool konsole, bool iterm_env) @@ -1304,6 +1302,11 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } + if (!true_xterm) { + // Cannot trust terminfo; safer to disable BCE. #7624 + unibi_set_bool(ut, unibi_back_color_erase, false); + } + if (xterm) { // Termit, LXTerminal, GTKTerm2, GNOME Terminal, MATE Terminal, roxterm, // and EvilVTE falsely claim to be xterm and do not support important xterm @@ -1397,11 +1400,9 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - // Terminals where there is actually 256-colour SGR support despite what - // the terminfo record may say. + // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { - // See http://fedoraproject.org/wiki/Features/256_Color_Terminals for - // more on this. + // See http://fedoraproject.org/wiki/Features/256_Color_Terminals if (true_xterm || iterm || iterm_pretending_xterm) { unibi_set_num(ut, unibi_max_colors, 256); unibi_set_str(ut, unibi_set_a_foreground, XTERM_SETAF_256_COLON); @@ -1417,8 +1418,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB_256); } } - // Terminals where there is actually 16-colour SGR support despite what - // the terminfo record may say. + // Terminals with 16-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 16) { if (colorterm) { unibi_set_num(ut, unibi_max_colors, 16); @@ -1427,9 +1427,8 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } - // Some terminals can not currently be trusted to report if they support - // DECSCUSR or not. So we need to have a blacklist for when we should not - // trust the reported features. + // Some terminals cannot be trusted to report DECSCUSR support. So we keep + // blacklist for when we should not trust the reported features. if (!((vte_version != 0 && vte_version < 3900) || konsole)) { // Dickey ncurses terminfo has included the Ss and Se capabilities, // pioneered by tmux, since 2011-07-14. So adding them to terminal types, -- cgit From 0b93bab6c22edf7a07cf965ebbbf631b93e1dc1b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 24 Nov 2017 09:53:09 +0100 Subject: tui: update cleared area only if non-default bg This check was removed in 133ae5eeeff3 without explanation. --- src/nvim/tui/tui.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 302354e826..c2e597c36c 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -940,9 +940,10 @@ static void tui_scroll(UI *ui, Integer count) } cursor_goto(ui, saved_row, saved_col); - if (!scroll_clears_to_current_colour) { - // This is required because scrolling will leave wrong background in the - // cleared area on non-bge terminals. + if (!scroll_clears_to_current_colour && grid->bg != -1) { + // Scrolling may leave wrong background in the cleared area on non-bge + // terminals. Update the cleared area of the terminal if its builtin + // scrolling facility was used and bg color is not the default. clear_region(ui, clear_top, clear_bot, grid->left, grid->right); } } else { -- cgit From 3717e2157f2d45ce23dbe4ac03085fea2d956dc4 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 23 Jul 2017 18:04:14 +0200 Subject: Revert channel logging, rebased on new code below --- src/nvim/eval.c | 9 +++------ src/nvim/msgpack_rpc/channel.c | 43 ++++++++++++++---------------------------- 2 files changed, 17 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f414e771d7..b8bae3e293 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -15168,8 +15168,7 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } const char *error = NULL; - eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); - uint64_t id = channel_connect(tcp, address, 50, (char *)IObuff, &error); + uint64_t id = channel_connect(tcp, address, 50, &error); if (error) { EMSG2(_("connection failed: %s"), error); @@ -22488,9 +22487,8 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) if (data->rpc) { - eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); - // RPC channel takes over the in/out streams. - channel_from_process(proc, data->id, (char *)IObuff); + // the rpc channel takes over the in and out streams + channel_from_process(proc, data->id); } else { wstream_init(proc->in, 0); if (proc->out) { @@ -22855,4 +22853,3 @@ void ex_checkhealth(exarg_T *eap) xfree(buf); } - diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 5efdb9a194..18f6334780 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -115,15 +115,12 @@ void channel_teardown(void) /// Creates an API channel by starting a process and connecting to its /// stdin/stdout. stderr is handled by the job infrastructure. /// -/// @param proc process object -/// @param id (optional) channel id -/// @param source description of source function, rplugin name, TCP addr, etc -/// -/// @return Channel id (> 0), on success. 0, on error. -uint64_t channel_from_process(Process *proc, uint64_t id, char *source) +/// @param argv The argument vector for the process. [consumed] +/// @return The channel id (> 0), on success. +/// 0, on error. +uint64_t channel_from_process(Process *proc, uint64_t id) { - Channel *channel = register_channel(kChannelTypeProc, id, proc->events, - source); + Channel *channel = register_channel(kChannelTypeProc, id, proc->events); incref(channel); // process channels are only closed by the exit_cb channel->data.proc = proc; @@ -142,8 +139,7 @@ uint64_t channel_from_process(Process *proc, uint64_t id, char *source) /// @param watcher The SocketWatcher ready to accept the connection void channel_from_connection(SocketWatcher *watcher) { - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, - watcher->addr); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); socket_watcher_accept(watcher, &channel->data.stream); incref(channel); // close channel only after the stream is closed channel->data.stream.internal_close_cb = close_cb; @@ -156,9 +152,8 @@ void channel_from_connection(SocketWatcher *watcher) &channel->data.stream); } -/// @param source description of source function, rplugin name, TCP addr, etc -uint64_t channel_connect(bool tcp, const char *address, int timeout, - char *source, const char **error) +uint64_t channel_connect(bool tcp, const char *address, + int timeout, const char **error) { if (!tcp) { char *path = fix_fname(address); @@ -170,7 +165,7 @@ uint64_t channel_connect(bool tcp, const char *address, int timeout, xfree(path); } - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, source); + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); if (!socket_connect(&main_loop, &channel->data.stream, tcp, address, timeout, error)) { decref(channel); @@ -323,10 +318,11 @@ bool channel_close(uint64_t id) return true; } -/// Creates an API channel from stdin/stdout. Used to embed Nvim. +/// Creates an API channel from stdin/stdout. This is used when embedding +/// Neovim void channel_from_stdio(void) { - Channel *channel = register_channel(kChannelTypeStdio, 0, NULL, NULL); + Channel *channel = register_channel(kChannelTypeStdio, 0, NULL); incref(channel); // stdio channels are only closed on exit // read stream rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); @@ -342,7 +338,7 @@ void channel_from_stdio(void) /// when an instance connects to its own named pipe. uint64_t channel_create_internal(void) { - Channel *channel = register_channel(kChannelTypeInternal, 0, NULL, NULL); + Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); incref(channel); // internal channel lives until process exit return channel->id; } @@ -778,12 +774,9 @@ static void close_cb(Stream *stream, void *data) decref(data); } -/// @param source description of source function, rplugin name, TCP addr, etc static Channel *register_channel(ChannelType type, uint64_t id, - MultiQueue *events, char *source) + MultiQueue *events) { - // Jobs and channels share the same id namespace. - assert(id == 0 || !pmap_get(uint64_t)(channels, id)); Channel *rv = xmalloc(sizeof(Channel)); rv->events = events ? events : multiqueue_new_child(main_loop.events); rv->type = type; @@ -795,14 +788,6 @@ static Channel *register_channel(ChannelType type, uint64_t id, rv->next_request_id = 1; kv_init(rv->call_stack); pmap_put(uint64_t)(channels, rv->id, rv); - - ILOG("new channel %" PRIu64 " (%s): %s", rv->id, - (type == kChannelTypeProc ? "proc" - : (type == kChannelTypeSocket ? "socket" - : (type == kChannelTypeStdio ? "stdio" - : (type == kChannelTypeInternal ? "internal" : "?")))), - (source ? source : "?")); - return rv; } -- cgit From 5215e3205a07b85e4e4cf1f8a8ca6be2b9556459 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 27 Aug 2017 11:59:33 +0200 Subject: channels: refactor --- src/nvim/api/ui.c | 2 +- src/nvim/api/vim.c | 4 +- src/nvim/channel.c | 61 ++++++ src/nvim/channel.h | 120 +++++++++++ src/nvim/eval.c | 314 +++++++++++------------------ src/nvim/eval/typval.c | 24 +++ src/nvim/event/libuv_process.c | 12 +- src/nvim/event/process.c | 77 +++---- src/nvim/event/process.h | 15 +- src/nvim/event/stream.h | 2 +- src/nvim/globals.h | 7 +- src/nvim/msgpack_rpc/channel.c | 388 ++++++++++++------------------------ src/nvim/msgpack_rpc/channel.h | 2 + src/nvim/msgpack_rpc/channel_defs.h | 36 ++++ src/nvim/os/pty_process_unix.c | 10 +- src/nvim/os/pty_process_win.c | 12 +- src/nvim/os/shell.c | 26 +-- 17 files changed, 580 insertions(+), 532 deletions(-) create mode 100644 src/nvim/channel.c create mode 100644 src/nvim/channel.h create mode 100644 src/nvim/msgpack_rpc/channel_defs.h (limited to 'src') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index afbee09c1c..a9eaccfac5 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -252,7 +252,7 @@ static void remote_ui_flush(UI *ui) { UIData *data = ui->data; if (data->buffer.size > 0) { - channel_send_event(data->channel_id, "redraw", data->buffer); + rpc_send_event(data->channel_id, "redraw", data->buffer); data->buffer = (Array)ARRAY_DICT_INIT; } } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d2b0e329c9..f4ccf07bec 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -721,7 +721,7 @@ void nvim_subscribe(uint64_t channel_id, String event) char e[METHOD_MAXLEN + 1]; memcpy(e, event.data, length); e[length] = NUL; - channel_subscribe(channel_id, e); + rpc_subscribe(channel_id, e); } /// Unsubscribes to event broadcasts @@ -737,7 +737,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event) char e[METHOD_MAXLEN + 1]; memcpy(e, event.data, length); e[length] = NUL; - channel_unsubscribe(channel_id, e); + rpc_unsubscribe(channel_id, e); } Integer nvim_get_color_by_name(String name) diff --git a/src/nvim/channel.c b/src/nvim/channel.c new file mode 100644 index 0000000000..e61ec9c19b --- /dev/null +++ b/src/nvim/channel.c @@ -0,0 +1,61 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/api/ui.h" +#include "nvim/channel.h" +#include "nvim/msgpack_rpc/channel.h" + +PMap(uint64_t) *channels = NULL; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "channel.c.generated.h" +#endif +/// Teardown the module +void channel_teardown(void) +{ + if (!channels) { + return; + } + + Channel *channel; + + map_foreach_value(channels, channel, { + (void)channel; // close_channel(channel); + }); +} + +/// Initializes the module +void channel_init(void) +{ + channels = pmap_new(uint64_t)(); + rpc_init(); + remote_ui_init(); +} + +void channel_incref(Channel *channel) +{ + channel->refcount++; +} + +void channel_decref(Channel *channel) +{ + if (!(--channel->refcount)) { + multiqueue_put(main_loop.fast_events, free_channel_event, 1, channel); + } +} + +static void free_channel_event(void **argv) +{ + Channel *channel = argv[0]; + if (channel->is_rpc) { + rpc_free(channel); + } + + callback_free(&channel->on_stdout); + callback_free(&channel->on_stderr); + callback_free(&channel->on_exit); + + pmap_del(uint64_t)(channels, channel->id); + multiqueue_free(channel->events); + xfree(channel); +} diff --git a/src/nvim/channel.h b/src/nvim/channel.h new file mode 100644 index 0000000000..b48f508722 --- /dev/null +++ b/src/nvim/channel.h @@ -0,0 +1,120 @@ +#ifndef NVIM_CHANNEL_H +#define NVIM_CHANNEL_H + +#include "nvim/main.h" +#include "nvim/event/socket.h" +#include "nvim/event/process.h" +#include "nvim/os/pty_process.h" +#include "nvim/event/libuv_process.h" +#include "nvim/eval/typval.h" +#include "nvim/msgpack_rpc/channel_defs.h" + +typedef enum { + kChannelStreamProc, + kChannelStreamSocket, + kChannelStreamStdio, + kChannelStreamInternal +} ChannelStreamType; + +typedef struct { + Stream in; + Stream out; +} StdioPair; + +// typedef struct { +// Callback on_out; +// Callback on_close; +// Garray buffer; +// bool buffering; +// } CallbackReader + +#define CallbackReader Callback + +struct Channel { + uint64_t id; + size_t refcount; + MultiQueue *events; + + ChannelStreamType streamtype; + union { + Process proc; + LibuvProcess uv; + PtyProcess pty; + Stream socket; + StdioPair stdio; + } stream; + + bool is_rpc; + RpcState rpc; + Terminal *term; + + CallbackReader on_stdout; + CallbackReader on_stderr; + Callback on_exit; + + varnumber_T *status_ptr; // TODO: refactor? +}; + +EXTERN PMap(uint64_t) *channels; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "channel.h.generated.h" +#endif + +static inline Channel *channel_alloc(ChannelStreamType type) +{ + Channel *chan = xcalloc(1, sizeof(*chan)); + chan->id = next_chan_id++; + chan->events = multiqueue_new_child(main_loop.events); + chan->refcount = 1; + chan->streamtype = type; + pmap_put(uint64_t)(channels, chan->id, chan); + return chan; +} + +/// @returns Channel with the id or NULL if not found +static inline Channel *find_channel(uint64_t id) +{ + return pmap_get(uint64_t)(channels, id); +} + +static inline Stream *channel_instream(Channel *chan) + FUNC_ATTR_NONNULL_ALL +{ + switch (chan->streamtype) { + case kChannelStreamProc: + return &chan->stream.proc.in; + + case kChannelStreamSocket: + return &chan->stream.socket; + + case kChannelStreamStdio: + return &chan->stream.stdio.in; + + case kChannelStreamInternal: + abort(); + } + abort(); +} + +static inline Stream *channel_outstream(Channel *chan) + FUNC_ATTR_NONNULL_ALL +{ + switch (chan->streamtype) { + case kChannelStreamProc: + return &chan->stream.proc.out; + + case kChannelStreamSocket: + return &chan->stream.socket; + + case kChannelStreamStdio: + return &chan->stream.stdio.out; + + case kChannelStreamInternal: + abort(); + } + abort(); +} + + +#endif // NVIM_CHANNEL_H diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b8bae3e293..013cfce78d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -24,6 +24,7 @@ #endif #include "nvim/eval.h" #include "nvim/buffer.h" +#include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -437,29 +438,12 @@ static ScopeDictDictItem vimvars_var; #define vimvarht vimvardict.dv_hashtab typedef struct { - union { - LibuvProcess uv; - PtyProcess pty; - } proc; - Stream in, out, err; // Initialized in common_job_start(). - Terminal *term; - bool stopped; - bool exited; - bool rpc; - int refcount; - Callback on_stdout, on_stderr, on_exit; - varnumber_T *status_ptr; - uint64_t id; - MultiQueue *events; -} TerminalJobData; - -typedef struct { - TerminalJobData *data; + Channel *data; Callback *callback; const char *type; list_T *received; int status; -} JobEvent; +} ChannelEvent; typedef struct { TimeWatcher tw; @@ -513,7 +497,6 @@ typedef enum { #define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ #define FNE_CHECK_START 2 /* find_name_end(): check name starts with valid character */ -static PMap(uint64_t) *jobs = NULL; static uint64_t last_timer_id = 0; static PMap(uint64_t) *timers = NULL; @@ -556,7 +539,6 @@ void eval_init(void) { vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; - jobs = pmap_new(uint64_t)(); timers = pmap_new(uint64_t)(); struct vimvar *p; @@ -5141,8 +5123,8 @@ bool garbage_collect(bool testing) // Jobs { - TerminalJobData *data; - map_foreach_value(jobs, data, { + Channel *data; + map_foreach_value(channels, data, { set_ref_in_callback(&data->on_stdout, copyID, NULL, NULL); set_ref_in_callback(&data->on_stderr, copyID, NULL, NULL); set_ref_in_callback(&data->on_exit, copyID, NULL, NULL); @@ -11433,24 +11415,23 @@ static void f_jobclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - TerminalJobData *data = find_job(argvars[0].vval.v_number); + Channel *data = find_job(argvars[0].vval.v_number, true); if (!data) { - EMSG(_(e_invjob)); return; } - Process *proc = (Process *)&data->proc; + Process *proc = (Process *)&data->stream.proc; if (argvars[1].v_type == VAR_STRING) { char *stream = (char *)argvars[1].vval.v_string; if (!strcmp(stream, "stdin")) { - if (data->rpc) { + if (data->is_rpc) { EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); } else { process_close_in(proc); } } else if (!strcmp(stream, "stdout")) { - if (data->rpc) { + if (data->is_rpc) { EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); } else { process_close_out(proc); @@ -11458,7 +11439,7 @@ static void f_jobclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (!strcmp(stream, "stderr")) { process_close_err(proc); } else if (!strcmp(stream, "rpc")) { - if (data->rpc) { + if (data->is_rpc) { channel_close(data->id); } else { EMSG(_("Invalid job stream: Not an rpc job")); @@ -11467,13 +11448,13 @@ static void f_jobclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_("Invalid job stream \"%s\""), stream); } } else { - if (data->rpc) { + if (data->is_rpc) { channel_close(data->id); process_close_err(proc); } else { process_close_streams(proc); if (proc->type == kProcessTypePty) { - pty_process_close_master(&data->proc.pty); + pty_process_close_master(&data->stream.pty); } } } @@ -11494,13 +11475,12 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - TerminalJobData *data = find_job(argvars[0].vval.v_number); + Channel *data = find_job(argvars[0].vval.v_number, true); if (!data) { - EMSG(_(e_invjob)); return; } - Process *proc = (Process *)&data->proc; + Process *proc = (Process *)&data->stream.proc; rettv->vval.v_number = proc->pid; } @@ -11521,18 +11501,19 @@ static void f_jobsend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - TerminalJobData *data = find_job(argvars[0].vval.v_number); + Channel *data = find_channel(argvars[0].vval.v_number); if (!data) { - EMSG(_(e_invjob)); + EMSG(_(e_invchan)); return; } - if (((Process *)&data->proc)->in->closed) { + Stream *in = channel_instream(data); + if (in->closed) { EMSG(_("Can't send data to the job: stdin is closed")); return; } - if (data->rpc) { + if (data->is_rpc) { EMSG(_("Can't send raw data to rpc channel")); return; } @@ -11546,7 +11527,7 @@ static void f_jobsend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } WBuffer *buf = wstream_new_buffer(input, input_len, 1, xfree); - rettv->vval.v_number = wstream_write(data->proc.uv.process.in, buf); + rettv->vval.v_number = wstream_write(in, buf); } // "jobresize(job, width, height)" function @@ -11567,19 +11548,17 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } - TerminalJobData *data = find_job(argvars[0].vval.v_number); + Channel *data = find_job(argvars[0].vval.v_number, true); if (!data) { - EMSG(_(e_invjob)); return; } - if (data->proc.uv.process.type != kProcessTypePty) { - EMSG(_(e_jobnotpty)); + if (data->stream.proc.type != kProcessTypePty) { + EMSG(_(e_channotpty)); return; } - pty_process_resize(&data->proc.pty, argvars[1].vval.v_number, - argvars[2].vval.v_number); + pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, argvars[2].vval.v_number); rettv->vval.v_number = 1; } @@ -11697,31 +11676,25 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, + Channel *data = common_job_init(argv, on_stdout, on_stderr, on_exit, pty, rpc, detach, cwd); - Process *proc = (Process *)&data->proc; if (pty) { + PtyProcess *pty = &data->stream.pty; uint16_t width = (uint16_t)tv_dict_get_number(job_opts, "width"); if (width > 0) { - data->proc.pty.width = width; + pty->width = width; } uint16_t height = (uint16_t)tv_dict_get_number(job_opts, "height"); if (height > 0) { - data->proc.pty.height = height; + pty->height = height; } char *term = tv_dict_get_string(job_opts, "TERM", true); if (term) { - data->proc.pty.term_name = term; + pty->term_name = term; } } - if (!rpc && on_stdout.type == kCallbackNone) { - proc->out = NULL; - } - if (on_stderr.type == kCallbackNone) { - proc->err = NULL; - } common_job_start(data, rettv); } @@ -11742,14 +11715,12 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } - TerminalJobData *data = find_job(argvars[0].vval.v_number); + Channel *data = find_job(argvars[0].vval.v_number, true); if (!data) { - EMSG(_(e_invjob)); return; } - process_stop((Process *)&data->proc); - data->stopped = true; + process_stop((Process *)&data->stream.proc); rettv->vval.v_number = 1; } @@ -11778,9 +11749,9 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) // is used to represent an invalid job id, -2 is for a interrupted job and // -1 for jobs that were skipped or timed out. for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - TerminalJobData *data = NULL; + Channel *data = NULL; if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number, false))) { tv_list_append_number(rv, -3); } else { // append the list item and set the status pointer so we'll collect the @@ -11802,16 +11773,16 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - TerminalJobData *data = NULL; + Channel *data = NULL; if (remaining == 0) { // timed out break; } if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number, false))) { continue; } - int status = process_wait((Process *)&data->proc, remaining, waiting_jobs); + int status = process_wait((Process *)&data->stream.proc, remaining, waiting_jobs); if (status < 0) { // interrupted or timed out, skip remaining jobs. if (status == -2) { @@ -11832,9 +11803,9 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - TerminalJobData *data = NULL; + Channel *data = NULL; if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number, false))) { continue; } // remove the status pointer because the list may be freed before the @@ -11844,9 +11815,9 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) // restore the parent queue for any jobs still alive for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - TerminalJobData *data = NULL; + Channel *data = NULL; if (arg->li_tv.v_type != VAR_NUMBER - || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number, false))) { continue; } // restore the parent queue for the job @@ -13803,9 +13774,8 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) ADD(args, vim_to_object(tv)); } - if (!channel_send_event((uint64_t)argvars[0].vval.v_number, - tv_get_string(&argvars[1]), - args)) { + if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, + tv_get_string(&argvars[1]), args)) { EMSG2(_(e_invarg2), "Channel doesn't exist"); return; } @@ -13870,10 +13840,8 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) Error err = ERROR_INIT; - Object result = channel_send_call((uint64_t)argvars[0].vval.v_number, - tv_get_string(&argvars[1]), - args, - &err); + Object result = rpc_send_call((uint64_t)argvars[0].vval.v_number, + tv_get_string(&argvars[1]), args, &err); if (l_provider_call_nesting) { current_SID = save_current_SID; @@ -13954,7 +13922,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) // The last item of argv must be NULL argv[i] = NULL; - TerminalJobData *data = common_job_init(argv, CALLBACK_NONE, CALLBACK_NONE, + Channel *data = common_job_init(argv, CALLBACK_NONE, CALLBACK_NONE, CALLBACK_NONE, false, true, false, NULL); common_job_start(data, rettv); @@ -13977,10 +13945,11 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // if called with a job, stop it, else closes the channel - if (pmap_get(uint64_t)(jobs, argvars[0].vval.v_number)) { + uint64_t id = argvars[0].vval.v_number; + if (find_job(id, false)) { // FIXME f_jobstop(argvars, rettv, NULL); } else { - rettv->vval.v_number = channel_close(argvars[0].vval.v_number); + rettv->vval.v_number = channel_close(id); } } @@ -16689,11 +16658,11 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } uint16_t term_width = MAX(0, curwin->w_width - win_col_off(curwin)); - TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit, + Channel *data = common_job_init(argv, on_stdout, on_stderr, on_exit, true, false, false, cwd); - data->proc.pty.width = term_width; - data->proc.pty.height = curwin->w_height; - data->proc.pty.term_name = xstrdup("xterm-256color"); + data->stream.pty.width = term_width; + data->stream.pty.height = curwin->w_height; + data->stream.pty.term_name = xstrdup("xterm-256color"); if (!common_job_start(data, rettv)) { return; } @@ -16705,7 +16674,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) topts.resize_cb = term_resize; topts.close_cb = term_close; - int pid = data->proc.pty.process.pid; + int pid = data->stream.pty.process.pid; char buf[1024]; // format the title with the pid to conform with the term:// URI @@ -16725,7 +16694,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Terminal *term = terminal_open(topts); data->term = term; - data->refcount++; + channel_incref(data); return; } @@ -16760,30 +16729,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) return true; } -/// Unref/free callback -void callback_free(Callback *const callback) - FUNC_ATTR_NONNULL_ALL -{ - switch (callback->type) { - case kCallbackFuncref: { - func_unref(callback->data.funcref); - xfree(callback->data.funcref); - break; - } - case kCallbackPartial: { - partial_unref(callback->data.partial); - break; - } - case kCallbackNone: { - break; - } - default: { - abort(); - } - } - callback->type = kCallbackNone; -} - bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL @@ -22402,7 +22347,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, return ret; } -static inline TerminalJobData *common_job_init(char **argv, +static inline Channel *common_job_init(char **argv, Callback on_stdout, Callback on_stderr, Callback on_exit, @@ -22411,25 +22356,18 @@ static inline TerminalJobData *common_job_init(char **argv, bool detach, const char *cwd) { - TerminalJobData *data = xcalloc(1, sizeof(TerminalJobData)); - data->stopped = false; + Channel *data = channel_alloc(kChannelStreamProc); data->on_stdout = on_stdout; data->on_stderr = on_stderr; data->on_exit = on_exit; - data->events = multiqueue_new_child(main_loop.events); - data->rpc = rpc; + data->is_rpc = rpc; if (pty) { - data->proc.pty = pty_process_init(&main_loop, data); + data->stream.pty = pty_process_init(&main_loop, data); } else { - data->proc.uv = libuv_process_init(&main_loop, data); + data->stream.uv = libuv_process_init(&main_loop, data); } - Process *proc = (Process *)&data->proc; + Process *proc = (Process *)&data->stream.proc; proc->argv = argv; - proc->in = &data->in; - proc->out = &data->out; - if (!pty) { - proc->err = &data->err; - } proc->cb = eval_job_process_exit_cb; proc->events = data->events; proc->detach = detach; @@ -22456,80 +22394,66 @@ static inline bool common_job_callbacks(dict_T *vopts, Callback *on_stdout, return false; } -static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) +static inline bool common_job_start(Channel *data, typval_T *rettv) { - Process *proc = (Process *)&data->proc; + Process *proc = (Process *)&data->stream.proc; if (proc->type == kProcessTypePty && proc->detach) { EMSG2(_(e_invarg2), "terminal/pty job cannot be detached"); - xfree(data->proc.pty.term_name); + xfree(data->stream.pty.term_name); shell_free_argv(proc->argv); - free_term_job_data_event((void **)&data); + channel_decref(data); return false; } - data->id = next_chan_id++; - pmap_put(uint64_t)(jobs, data->id, data); - data->refcount++; char *cmd = xstrdup(proc->argv[0]); - int status = process_spawn(proc); + bool has_out, has_err; + if (proc->type == kProcessTypePty) { + has_out = true; + has_err = false; + } else { + has_out = data->is_rpc || data->on_stdout.type != kCallbackNone; + has_err = data->on_stderr.type != kCallbackNone; + } + int status = process_spawn(proc, true, has_out, has_err); if (status) { EMSG3(_(e_jobspawn), os_strerror(status), cmd); xfree(cmd); if (proc->type == kProcessTypePty) { - xfree(data->proc.pty.term_name); + xfree(data->stream.pty.term_name); } rettv->vval.v_number = proc->status; - term_job_data_decref(data); + channel_decref(data); return false; } xfree(cmd); - if (data->rpc) { - // the rpc channel takes over the in and out streams - channel_from_process(proc, data->id); + if (data->is_rpc) { + // the rpc takes over the in and out streams + rpc_start(data); } else { - wstream_init(proc->in, 0); - if (proc->out) { - rstream_init(proc->out, 0); - rstream_start(proc->out, on_job_stdout, data); + wstream_init(&proc->in, 0); + if (has_out) { + rstream_init(&proc->out, 0); + rstream_start(&proc->out, on_job_stdout, data); } } - if (proc->err) { - rstream_init(proc->err, 0); - rstream_start(proc->err, on_job_stderr, data); + if (has_err) { + rstream_init(&proc->err, 0); + rstream_start(&proc->err, on_job_stderr, data); } rettv->vval.v_number = data->id; return true; } -static inline void free_term_job_data_event(void **argv) -{ - TerminalJobData *data = argv[0]; - callback_free(&data->on_stdout); - callback_free(&data->on_stderr); - callback_free(&data->on_exit); - - multiqueue_free(data->events); - pmap_del(uint64_t)(jobs, data->id); - xfree(data); -} - -static inline void free_term_job_data(TerminalJobData *data) -{ - // data->queue may still be used after this function returns(process_wait), so - // only free in the next event loop iteration - multiqueue_put(main_loop.fast_events, free_term_job_data_event, 1, data); -} - // vimscript job callbacks must be executed on Nvim main loop -static inline void process_job_event(TerminalJobData *data, Callback *callback, +static inline void process_job_event(Channel *data, Callback *callback, const char *type, char *buf, size_t count, int status) { - JobEvent event_data; + ChannelEvent event_data; event_data.received = NULL; if (buf) { event_data.received = tv_list_alloc(); @@ -22566,18 +22490,18 @@ static inline void process_job_event(TerminalJobData *data, Callback *callback, static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, void *job, bool eof) { - TerminalJobData *data = job; + Channel *data = job; on_job_output(stream, job, buf, count, eof, &data->on_stdout, "stdout"); } static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, void *job, bool eof) { - TerminalJobData *data = job; + Channel *data = job; on_job_output(stream, job, buf, count, eof, &data->on_stderr, "stderr"); } -static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf, +static void on_job_output(Stream *stream, Channel *data, RBuffer *buf, size_t count, bool eof, Callback *callback, const char *type) { @@ -22604,14 +22528,14 @@ static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf, static void eval_job_process_exit_cb(Process *proc, int status, void *d) { - TerminalJobData *data = d; - if (data->term && !data->exited) { - data->exited = true; + Channel *data = d; + if (data->term && !data->stream.proc.exited) { + data->stream.proc.exited = true; char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status); terminal_close(data->term, msg); } - if (data->rpc) { + if (data->is_rpc) { channel_process_exit(data->id, status); } @@ -22621,58 +22545,51 @@ static void eval_job_process_exit_cb(Process *proc, int status, void *d) process_job_event(data, &data->on_exit, "exit", NULL, 0, status); - term_job_data_decref(data); + channel_decref(data); } static void term_write(char *buf, size_t size, void *d) { - TerminalJobData *job = d; - if (job->in.closed) { + Channel *job = d; + if (job->stream.proc.in.closed) { // If the backing stream was closed abruptly, there may be write events // ahead of the terminal close event. Just ignore the writes. ILOG("write failed: stream is closed"); return; } WBuffer *wbuf = wstream_new_buffer(xmemdup(buf, size), size, 1, xfree); - wstream_write(&job->in, wbuf); + wstream_write(&job->stream.proc.in, wbuf); } static void term_resize(uint16_t width, uint16_t height, void *d) { - TerminalJobData *data = d; - pty_process_resize(&data->proc.pty, width, height); + Channel *data = d; + pty_process_resize(&data->stream.pty, width, height); } static inline void term_delayed_free(void **argv) { - TerminalJobData *j = argv[0]; - if (j->in.pending_reqs || j->out.pending_reqs || j->err.pending_reqs) { + Channel *j = argv[0]; + if (j->stream.proc.in.pending_reqs || j->stream.proc.out.pending_reqs) { multiqueue_put(j->events, term_delayed_free, 1, j); return; } terminal_destroy(j->term); - term_job_data_decref(j); + channel_decref(j); } static void term_close(void *d) { - TerminalJobData *data = d; - if (!data->exited) { - data->exited = true; - process_stop((Process *)&data->proc); + Channel *data = d; + if (!data->stream.proc.exited) { + data->stream.proc.exited = true; + process_stop((Process *)&data->stream.proc); } multiqueue_put(data->events, term_delayed_free, 1, data); } -static void term_job_data_decref(TerminalJobData *data) -{ - if (!(--data->refcount)) { - free_term_job_data(data); - } -} - -static void on_job_event(JobEvent *ev) +static void on_job_event(ChannelEvent *ev) { if (!ev->callback) { return; @@ -22704,15 +22621,24 @@ static void on_job_event(JobEvent *ev) tv_clear(&rettv); } -static TerminalJobData *find_job(uint64_t id) +static Channel *find_job(uint64_t id, bool show_error) { - TerminalJobData *data = pmap_get(uint64_t)(jobs, id); - if (!data || data->stopped) { + Channel *data = find_channel(id); + if (!data || data->streamtype != kChannelStreamProc + || process_is_stopped(&data->stream.proc)) { + if (show_error) { + if (data && data->streamtype != kChannelStreamProc) { + EMSG(_(e_invchanjob)); + } else { + EMSG(_(e_invchan)); + } + } return NULL; } return data; } + static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) { if (check_restricted() || check_secure()) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 262ea922ef..99382d2a24 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -847,6 +847,30 @@ bool tv_callback_equal(const Callback *const cb1, const Callback *const cb2) return false; } +/// Unref/free callback +void callback_free(Callback *const callback) + FUNC_ATTR_NONNULL_ALL +{ + switch (callback->type) { + case kCallbackFuncref: { + func_unref(callback->data.funcref); + xfree(callback->data.funcref); + break; + } + case kCallbackPartial: { + partial_unref(callback->data.partial); + break; + } + case kCallbackNone: { + break; + } + default: { + abort(); + } + } + callback->type = kCallbackNone; +} + /// Remove watcher from a dictionary /// /// @param dict Dictionary to remove watcher from. diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 758b35796e..c101cb1bb9 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -46,22 +46,22 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvstdio[2].flags = UV_IGNORE; uvproc->uv.data = proc; - if (proc->in) { + if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t, - &proc->in->uv.pipe); + &proc->in.uv.pipe); } - if (proc->out) { + if (!proc->out.closed) { uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t, - &proc->out->uv.pipe); + &proc->out.uv.pipe); } - if (proc->err) { + if (!proc->err.closed) { uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; uvproc->uvstdio[2].data.stream = STRUCT_CAST(uv_stream_t, - &proc->err->uv.pipe); + &proc->err.uv.pipe); } int status; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 41e793500a..1bbe4d9cad 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -27,26 +27,33 @@ #define CLOSE_PROC_STREAM(proc, stream) \ do { \ - if (proc->stream && !proc->stream->closed) { \ - stream_close(proc->stream, NULL, NULL); \ + if (!proc->stream.closed) { \ + stream_close(&proc->stream, NULL, NULL); \ } \ } while (0) static bool process_is_tearing_down = false; /// @returns zero on success, or negative error code -int process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL +int process_spawn(Process *proc, bool in, bool out, bool err) + FUNC_ATTR_NONNULL_ALL { - if (proc->in) { - uv_pipe_init(&proc->loop->uv, &proc->in->uv.pipe, 0); + if (in) { + uv_pipe_init(&proc->loop->uv, &proc->in.uv.pipe, 0); + } else { + proc->in.closed = true; } - if (proc->out) { - uv_pipe_init(&proc->loop->uv, &proc->out->uv.pipe, 0); + if (out) { + uv_pipe_init(&proc->loop->uv, &proc->out.uv.pipe, 0); + } else { + proc->out.closed = true; } - if (proc->err) { - uv_pipe_init(&proc->loop->uv, &proc->err->uv.pipe, 0); + if (err) { + uv_pipe_init(&proc->loop->uv, &proc->err.uv.pipe, 0); + } else { + proc->err.closed = true; } int status; @@ -62,14 +69,14 @@ int process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL } if (status) { - if (proc->in) { - uv_close((uv_handle_t *)&proc->in->uv.pipe, NULL); + if (in) { + uv_close((uv_handle_t *)&proc->in.uv.pipe, NULL); } - if (proc->out) { - uv_close((uv_handle_t *)&proc->out->uv.pipe, NULL); + if (out) { + uv_close((uv_handle_t *)&proc->out.uv.pipe, NULL); } - if (proc->err) { - uv_close((uv_handle_t *)&proc->err->uv.pipe, NULL); + if (err) { + uv_close((uv_handle_t *)&proc->err.uv.pipe, NULL); } if (proc->type == kProcessTypeUv) { @@ -82,30 +89,30 @@ int process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL return status; } - if (proc->in) { - stream_init(NULL, proc->in, -1, - STRUCT_CAST(uv_stream_t, &proc->in->uv.pipe)); - proc->in->events = proc->events; - proc->in->internal_data = proc; - proc->in->internal_close_cb = on_process_stream_close; + if (in) { + stream_init(NULL, &proc->in, -1, + STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe)); + proc->in.events = proc->events; + proc->in.internal_data = proc; + proc->in.internal_close_cb = on_process_stream_close; proc->refcount++; } - if (proc->out) { - stream_init(NULL, proc->out, -1, - STRUCT_CAST(uv_stream_t, &proc->out->uv.pipe)); - proc->out->events = proc->events; - proc->out->internal_data = proc; - proc->out->internal_close_cb = on_process_stream_close; + if (out) { + stream_init(NULL, &proc->out, -1, + STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe)); + proc->out.events = proc->events; + proc->out.internal_data = proc; + proc->out.internal_close_cb = on_process_stream_close; proc->refcount++; } - if (proc->err) { - stream_init(NULL, proc->err, -1, - STRUCT_CAST(uv_stream_t, &proc->err->uv.pipe)); - proc->err->events = proc->events; - proc->err->internal_data = proc; - proc->err->internal_close_cb = on_process_stream_close; + if (err) { + stream_init(NULL, &proc->err, -1, + STRUCT_CAST(uv_stream_t, &proc->err.uv.pipe)); + proc->err.events = proc->events; + proc->err.internal_data = proc; + proc->err.internal_close_cb = on_process_stream_close; proc->refcount++; } @@ -395,8 +402,8 @@ static void process_close_handles(void **argv) { Process *proc = argv[0]; - flush_stream(proc, proc->out); - flush_stream(proc, proc->err); + flush_stream(proc, &proc->out); + flush_stream(proc, &proc->err); process_close_streams(proc); process_close(proc); diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 5c00e8e7ec..0897563c83 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -23,13 +23,15 @@ struct process { uint64_t stopped_time; const char *cwd; char **argv; - Stream *in, *out, *err; + Stream in, out, err; process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; + bool exited; // TODO: redundant bool closed, detach; MultiQueue *events; }; + static inline Process process_init(Loop *loop, ProcessType type, void *data) { return (Process) { @@ -43,9 +45,9 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) .stopped_time = 0, .cwd = NULL, .argv = NULL, - .in = NULL, - .out = NULL, - .err = NULL, + .in = { .closed = false }, + .out = { .closed = false }, + .err = { .closed = false }, .cb = NULL, .closed = false, .internal_close_cb = NULL, @@ -54,6 +56,11 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) }; } +static inline bool process_is_stopped(Process *proc) +{ + return proc->stopped_time != 0; +} + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/process.h.generated.h" #endif diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h index d27497e4a4..70a708ff4d 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -33,6 +33,7 @@ typedef void (*stream_write_cb)(Stream *stream, void *data, int status); typedef void (*stream_close_cb)(Stream *stream, void *data); struct stream { + bool closed; union { uv_pipe_t pipe; uv_tcp_t tcp; @@ -52,7 +53,6 @@ struct stream { size_t maxmem; size_t pending_reqs; size_t num_bytes; - bool closed; MultiQueue *events; }; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 1ff0f7eb89..c639dbcf83 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1074,11 +1074,12 @@ EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s")); EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range")); EXTERN char_u e_invcmd[] INIT(= N_("E476: Invalid command")); EXTERN char_u e_isadir2[] INIT(= N_("E17: \"%s\" is a directory")); -EXTERN char_u e_invjob[] INIT(= N_("E900: Invalid job id")); +EXTERN char_u e_invchan[] INIT(= N_("E900: Invalid channel id")); +EXTERN char_u e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job")); EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full")); EXTERN char_u e_jobspawn[] INIT(= N_( - "E903: Process failed to start: %s: \"%s\"")); -EXTERN char_u e_jobnotpty[] INIT(= N_("E904: Job is not connected to a pty")); + "E903: Process failed to start: %s: \"%s\"")); +EXTERN char_u e_channotpty[] INIT(= N_("E904: channel is not a pty")); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 18f6334780..42b1f63830 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -11,6 +11,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/api/ui.h" +#include "nvim/channel.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/event/loop.h" @@ -40,47 +41,6 @@ #define log_server_msg(...) #endif -typedef enum { - kChannelTypeSocket, - kChannelTypeProc, - kChannelTypeStdio, - kChannelTypeInternal -} ChannelType; - -typedef struct { - uint64_t request_id; - bool returned, errored; - Object result; -} ChannelCallFrame; - -typedef struct { - uint64_t id; - size_t refcount; - PMap(cstr_t) *subscribed_events; - bool closed; - ChannelType type; - msgpack_unpacker *unpacker; - union { - Stream stream; // bidirectional (socket) - Process *proc; - struct { - Stream in; - Stream out; - } std; - } data; - uint64_t next_request_id; - kvec_t(ChannelCallFrame *) call_stack; - MultiQueue *events; -} Channel; - -typedef struct { - Channel *channel; - MsgpackRpcRequestHandler handler; - Array args; - uint64_t request_id; -} RequestEvent; - -static PMap(uint64_t) *channels = NULL; static PMap(cstr_t) *event_strings = NULL; static msgpack_sbuffer out_buffer; @@ -88,50 +48,32 @@ static msgpack_sbuffer out_buffer; # include "msgpack_rpc/channel.c.generated.h" #endif -/// Initializes the module -void channel_init(void) +void rpc_init(void) { ch_before_blocking_events = multiqueue_new_child(main_loop.events); - channels = pmap_new(uint64_t)(); event_strings = pmap_new(cstr_t)(); msgpack_sbuffer_init(&out_buffer); - remote_ui_init(); } -/// Teardown the module -void channel_teardown(void) -{ - if (!channels) { - return; - } - - Channel *channel; - map_foreach_value(channels, channel, { - close_channel(channel); - }); -} - -/// Creates an API channel by starting a process and connecting to its -/// stdin/stdout. stderr is handled by the job infrastructure. -/// -/// @param argv The argument vector for the process. [consumed] -/// @return The channel id (> 0), on success. -/// 0, on error. -uint64_t channel_from_process(Process *proc, uint64_t id) +void rpc_start(Channel *channel) { - Channel *channel = register_channel(kChannelTypeProc, id, proc->events); - incref(channel); // process channels are only closed by the exit_cb - channel->data.proc = proc; + channel->is_rpc = true; + RpcState *rpc = &channel->rpc; + rpc->closed = false; + rpc->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE); + rpc->subscribed_events = pmap_new(cstr_t)(); + rpc->next_request_id = 1; + kv_init(rpc->call_stack); - wstream_init(proc->in, 0); - rstream_init(proc->out, 0); - rstream_start(proc->out, receive_msgpack, channel); + Stream *in = channel_instream(channel); + Stream *out = channel_outstream(channel); - DLOG("ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, proc->in, - proc->out); + DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, in, out); - return channel->id; + wstream_init(in, 0); + rstream_init(out, CHANNEL_BUFFER_SIZE); + rstream_start(out, receive_msgpack, channel); } /// Creates an API channel from a tcp/pipe socket connection @@ -139,19 +81,15 @@ uint64_t channel_from_process(Process *proc, uint64_t id) /// @param watcher The SocketWatcher ready to accept the connection void channel_from_connection(SocketWatcher *watcher) { - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); - socket_watcher_accept(watcher, &channel->data.stream); - incref(channel); // close channel only after the stream is closed - channel->data.stream.internal_close_cb = close_cb; - channel->data.stream.internal_data = channel; - wstream_init(&channel->data.stream, 0); - rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE); - rstream_start(&channel->data.stream, receive_msgpack, channel); - - DLOG("ch %" PRIu64 " in/out-stream=%p", channel->id, - &channel->data.stream); + Channel *channel = channel_alloc(kChannelStreamSocket); + socket_watcher_accept(watcher, &channel->stream.socket); + channel_incref(channel); // close channel only after the stream is closed + channel->stream.socket.internal_close_cb = close_cb; + channel->stream.socket.internal_data = channel; + rpc_start(channel); } +/// TODO: move to eval.c, also support bytes uint64_t channel_connect(bool tcp, const char *address, int timeout, const char **error) { @@ -165,34 +103,40 @@ uint64_t channel_connect(bool tcp, const char *address, xfree(path); } - Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); - if (!socket_connect(&main_loop, &channel->data.stream, + Channel *channel = channel_alloc(kChannelStreamSocket); + if (!socket_connect(&main_loop, &channel->stream.socket, tcp, address, timeout, error)) { - decref(channel); + channel_decref(channel); return 0; } - incref(channel); // close channel only after the stream is closed - channel->data.stream.internal_close_cb = close_cb; - channel->data.stream.internal_data = channel; - wstream_init(&channel->data.stream, 0); - rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE); - rstream_start(&channel->data.stream, receive_msgpack, channel); + channel_incref(channel); // close channel only after the stream is closed + channel->stream.socket.internal_close_cb = close_cb; + channel->stream.socket.internal_data = channel; + rpc_start(channel); return channel->id; } +static Channel *find_rpc_channel(uint64_t id) +{ + Channel *chan = find_channel(id); + if (!chan || !chan->is_rpc || chan->rpc.closed) { + return NULL; + } + return chan; +} + /// Publishes an event to a channel. /// /// @param id Channel id. 0 means "broadcast to all subscribed channels" /// @param name Event name (application-defined) /// @param args Array of event arguments /// @return True if the event was sent successfully, false otherwise. -bool channel_send_event(uint64_t id, const char *name, Array args) +bool rpc_send_event(uint64_t id, const char *name, Array args) { Channel *channel = NULL; - if (id && (!(channel = pmap_get(uint64_t)(channels, id)) - || channel->closed)) { + if (id && (!(channel = find_rpc_channel(id)))) { api_free_array(args); return false; } @@ -213,29 +157,30 @@ bool channel_send_event(uint64_t id, const char *name, Array args) /// @param args Array with method arguments /// @param[out] error True if the return value is an error /// @return Whatever the remote method returned -Object channel_send_call(uint64_t id, - const char *method_name, - Array args, - Error *err) +Object rpc_send_call(uint64_t id, + const char *method_name, + Array args, + Error *err) { Channel *channel = NULL; - if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) { + if (!(channel = find_rpc_channel(id))) { api_set_error(err, kErrorTypeException, "Invalid channel: %" PRIu64, id); api_free_array(args); return NIL; } - incref(channel); - uint64_t request_id = channel->next_request_id++; + channel_incref(channel); + RpcState *rpc = &channel->rpc; + uint64_t request_id = rpc->next_request_id++; // Send the msgpack-rpc request send_request(channel, request_id, method_name, args); // Push the frame ChannelCallFrame frame = { request_id, false, false, NIL }; - kv_push(channel->call_stack, &frame); + kv_push(rpc->call_stack, &frame); LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned); - (void)kv_pop(channel->call_stack); + (void)kv_pop(rpc->call_stack); if (frame.errored) { if (frame.result.type == kObjectTypeString) { @@ -260,7 +205,7 @@ Object channel_send_call(uint64_t id, api_free_object(frame.result); } - decref(channel); + channel_decref(channel); return frame.errored ? NIL : frame.result; } @@ -269,11 +214,11 @@ Object channel_send_call(uint64_t id, /// /// @param id The channel id /// @param event The event type string -void channel_subscribe(uint64_t id, char *event) +void rpc_subscribe(uint64_t id, char *event) { Channel *channel; - if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) { + if (!(channel = find_rpc_channel(id))) { abort(); } @@ -284,18 +229,18 @@ void channel_subscribe(uint64_t id, char *event) pmap_put(cstr_t)(event_strings, event_string, event_string); } - pmap_put(cstr_t)(channel->subscribed_events, event_string, event_string); + pmap_put(cstr_t)(channel->rpc.subscribed_events, event_string, event_string); } /// Unsubscribes to event broadcasts /// /// @param id The channel id /// @param event The event type string -void channel_unsubscribe(uint64_t id, char *event) +void rpc_unsubscribe(uint64_t id, char *event) { Channel *channel; - if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) { + if (!(channel = find_rpc_channel(id))) { abort(); } @@ -310,7 +255,7 @@ bool channel_close(uint64_t id) { Channel *channel; - if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) { + if (!(channel = find_rpc_channel(id))) { return false; } @@ -322,24 +267,22 @@ bool channel_close(uint64_t id) /// Neovim void channel_from_stdio(void) { - Channel *channel = register_channel(kChannelTypeStdio, 0, NULL); - incref(channel); // stdio channels are only closed on exit + Channel *channel = channel_alloc(kChannelStreamStdio); + channel_incref(channel); // stdio channels are only closed on exit // read stream - rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); - rstream_start(&channel->data.std.in, receive_msgpack, channel); - // write stream - wstream_init_fd(&main_loop, &channel->data.std.out, 1, 0); + rstream_init_fd(&main_loop, &channel->stream.stdio.in, 0, CHANNEL_BUFFER_SIZE); + wstream_init_fd(&main_loop, &channel->stream.stdio.out, 1, 0); - DLOG("ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, - &channel->data.std.in, &channel->data.std.out); + rpc_start(channel); } /// Creates a loopback channel. This is used to avoid deadlock /// when an instance connects to its own named pipe. uint64_t channel_create_internal(void) { - Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); - incref(channel); // internal channel lives until process exit + Channel *channel = channel_alloc(kChannelStreamInternal); + channel_incref(channel); // internal channel lives until process exit + rpc_start(channel); return channel->id; } @@ -347,8 +290,8 @@ void channel_process_exit(uint64_t id, int status) { Channel *channel = pmap_get(uint64_t)(channels, id); - channel->closed = true; - decref(channel); + // channel_decref(channel); remove?? + channel->rpc.closed = true; } // rstream.c:read_event() invokes this as stream->read_cb(). @@ -356,7 +299,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, bool eof) { Channel *channel = data; - incref(channel); + channel_incref(channel); if (eof) { close_channel(channel); @@ -367,30 +310,19 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, goto end; } - if ((chan_wstream(channel) != NULL && chan_wstream(channel)->closed) - || (chan_rstream(channel) != NULL && chan_rstream(channel)->closed)) { - char buf[256]; - snprintf(buf, sizeof(buf), - "ch %" PRIu64 ": stream closed unexpectedly. " - "closing channel", - channel->id); - call_set_error(channel, buf, WARN_LOG_LEVEL); - goto end; - } - size_t count = rbuffer_size(rbuf); - DLOG("ch %" PRIu64 ": parsing %u bytes from msgpack Stream: %p", + DLOG("ch %" PRIu64 ": parsing %zu bytes from msgpack Stream: %p", channel->id, count, stream); // Feed the unpacker with data - msgpack_unpacker_reserve_buffer(channel->unpacker, count); - rbuffer_read(rbuf, msgpack_unpacker_buffer(channel->unpacker), count); - msgpack_unpacker_buffer_consumed(channel->unpacker, count); + msgpack_unpacker_reserve_buffer(channel->rpc.unpacker, count); + rbuffer_read(rbuf, msgpack_unpacker_buffer(channel->rpc.unpacker), count); + msgpack_unpacker_buffer_consumed(channel->rpc.unpacker, count); parse_msgpack(channel); end: - decref(channel); + channel_decref(channel); } static void parse_msgpack(Channel *channel) @@ -400,8 +332,8 @@ static void parse_msgpack(Channel *channel) msgpack_unpack_return result; // Deserialize everything we can. - while ((result = msgpack_unpacker_next(channel->unpacker, &unpacked)) == - MSGPACK_UNPACK_SUCCESS) { + while ((result = msgpack_unpacker_next(channel->rpc.unpacker, &unpacked)) == + MSGPACK_UNPACK_SUCCESS) { bool is_response = is_rpc_response(&unpacked.data); log_client_msg(channel->id, !is_response, unpacked.data); @@ -427,7 +359,7 @@ static void parse_msgpack(Channel *channel) if (result == MSGPACK_UNPACK_NOMEM_ERROR) { mch_errmsg(e_outofmem); mch_errmsg("\n"); - decref(channel); + channel_decref(channel); preserve_exit(); } @@ -492,7 +424,7 @@ static void handle_request(Channel *channel, msgpack_object *request) evdata->handler = handler; evdata->args = args; evdata->request_id = request_id; - incref(channel); + channel_incref(channel); if (handler.async) { bool is_get_mode = handler.fn == handle_nvim_get_mode; @@ -530,66 +462,30 @@ static void on_request_event(void **argv) api_free_object(result); } api_free_array(args); - decref(channel); + channel_decref(channel); xfree(e); api_clear_error(&error); } -/// Returns the Stream that a Channel writes to. -static Stream *chan_wstream(Channel *chan) -{ - switch (chan->type) { - case kChannelTypeSocket: - return &chan->data.stream; - case kChannelTypeProc: - return chan->data.proc->in; - case kChannelTypeStdio: - return &chan->data.std.out; - case kChannelTypeInternal: - return NULL; - } - abort(); -} - -/// Returns the Stream that a Channel reads from. -static Stream *chan_rstream(Channel *chan) -{ - switch (chan->type) { - case kChannelTypeSocket: - return &chan->data.stream; - case kChannelTypeProc: - return chan->data.proc->out; - case kChannelTypeStdio: - return &chan->data.std.in; - case kChannelTypeInternal: - return NULL; - } - abort(); -} - - static bool channel_write(Channel *channel, WBuffer *buffer) { - bool success = false; + bool success; - if (channel->closed) { + if (channel->rpc.closed) { wstream_release_wbuffer(buffer); return false; } - switch (channel->type) { - case kChannelTypeSocket: - case kChannelTypeProc: - case kChannelTypeStdio: - success = wstream_write(chan_wstream(channel), buffer); - break; - case kChannelTypeInternal: - incref(channel); - CREATE_EVENT(channel->events, internal_read_event, 2, channel, buffer); - success = true; - break; + if (channel->streamtype == kChannelStreamInternal) { + channel_incref(channel); + CREATE_EVENT(channel->events, internal_read_event, 2, channel, buffer); + success = true; + } else { + Stream *in = channel_instream(channel); + success = wstream_write(in, buffer); } + if (!success) { // If the write failed for any reason, close the channel char buf[256]; @@ -609,14 +505,14 @@ static void internal_read_event(void **argv) Channel *channel = argv[0]; WBuffer *buffer = argv[1]; - msgpack_unpacker_reserve_buffer(channel->unpacker, buffer->size); - memcpy(msgpack_unpacker_buffer(channel->unpacker), + msgpack_unpacker_reserve_buffer(channel->rpc.unpacker, buffer->size); + memcpy(msgpack_unpacker_buffer(channel->rpc.unpacker), buffer->data, buffer->size); - msgpack_unpacker_buffer_consumed(channel->unpacker, buffer->size); + msgpack_unpacker_buffer_consumed(channel->rpc.unpacker, buffer->size); parse_msgpack(channel); - decref(channel); + channel_decref(channel); wstream_release_wbuffer(buffer); } @@ -665,7 +561,8 @@ static void broadcast_event(const char *name, Array args) Channel *channel; map_foreach_value(channels, channel, { - if (pmap_has(cstr_t)(channel->subscribed_events, name)) { + if (channel->is_rpc + && pmap_has(cstr_t)(channel->rpc.subscribed_events, name)) { kv_push(subscribed, channel); } }); @@ -695,10 +592,11 @@ end: static void unsubscribe(Channel *channel, char *event) { char *event_string = pmap_get(cstr_t)(event_strings, event); - pmap_del(cstr_t)(channel->subscribed_events, event_string); + pmap_del(cstr_t)(channel->rpc.subscribed_events, event_string); map_foreach_value(channels, channel, { - if (pmap_has(cstr_t)(channel->subscribed_events, event_string)) { + if (channel->is_rpc + && pmap_has(cstr_t)(channel->rpc.subscribed_events, event_string)) { return; } }); @@ -709,86 +607,65 @@ static void unsubscribe(Channel *channel, char *event) } /// Close the channel streams/process and free the channel resources. +/// TODO: move to channel.h static void close_channel(Channel *channel) { - if (channel->closed) { + if (channel->rpc.closed) { return; } - channel->closed = true; + channel->rpc.closed = true; - switch (channel->type) { - case kChannelTypeSocket: - stream_close(&channel->data.stream, NULL, NULL); + switch (channel->streamtype) { + case kChannelStreamSocket: + stream_close(&channel->stream.socket, NULL, NULL); break; - case kChannelTypeProc: + case kChannelStreamProc: // Only close the rpc channel part, // there could be an error message on the stderr stream - process_close_in(channel->data.proc); - process_close_out(channel->data.proc); + process_close_in(&channel->stream.proc); + process_close_out(&channel->stream.proc); break; - case kChannelTypeStdio: - stream_close(&channel->data.std.in, NULL, NULL); - stream_close(&channel->data.std.out, NULL, NULL); + case kChannelStreamStdio: + stream_close(&channel->stream.stdio.in, NULL, NULL); + stream_close(&channel->stream.stdio.out, NULL, NULL); multiqueue_put(main_loop.fast_events, exit_event, 1, channel); return; - case kChannelTypeInternal: + case kChannelStreamInternal: // nothing to free. break; } - decref(channel); + channel_decref(channel); } static void exit_event(void **argv) { - decref(argv[0]); + channel_decref(argv[0]); if (!exiting) { mch_exit(0); } } -static void free_channel(Channel *channel) +void rpc_free(Channel *channel) { remote_ui_disconnect(channel->id); - pmap_del(uint64_t)(channels, channel->id); - msgpack_unpacker_free(channel->unpacker); + msgpack_unpacker_free(channel->rpc.unpacker); // Unsubscribe from all events char *event_string; - map_foreach_value(channel->subscribed_events, event_string, { + map_foreach_value(channel->rpc.subscribed_events, event_string, { unsubscribe(channel, event_string); }); - pmap_free(cstr_t)(channel->subscribed_events); - kv_destroy(channel->call_stack); - if (channel->type != kChannelTypeProc) { - multiqueue_free(channel->events); - } - xfree(channel); + pmap_free(cstr_t)(channel->rpc.subscribed_events); + kv_destroy(channel->rpc.call_stack); } static void close_cb(Stream *stream, void *data) { - decref(data); -} - -static Channel *register_channel(ChannelType type, uint64_t id, - MultiQueue *events) -{ - Channel *rv = xmalloc(sizeof(Channel)); - rv->events = events ? events : multiqueue_new_child(main_loop.events); - rv->type = type; - rv->refcount = 1; - rv->closed = false; - rv->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE); - rv->id = id > 0 ? id : next_chan_id++; - rv->subscribed_events = pmap_new(cstr_t)(); - rv->next_request_id = 1; - kv_init(rv->call_stack); - pmap_put(uint64_t)(channels, rv->id, rv); - return rv; + channel_decref(data); } static bool is_rpc_response(msgpack_object *obj) @@ -803,15 +680,18 @@ static bool is_rpc_response(msgpack_object *obj) static bool is_valid_rpc_response(msgpack_object *obj, Channel *channel) { uint64_t response_id = obj->via.array.ptr[1].via.u64; + if (kv_size(channel->rpc.call_stack) == 0) { + return false; + } + // Must be equal to the frame at the stack's bottom - return kv_size(channel->call_stack) && response_id - == kv_A(channel->call_stack, kv_size(channel->call_stack) - 1)->request_id; + ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); + return response_id == frame->request_id; } static void complete_call(msgpack_object *obj, Channel *channel) { - ChannelCallFrame *frame = kv_A(channel->call_stack, - kv_size(channel->call_stack) - 1); + ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); frame->returned = true; frame->errored = obj->via.array.ptr[2].type != MSGPACK_OBJECT_NIL; @@ -825,8 +705,8 @@ static void complete_call(msgpack_object *obj, Channel *channel) static void call_set_error(Channel *channel, char *msg, int loglevel) { LOG(loglevel, "RPC: %s", msg); - for (size_t i = 0; i < kv_size(channel->call_stack); i++) { - ChannelCallFrame *frame = kv_A(channel->call_stack, i); + for (size_t i = 0; i < kv_size(channel->rpc.call_stack); i++) { + ChannelCallFrame *frame = kv_A(channel->rpc.call_stack, i); frame->returned = true; frame->errored = true; api_free_object(frame->result); @@ -875,18 +755,6 @@ static WBuffer *serialize_response(uint64_t channel_id, return rv; } -static void incref(Channel *channel) -{ - channel->refcount++; -} - -static void decref(Channel *channel) -{ - if (!(--channel->refcount)) { - free_channel(channel); - } -} - #if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL #define REQ "[request] " #define RES "[response] " diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h index f8fe6f129b..9ff5abdc5f 100644 --- a/src/nvim/msgpack_rpc/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -8,6 +8,7 @@ #include "nvim/event/socket.h" #include "nvim/event/process.h" #include "nvim/vim.h" +#include "nvim/channel.h" #define METHOD_MAXLEN 512 @@ -16,6 +17,7 @@ /// of os_inchar(), so they are processed "just-in-time". MultiQueue *ch_before_blocking_events; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "msgpack_rpc/channel.h.generated.h" #endif diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h new file mode 100644 index 0000000000..6d8362e8b7 --- /dev/null +++ b/src/nvim/msgpack_rpc/channel_defs.h @@ -0,0 +1,36 @@ +#ifndef NVIM_MSGPACK_RPC_CHANNEL_DEFS_H +#define NVIM_MSGPACK_RPC_CHANNEL_DEFS_H + +#include +#include +#include + +#include "nvim/api/private/defs.h" +#include "nvim/event/socket.h" +#include "nvim/event/process.h" +#include "nvim/vim.h" + +typedef struct Channel Channel; + +typedef struct { + uint64_t request_id; + bool returned, errored; + Object result; +} ChannelCallFrame; + +typedef struct { + Channel *channel; + MsgpackRpcRequestHandler handler; + Array args; + uint64_t request_id; +} RequestEvent; + +typedef struct { + PMap(cstr_t) *subscribed_events; + bool closed; + msgpack_unpacker *unpacker; + uint64_t next_request_id; + kvec_t(ChannelCallFrame *) call_stack; +} RpcState; + +#endif // NVIM_MSGPACK_RPC_CHANNEL_DEFS_H diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index ee3ab96a83..e39f837c1a 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -47,7 +47,7 @@ int pty_process_spawn(PtyProcess *ptyproc) int status = 0; // zero or negative error code (libuv convention) Process *proc = (Process *)ptyproc; - assert(!proc->err); + assert(proc->err.closed); uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD); ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 }; uv_disable_stdio_inheritance(); @@ -83,12 +83,12 @@ int pty_process_spawn(PtyProcess *ptyproc) goto error; } - if (proc->in - && (status = set_duplicating_descriptor(master, &proc->in->uv.pipe))) { + if (!proc->in.closed + && (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) { goto error; } - if (proc->out - && (status = set_duplicating_descriptor(master, &proc->out->uv.pipe))) { + if (!proc->out.closed + && (status = set_duplicating_descriptor(master, &proc->out.uv.pipe))) { goto error; } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index ef8a699c56..3c4839a076 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -44,7 +44,7 @@ int pty_process_spawn(PtyProcess *ptyproc) wchar_t *cwd = NULL; const char *emsg = NULL; - assert(!proc->err); + assert(proc->err.closed); cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); if (cfg == NULL) { @@ -71,20 +71,20 @@ int pty_process_spawn(PtyProcess *ptyproc) goto cleanup; } - if (proc->in != NULL) { + if (!proc->in.closed) { in_req = xmalloc(sizeof(uv_connect_t)); uv_pipe_connect( in_req, - &proc->in->uv.pipe, + &proc->in.uv.pipe, in_name, pty_process_connect_cb); } - if (proc->out != NULL) { + if (!proc->out.closed) { out_req = xmalloc(sizeof(uv_connect_t)); uv_pipe_connect( out_req, - &proc->out->uv.pipe, + &proc->out.uv.pipe, out_name, pty_process_connect_cb); } @@ -228,7 +228,7 @@ static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) PtyProcess *ptyproc = wait_eof_timer->data; Process *proc = (Process *)ptyproc; - if (!proc->out || !uv_is_readable(proc->out->uvstream)) { + if (proc->out.closed || !uv_is_readable(proc->out.uvstream)) { uv_timer_stop(&ptyproc->wait_eof_timer); pty_process_finish2(ptyproc); } diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 32e9a70e57..f54fe412ba 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -207,16 +207,12 @@ static int do_os_system(char **argv, char prog[MAXPATHL]; xstrlcpy(prog, argv[0], MAXPATHL); - Stream in, out, err; LibuvProcess uvproc = libuv_process_init(&main_loop, &buf); Process *proc = &uvproc.process; MultiQueue *events = multiqueue_new_child(main_loop.events); proc->events = events; proc->argv = argv; - proc->in = input != NULL ? &in : NULL; - proc->out = &out; - proc->err = &err; - int status = process_spawn(proc); + int status = process_spawn(proc, input != NULL, true, true); if (status) { loop_poll_events(&main_loop, 0); // Failed, probably 'shell' is not executable. @@ -236,27 +232,27 @@ static int do_os_system(char **argv, // streams while there's still data in the OS buffer (due to the process // exiting before all data is read). if (input != NULL) { - proc->in->events = NULL; - wstream_init(proc->in, 0); + proc->in.events = NULL; + wstream_init(&proc->in, 0); } - proc->out->events = NULL; - rstream_init(proc->out, 0); - rstream_start(proc->out, data_cb, &buf); - proc->err->events = NULL; - rstream_init(proc->err, 0); - rstream_start(proc->err, data_cb, &buf); + proc->out.events = NULL; + rstream_init(&proc->out, 0); + rstream_start(&proc->out, data_cb, &buf); + proc->err.events = NULL; + rstream_init(&proc->err, 0); + rstream_start(&proc->err, data_cb, &buf); // write the input, if any if (input) { WBuffer *input_buffer = wstream_new_buffer((char *) input, len, 1, NULL); - if (!wstream_write(&in, input_buffer)) { + if (!wstream_write(&proc->in, input_buffer)) { // couldn't write, stop the process and tell the user about it process_stop(proc); return -1; } // close the input stream after everything is written - wstream_set_write_cb(&in, shell_write_cb, NULL); + wstream_set_write_cb(&proc->in, shell_write_cb, NULL); } // Invoke busy_start here so LOOP_PROCESS_EVENTS_UNTIL will not change the -- cgit From 1ebc96fe10fbdbec22caa26d5d52a9f095da9687 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 5 Jun 2017 08:29:10 +0200 Subject: channels: allow bytes sockets and stdio, and buffered bytes output --- src/nvim/channel.c | 417 ++++++++++++++++++++++++++++++++++++++++- src/nvim/channel.h | 36 ++-- src/nvim/eval.c | 359 +++++++++-------------------------- src/nvim/eval.h | 3 + src/nvim/eval.lua | 1 + src/nvim/eval/typval.c | 11 +- src/nvim/event/process.c | 11 +- src/nvim/event/rstream.c | 28 +-- src/nvim/event/stream.h | 6 +- src/nvim/globals.h | 8 +- src/nvim/main.c | 19 +- src/nvim/msgpack_rpc/channel.c | 87 +-------- src/nvim/os/shell.c | 7 +- src/nvim/rbuffer.c | 2 +- 14 files changed, 574 insertions(+), 421 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index e61ec9c19b..5ee4e5be09 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -3,10 +3,25 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" +#include "nvim/eval.h" +#include "nvim/event/socket.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/server.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/ascii.h" +static bool did_stdio = false; PMap(uint64_t) *channels = NULL; +typedef struct { + Channel *data; + Callback *callback; + const char *type; + list_T *received; + int status; +} ChannelEvent; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.c.generated.h" #endif @@ -32,6 +47,21 @@ void channel_init(void) remote_ui_init(); } +/// Allocates a channel. +/// +/// Channel is allocated with refcount 1, which should be decreased +/// when the underlying stream closes. +static Channel *channel_alloc(ChannelStreamType type) +{ + Channel *chan = xcalloc(1, sizeof(*chan)); + chan->id = type == kChannelStreamStdio ? 1 : next_chan_id++; + chan->events = multiqueue_new_child(main_loop.events); + chan->refcount = 1; + chan->streamtype = type; + pmap_put(uint64_t)(channels, chan->id, chan); + return chan; +} + void channel_incref(Channel *channel) { channel->refcount++; @@ -44,6 +74,21 @@ void channel_decref(Channel *channel) } } +void callback_reader_free(CallbackReader *reader) +{ + callback_free(&reader->cb); + if (reader->buffered) { + ga_clear(&reader->buffer); + } +} + +void callback_reader_start(CallbackReader *reader) +{ + if (reader->buffered) { + ga_init(&reader->buffer, sizeof(char *), 1); + } +} + static void free_channel_event(void **argv) { Channel *channel = argv[0]; @@ -51,11 +96,379 @@ static void free_channel_event(void **argv) rpc_free(channel); } - callback_free(&channel->on_stdout); - callback_free(&channel->on_stderr); + callback_reader_free(&channel->on_stdout); + callback_reader_free(&channel->on_stderr); callback_free(&channel->on_exit); pmap_del(uint64_t)(channels, channel->id); multiqueue_free(channel->events); xfree(channel); } + +static void channel_destroy_early(Channel *chan) +{ + if ((chan->id != --next_chan_id)) { + abort(); + } + + if ((--chan->refcount != 0)) { + abort(); + } + + free_channel_event((void **)&chan); +} + + +static void close_cb(Stream *stream, void *data) +{ + channel_decref(data); +} + +Channel *channel_job_start(char **argv, CallbackReader on_stdout, + CallbackReader on_stderr, Callback on_exit, + bool pty, bool rpc, bool detach, const char *cwd, + uint16_t pty_width, uint16_t pty_height, + char *term_name, varnumber_T *status_out) +{ + Channel *chan = channel_alloc(kChannelStreamProc); + chan->on_stdout = on_stdout; + chan->on_stderr = on_stderr; + chan->on_exit = on_exit; + chan->is_rpc = rpc; + + if (pty) { + if (detach) { + EMSG2(_(e_invarg2), "terminal/pty job cannot be detached"); + shell_free_argv(argv); + xfree(term_name); + channel_destroy_early(chan); + *status_out = 0; + return NULL; + } + chan->stream.pty = pty_process_init(&main_loop, chan); + if (pty_width > 0) { + chan->stream.pty.width = pty_width; + } + if (pty_height > 0) { + chan->stream.pty.height = pty_height; + } + if (term_name) { + chan->stream.pty.term_name = term_name; + } + } else { + chan->stream.uv = libuv_process_init(&main_loop, chan); + } + + Process *proc = (Process *)&chan->stream.proc; + proc->argv = argv; + proc->cb = channel_process_exit_cb; + proc->events = chan->events; + proc->detach = detach; + proc->cwd = cwd; + + char *cmd = xstrdup(proc->argv[0]); + bool has_out, has_err; + if (proc->type == kProcessTypePty) { + has_out = true; + has_err = false; + } else { + has_out = chan->is_rpc || callback_reader_set(chan->on_stdout); + has_err = callback_reader_set(chan->on_stderr); + } + int status = process_spawn(proc, true, has_out, has_err); + if (has_err) { + proc->err.events = chan->events; + } + if (status) { + EMSG3(_(e_jobspawn), os_strerror(status), cmd); + xfree(cmd); + if (proc->type == kProcessTypePty) { + xfree(chan->stream.pty.term_name); + } + channel_destroy_early(chan); + *status_out = proc->status; + return NULL; + } + xfree(cmd); + + wstream_init(&proc->in, 0); + if (has_out) { + rstream_init(&proc->out, 0); + } + + if (chan->is_rpc) { + // the rpc takes over the in and out streams + rpc_start(chan); + } else { + proc->in.events = chan->events; + if (has_out) { + callback_reader_start(&chan->on_stdout); + proc->out.events = chan->events; + rstream_start(&proc->out, on_job_stdout, chan); + } + } + + if (has_err) { + callback_reader_start(&chan->on_stderr); + rstream_init(&proc->err, 0); + rstream_start(&proc->err, on_job_stderr, chan); + } + *status_out = (varnumber_T)chan->id; + return chan; +} + + +uint64_t channel_connect(bool tcp, const char *address, + bool rpc, CallbackReader on_output, + int timeout, const char **error) +{ + if (!tcp && rpc) { + char *path = fix_fname(address); + if (server_owns_pipe_address(path)) { + // avoid deadlock + xfree(path); + return channel_create_internal_rpc(); + } + xfree(path); + } + + Channel *channel = channel_alloc(kChannelStreamSocket); + if (!socket_connect(&main_loop, &channel->stream.socket, + tcp, address, timeout, error)) { + channel_destroy_early(channel); + return 0; + } + + channel_incref(channel); // close channel only after the stream is closed + channel->stream.socket.internal_close_cb = close_cb; + channel->stream.socket.internal_data = channel; + wstream_init(&channel->stream.socket, 0); + rstream_init(&channel->stream.socket, 0); + + if (rpc) { + rpc_start(channel); + } else { + channel->on_stdout = on_output; + callback_reader_start(&channel->on_stdout); + channel->stream.socket.events = channel->events; + rstream_start(&channel->stream.socket, on_socket_output, channel); + } + + return channel->id; +} + +/// Creates an RPC channel from a tcp/pipe socket connection +/// +/// @param watcher The SocketWatcher ready to accept the connection +void channel_from_connection(SocketWatcher *watcher) +{ + Channel *channel = channel_alloc(kChannelStreamSocket); + socket_watcher_accept(watcher, &channel->stream.socket); + channel_incref(channel); // close channel only after the stream is closed + channel->stream.socket.internal_close_cb = close_cb; + channel->stream.socket.internal_data = channel; + wstream_init(&channel->stream.socket, 0); + rstream_init(&channel->stream.socket, 0); + rpc_start(channel); +} + +/// Creates a loopback channel. This is used to avoid deadlock +/// when an instance connects to its own named pipe. +static uint64_t channel_create_internal_rpc(void) +{ + Channel *channel = channel_alloc(kChannelStreamInternal); + channel_incref(channel); // internal channel lives until process exit + rpc_start(channel); + return channel->id; +} + +/// Creates an API channel from stdin/stdout. This is used when embedding +/// Neovim +uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, + const char **error) + FUNC_ATTR_NONNULL_ALL +{ + if (!headless_mode) { + *error = _("can only be opened in headless mode"); + return 0; + } + + if (did_stdio) { + *error = _("channel was already open"); + return 0; + } + did_stdio = true; + + Channel *channel = channel_alloc(kChannelStreamStdio); + + rstream_init_fd(&main_loop, &channel->stream.stdio.in, 0, 0); + wstream_init_fd(&main_loop, &channel->stream.stdio.out, 1, 0); + + if (rpc) { + rpc_start(channel); + } else { + channel->on_stdout = on_output; + callback_reader_start(&channel->on_stdout); + channel->stream.stdio.in.events = channel->events; + channel->stream.stdio.out.events = channel->events; + rstream_start(&channel->stream.stdio.in, on_stdio_input, channel); + } + + return channel->id; +} + +// vimscript job callbacks must be executed on Nvim main loop +static inline void process_channel_event(Channel *chan, Callback *callback, + const char *type, char *buf, + size_t count, int status) +{ + ChannelEvent event_data; + event_data.received = NULL; + if (buf) { + event_data.received = tv_list_alloc(); + char *ptr = buf; + size_t remaining = count; + size_t off = 0; + + while (off < remaining) { + // append the line + if (ptr[off] == NL) { + tv_list_append_string(event_data.received, ptr, (ssize_t)off); + size_t skip = off + 1; + ptr += skip; + remaining -= skip; + off = 0; + continue; + } + if (ptr[off] == NUL) { + // Translate NUL to NL + ptr[off] = NL; + } + off++; + } + tv_list_append_string(event_data.received, ptr, (ssize_t)off); + } else { + event_data.status = status; + } + event_data.data = chan; + event_data.callback = callback; + event_data.type = type; + on_channel_event(&event_data); +} + +void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) +{ + Channel *chan = data; + on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdout"); +} + +void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) +{ + Channel *chan = data; + on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr, "stderr"); +} + +static void on_socket_output(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) +{ + Channel *chan = data; + on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "data"); +} + +static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) +{ + Channel *chan = data; + on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); +} + +static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, + size_t count, bool eof, CallbackReader *reader, + const char *type) +{ + // stub variable, to keep reading consistent with the order of events, only + // consider the count parameter. + size_t r; + char *ptr = rbuffer_read_ptr(buf, &r); + + if (eof) { + if (reader->buffered) { + process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, + (size_t)reader->buffer.ga_len, 0); + ga_clear(&reader->buffer); + } else if (callback_reader_set(*reader)) { + process_channel_event(chan, &reader->cb, type, ptr, 0, 0); + } + return; + } + + // The order here matters, the terminal must receive the data first because + // process_channel_event will modify the read buffer(convert NULs into NLs) + if (chan->term) { + terminal_receive(chan->term, ptr, count); + } + + rbuffer_consumed(buf, count); + if (reader->buffered) { + ga_concat_len(&reader->buffer, ptr, count); + } else if (callback_reader_set(*reader)) { + process_channel_event(chan, &reader->cb, type, ptr, count, 0); + } +} + +static void channel_process_exit_cb(Process *proc, int status, void *data) +{ + Channel *chan = data; + if (chan->term && !chan->stream.proc.exited) { + chan->stream.proc.exited = true; + char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; + snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status); + terminal_close(chan->term, msg); + } + if (chan->is_rpc) { + channel_process_exit(chan->id, status); + } + + if (chan->status_ptr) { + *chan->status_ptr = status; + } + + process_channel_event(chan, &chan->on_exit, "exit", NULL, 0, status); + + channel_decref(chan); +} + +static void on_channel_event(ChannelEvent *ev) +{ + if (!ev->callback) { + return; + } + + typval_T argv[4]; + + argv[0].v_type = VAR_NUMBER; + argv[0].v_lock = VAR_UNLOCKED; + argv[0].vval.v_number = (varnumber_T)ev->data->id; + + if (ev->received) { + argv[1].v_type = VAR_LIST; + argv[1].v_lock = VAR_UNLOCKED; + argv[1].vval.v_list = ev->received; + argv[1].vval.v_list->lv_refcount++; + } else { + argv[1].v_type = VAR_NUMBER; + argv[1].v_lock = VAR_UNLOCKED; + argv[1].vval.v_number = ev->status; + } + + argv[2].v_type = VAR_STRING; + argv[2].v_lock = VAR_UNLOCKED; + argv[2].vval.v_string = (uint8_t *)ev->type; + + typval_T rettv = TV_INITIAL_VALUE; + callback_call(ev->callback, 3, argv, &rettv); + tv_clear(&rettv); +} + diff --git a/src/nvim/channel.h b/src/nvim/channel.h index b48f508722..8ead6749a6 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -21,14 +21,19 @@ typedef struct { Stream out; } StdioPair; -// typedef struct { -// Callback on_out; -// Callback on_close; -// Garray buffer; -// bool buffering; -// } CallbackReader - -#define CallbackReader Callback +typedef struct { + Callback cb; + garray_T buffer; + bool buffered; +} CallbackReader; + +#define CALLBACK_READER_INIT ((CallbackReader){ .cb = CALLBACK_NONE, \ + .buffer = GA_EMPTY_INIT_VALUE, \ + .buffered = false }) +static inline bool callback_reader_set(CallbackReader reader) +{ + return reader.cb.type != kCallbackNone; +} struct Channel { uint64_t id; @@ -61,17 +66,6 @@ EXTERN PMap(uint64_t) *channels; # include "channel.h.generated.h" #endif -static inline Channel *channel_alloc(ChannelStreamType type) -{ - Channel *chan = xcalloc(1, sizeof(*chan)); - chan->id = next_chan_id++; - chan->events = multiqueue_new_child(main_loop.events); - chan->refcount = 1; - chan->streamtype = type; - pmap_put(uint64_t)(channels, chan->id, chan); - return chan; -} - /// @returns Channel with the id or NULL if not found static inline Channel *find_channel(uint64_t id) { @@ -89,7 +83,7 @@ static inline Stream *channel_instream(Channel *chan) return &chan->stream.socket; case kChannelStreamStdio: - return &chan->stream.stdio.in; + return &chan->stream.stdio.out; case kChannelStreamInternal: abort(); @@ -108,7 +102,7 @@ static inline Stream *channel_outstream(Channel *chan) return &chan->stream.socket; case kChannelStreamStdio: - return &chan->stream.stdio.out; + return &chan->stream.stdio.in; case kChannelStreamInternal: abort(); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 013cfce78d..c2fd7ac19c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -437,14 +437,6 @@ static ScopeDictDictItem vimvars_var; /// v: hashtab #define vimvarht vimvardict.dv_hashtab -typedef struct { - Channel *data; - Callback *callback; - const char *type; - list_T *received; - int status; -} ChannelEvent; - typedef struct { TimeWatcher tw; int timer_id; @@ -5121,12 +5113,12 @@ bool garbage_collect(bool testing) // named functions (matters for closures) ABORTING(set_ref_in_functions(copyID)); - // Jobs + // Channels { Channel *data; map_foreach_value(channels, data, { - set_ref_in_callback(&data->on_stdout, copyID, NULL, NULL); - set_ref_in_callback(&data->on_stderr, copyID, NULL, NULL); + set_ref_in_callback_reader(&data->on_stdout, copyID, NULL, NULL); + set_ref_in_callback_reader(&data->on_stderr, copyID, NULL, NULL); set_ref_in_callback(&data->on_exit, copyID, NULL, NULL); }) } @@ -11643,8 +11635,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool detach = false; bool rpc = false; bool pty = false; - Callback on_stdout = CALLBACK_NONE; - Callback on_stderr = CALLBACK_NONE; + CallbackReader on_stdout = CALLBACK_READER_INIT, + on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; char *cwd = NULL; if (argvars[1].v_type == VAR_DICT) { @@ -11676,26 +11668,17 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - Channel *data = common_job_init(argv, on_stdout, on_stderr, on_exit, - pty, rpc, detach, cwd); + uint16_t width = 0, height = 0; + char *term_name = NULL; if (pty) { - PtyProcess *pty = &data->stream.pty; - uint16_t width = (uint16_t)tv_dict_get_number(job_opts, "width"); - if (width > 0) { - pty->width = width; - } - uint16_t height = (uint16_t)tv_dict_get_number(job_opts, "height"); - if (height > 0) { - pty->height = height; - } - char *term = tv_dict_get_string(job_opts, "TERM", true); - if (term) { - pty->term_name = term; - } + width = (uint16_t)tv_dict_get_number(job_opts, "width"); + height = (uint16_t)tv_dict_get_number(job_opts, "height"); + term_name = tv_dict_get_string(job_opts, "TERM", true); } - common_job_start(data, rettv); + channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, rpc, detach, + cwd, width, height, term_name, &rettv->vval.v_number); } // "jobstop()" function @@ -11782,7 +11765,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) || !(data = find_job(arg->li_tv.vval.v_number, false))) { continue; } - int status = process_wait((Process *)&data->stream.proc, remaining, waiting_jobs); + int status = process_wait((Process *)&data->stream.proc, remaining, + waiting_jobs); if (status < 0) { // interrupted or timed out, skip remaining jobs. if (status == -2) { @@ -13922,10 +13906,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) // The last item of argv must be NULL argv[i] = NULL; - Channel *data = common_job_init(argv, CALLBACK_NONE, CALLBACK_NONE, - CALLBACK_NONE, false, true, false, - NULL); - common_job_start(data, rettv); + channel_job_start(argv, CALLBACK_READER_INIT, CALLBACK_READER_INIT, + CALLBACK_NONE, false, true, false, NULL, 0, 0, NULL, + &rettv->vval.v_number); } // "rpcstop()" function @@ -13946,7 +13929,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) // if called with a job, stop it, else closes the channel uint64_t id = argvars[0].vval.v_number; - if (find_job(id, false)) { // FIXME + if (find_job(id, false)) { f_jobstop(argvars, rettv, NULL); } else { rettv->vval.v_number = channel_close(id); @@ -15126,18 +15109,19 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } bool rpc = false; + CallbackReader on_data = CALLBACK_READER_INIT; if (argvars[2].v_type == VAR_DICT) { dict_T *opts = argvars[2].vval.v_dict; rpc = tv_dict_get_number(opts, "rpc") != 0; - } - if (!rpc) { - EMSG2(_(e_invarg2), "rpc option must be true"); - return; + if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) { + return; + } + on_data.buffered = tv_dict_get_number(opts, "data_buffered"); } const char *error = NULL; - uint64_t id = channel_connect(tcp, address, 50, &error); + uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error); if (error) { EMSG2(_("connection failed: %s"), error); @@ -15517,6 +15501,35 @@ static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_sort_uniq(argvars, rettv, true); } +/// "stdioopen()" function +static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_invarg)); + return; + } + + + bool rpc = false; + CallbackReader on_stdin = CALLBACK_READER_INIT; + dict_T *opts = argvars[0].vval.v_dict; + rpc = tv_dict_get_number(opts, "rpc") != 0; + + if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { + return; + } + + const char *error; + uint64_t id = channel_from_stdio(rpc, on_stdin, &error); + if (!id) { + EMSG2(e_stdiochan2, error); + } + + + rettv->vval.v_number = (varnumber_T)id; + rettv->v_type = VAR_NUMBER; +} + /// "uniq({list})" function static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -16633,8 +16646,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Callback on_stdout = CALLBACK_NONE, on_stderr = CALLBACK_NONE, - on_exit = CALLBACK_NONE; + CallbackReader on_stdout = CALLBACK_READER_INIT, + on_stderr = CALLBACK_READER_INIT; + Callback on_exit = CALLBACK_NONE; dict_T *job_opts = NULL; const char *cwd = "."; if (argvars[1].v_type == VAR_DICT) { @@ -16658,23 +16672,23 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } uint16_t term_width = MAX(0, curwin->w_width - win_col_off(curwin)); - Channel *data = common_job_init(argv, on_stdout, on_stderr, on_exit, - true, false, false, cwd); - data->stream.pty.width = term_width; - data->stream.pty.height = curwin->w_height; - data->stream.pty.term_name = xstrdup("xterm-256color"); - if (!common_job_start(data, rettv)) { + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, + true, false, false, cwd, + term_width, curwin->w_height, + xstrdup("xterm-256color"), + &rettv->vval.v_number); + if (rettv->vval.v_number <= 0) { return; } TerminalOptions topts; - topts.data = data; + topts.data = chan; topts.width = term_width; topts.height = curwin->w_height; topts.write_cb = term_write; topts.resize_cb = term_resize; topts.close_cb = term_close; - int pid = data->stream.pty.process.pid; + int pid = chan->stream.pty.process.pid; char buf[1024]; // format the title with the pid to conform with the term:// URI @@ -16685,16 +16699,19 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) (void)setfname(curbuf, (char_u *)buf, NULL, true); // Save the job id and pid in b:terminal_job_{id,pid} Error err = ERROR_INIT; + dict_set_var(curbuf->b_vars, cstr_as_string("terminal_channel_id"), + INTEGER_OBJ(chan->id), false, false, &err); + // deprecated name: dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), - INTEGER_OBJ(rettv->vval.v_number), false, false, &err); + INTEGER_OBJ(chan->id), false, false, &err); api_clear_error(&err); dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); Terminal *term = terminal_open(topts); - data->term = term; - channel_incref(data); + chan->term = term; + channel_incref(chan); return; } @@ -16783,6 +16800,13 @@ static bool set_ref_in_callback(Callback *callback, int copyID, return false; } +static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + return set_ref_in_callback(&reader->cb, copyID, ht_stack, list_stack); +} + static void add_timer_info(typval_T *rettv, timer_T *timer) { list_T *list = rettv->vval.v_list; @@ -22347,206 +22371,29 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, return ret; } -static inline Channel *common_job_init(char **argv, - Callback on_stdout, - Callback on_stderr, - Callback on_exit, - bool pty, - bool rpc, - bool detach, - const char *cwd) -{ - Channel *data = channel_alloc(kChannelStreamProc); - data->on_stdout = on_stdout; - data->on_stderr = on_stderr; - data->on_exit = on_exit; - data->is_rpc = rpc; - if (pty) { - data->stream.pty = pty_process_init(&main_loop, data); - } else { - data->stream.uv = libuv_process_init(&main_loop, data); - } - Process *proc = (Process *)&data->stream.proc; - proc->argv = argv; - proc->cb = eval_job_process_exit_cb; - proc->events = data->events; - proc->detach = detach; - proc->cwd = cwd; - return data; -} - /// common code for getting job callbacks for jobstart, termopen and rpcstart /// /// @return true/false on success/failure. -static inline bool common_job_callbacks(dict_T *vopts, Callback *on_stdout, - Callback *on_stderr, Callback *on_exit) +static inline bool common_job_callbacks(dict_T *vopts, + CallbackReader *on_stdout, + CallbackReader *on_stderr, + Callback *on_exit) { - if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), on_stdout) - &&tv_dict_get_callback(vopts, S_LEN("on_stderr"), on_stderr) + if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), &on_stdout->cb) + &&tv_dict_get_callback(vopts, S_LEN("on_stderr"), &on_stderr->cb) && tv_dict_get_callback(vopts, S_LEN("on_exit"), on_exit)) { + on_stdout->buffered = tv_dict_get_number(vopts, "stdout_buffered"); + on_stderr->buffered = tv_dict_get_number(vopts, "stderr_buffered"); vopts->dv_refcount++; return true; } - callback_free(on_stdout); - callback_free(on_stderr); + callback_reader_free(on_stdout); + callback_reader_free(on_stderr); callback_free(on_exit); return false; } -static inline bool common_job_start(Channel *data, typval_T *rettv) -{ - Process *proc = (Process *)&data->stream.proc; - if (proc->type == kProcessTypePty && proc->detach) { - EMSG2(_(e_invarg2), "terminal/pty job cannot be detached"); - xfree(data->stream.pty.term_name); - shell_free_argv(proc->argv); - channel_decref(data); - return false; - } - - data->refcount++; - char *cmd = xstrdup(proc->argv[0]); - bool has_out, has_err; - if (proc->type == kProcessTypePty) { - has_out = true; - has_err = false; - } else { - has_out = data->is_rpc || data->on_stdout.type != kCallbackNone; - has_err = data->on_stderr.type != kCallbackNone; - } - int status = process_spawn(proc, true, has_out, has_err); - if (status) { - EMSG3(_(e_jobspawn), os_strerror(status), cmd); - xfree(cmd); - if (proc->type == kProcessTypePty) { - xfree(data->stream.pty.term_name); - } - rettv->vval.v_number = proc->status; - channel_decref(data); - return false; - } - xfree(cmd); - - - if (data->is_rpc) { - // the rpc takes over the in and out streams - rpc_start(data); - } else { - wstream_init(&proc->in, 0); - if (has_out) { - rstream_init(&proc->out, 0); - rstream_start(&proc->out, on_job_stdout, data); - } - } - - if (has_err) { - rstream_init(&proc->err, 0); - rstream_start(&proc->err, on_job_stderr, data); - } - rettv->vval.v_number = data->id; - return true; -} - -// vimscript job callbacks must be executed on Nvim main loop -static inline void process_job_event(Channel *data, Callback *callback, - const char *type, char *buf, size_t count, - int status) -{ - ChannelEvent event_data; - event_data.received = NULL; - if (buf) { - event_data.received = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(event_data.received, ptr, off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(event_data.received, ptr, off); - } else { - event_data.status = status; - } - event_data.data = data; - event_data.callback = callback; - event_data.type = type; - on_job_event(&event_data); -} - -static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, - void *job, bool eof) -{ - Channel *data = job; - on_job_output(stream, job, buf, count, eof, &data->on_stdout, "stdout"); -} - -static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, - void *job, bool eof) -{ - Channel *data = job; - on_job_output(stream, job, buf, count, eof, &data->on_stderr, "stderr"); -} - -static void on_job_output(Stream *stream, Channel *data, RBuffer *buf, - size_t count, bool eof, Callback *callback, - const char *type) -{ - if (eof) { - return; - } - - // stub variable, to keep reading consistent with the order of events, only - // consider the count parameter. - size_t r; - char *ptr = rbuffer_read_ptr(buf, &r); - - // The order here matters, the terminal must receive the data first because - // process_job_event will modify the read buffer(convert NULs into NLs) - if (data->term) { - terminal_receive(data->term, ptr, count); - } - - rbuffer_consumed(buf, count); - if (callback->type != kCallbackNone) { - process_job_event(data, callback, type, ptr, count, 0); - } -} - -static void eval_job_process_exit_cb(Process *proc, int status, void *d) -{ - Channel *data = d; - if (data->term && !data->stream.proc.exited) { - data->stream.proc.exited = true; - char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; - snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status); - terminal_close(data->term, msg); - } - if (data->is_rpc) { - channel_process_exit(data->id, status); - } - - if (data->status_ptr) { - *data->status_ptr = status; - } - - process_job_event(data, &data->on_exit, "exit", NULL, 0, status); - - channel_decref(data); -} static void term_write(char *buf, size_t size, void *d) { @@ -22589,38 +22436,6 @@ static void term_close(void *d) multiqueue_put(data->events, term_delayed_free, 1, data); } -static void on_job_event(ChannelEvent *ev) -{ - if (!ev->callback) { - return; - } - - typval_T argv[4]; - - argv[0].v_type = VAR_NUMBER; - argv[0].v_lock = 0; - argv[0].vval.v_number = ev->data->id; - - if (ev->received) { - argv[1].v_type = VAR_LIST; - argv[1].v_lock = 0; - argv[1].vval.v_list = ev->received; - argv[1].vval.v_list->lv_refcount++; - } else { - argv[1].v_type = VAR_NUMBER; - argv[1].v_lock = 0; - argv[1].vval.v_number = ev->status; - } - - argv[2].v_type = VAR_STRING; - argv[2].v_lock = 0; - argv[2].vval.v_string = (uint8_t *)ev->type; - - typval_T rettv = TV_INITIAL_VALUE; - callback_call(ev->callback, 3, argv, &rettv); - tv_clear(&rettv); -} - static Channel *find_job(uint64_t id, bool show_error) { Channel *data = find_channel(id); diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 070bc35bd5..7814a086fa 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -7,6 +7,9 @@ #include "nvim/eval/typval.h" #include "nvim/profile.h" #include "nvim/garray.h" +#include "nvim/event/rstream.h" +#include "nvim/event/wstream.h" +#include "nvim/channel.h" #define COPYID_INC 2 #define COPYID_MASK (~0x1) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 0e359fb61c..3150f26df6 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -273,6 +273,7 @@ return { sockconnect={args={2,3}}, sort={args={1, 3}}, soundfold={args=1}, + stdioopen={args=1}, spellbadword={args={0, 1}}, spellsuggest={args={1, 3}}, split={args={1, 3}}, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 99382d2a24..4bc3a85efb 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -374,7 +374,7 @@ void tv_list_append_dict(list_T *const list, dict_T *const dict) /// case string is considered to be usual zero-terminated /// string or NULL “empty” string. void tv_list_append_string(list_T *const l, const char *const str, - const ptrdiff_t len) + const ssize_t len) FUNC_ATTR_NONNULL_ARG(1) { if (str == NULL) { @@ -824,7 +824,7 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern, /// @param[in] cb2 Second callback to check. /// /// @return True if they are equal, false otherwise. -bool tv_callback_equal(const Callback *const cb1, const Callback *const cb2) +bool tv_callback_equal(const Callback *cb1, const Callback *cb2) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (cb1->type != cb2->type) { @@ -843,12 +843,12 @@ bool tv_callback_equal(const Callback *const cb1, const Callback *const cb2) return true; } } - assert(false); + abort(); return false; } /// Unref/free callback -void callback_free(Callback *const callback) +void callback_free(Callback *callback) FUNC_ATTR_NONNULL_ALL { switch (callback->type) { @@ -864,9 +864,6 @@ void callback_free(Callback *const callback) case kCallbackNone: { break; } - default: { - abort(); - } } callback->type = kCallbackNone; } diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 1bbe4d9cad..8946f049e2 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -92,7 +92,6 @@ int process_spawn(Process *proc, bool in, bool out, bool err) if (in) { stream_init(NULL, &proc->in, -1, STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe)); - proc->in.events = proc->events; proc->in.internal_data = proc; proc->in.internal_close_cb = on_process_stream_close; proc->refcount++; @@ -101,7 +100,6 @@ int process_spawn(Process *proc, bool in, bool out, bool err) if (out) { stream_init(NULL, &proc->out, -1, STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe)); - proc->out.events = proc->events; proc->out.internal_data = proc; proc->out.internal_close_cb = on_process_stream_close; proc->refcount++; @@ -110,7 +108,6 @@ int process_spawn(Process *proc, bool in, bool out, bool err) if (err) { stream_init(NULL, &proc->err, -1, STRUCT_CAST(uv_stream_t, &proc->err.uv.pipe)); - proc->err.events = proc->events; proc->err.internal_data = proc; proc->err.internal_close_cb = on_process_stream_close; proc->refcount++; @@ -382,15 +379,15 @@ static void flush_stream(Process *proc, Stream *stream) // Poll for data and process the generated events. loop_poll_events(proc->loop, 0); - if (proc->events) { - multiqueue_process_events(proc->events); + if (stream->events) { + multiqueue_process_events(stream->events); } // Stream can be closed if it is empty. if (num_bytes == stream->num_bytes) { - if (stream->read_cb) { + if (stream->read_cb && !stream->did_eof) { // Stream callback could miss EOF handling if a child keeps the stream - // open. + // open. But only send EOF if we haven't already. stream->read_cb(stream, stream->buffer, 0, stream->cb_data, true); } break; diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 2c4db08b30..e0500ba828 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -105,20 +105,20 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) { Stream *stream = uvstream->data; - if (cnt > 0) { - stream->num_bytes += (size_t)cnt; - } - if (cnt <= 0) { - if (cnt != UV_ENOBUFS - // cnt == 0 means libuv asked for a buffer and decided it wasn't needed: - // http://docs.libuv.org/en/latest/stream.html#c.uv_read_start. - // - // We don't need to do anything with the RBuffer because the next call - // to `alloc_cb` will return the same unused pointer(`rbuffer_produced` - // won't be called) - && cnt != 0) { - DLOG("closing Stream: %p: %s (%s)", stream, + // cnt == 0 means libuv asked for a buffer and decided it wasn't needed: + // http://docs.libuv.org/en/latest/stream.html#c.uv_read_start. + // + // We don't need to do anything with the RBuffer because the next call + // to `alloc_cb` will return the same unused pointer(`rbuffer_produced` + // won't be called) + if (cnt == UV_ENOBUFS || cnt == 0) { + return; + } else if (cnt == UV_EOF && uvstream->type == UV_TTY) { + // The TTY driver might signal TTY without closing the stream + invoke_read_cb(stream, 0, true); + } else { + DLOG("Closing Stream (%p): %s (%s)", stream, uv_err_name((int)cnt), os_strerror((int)cnt)); // Read error or EOF, either way stop the stream and invoke the callback // with eof == true @@ -130,6 +130,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // at this point we're sure that cnt is positive, no error occurred size_t nread = (size_t)cnt; + stream->num_bytes += nread; // Data was already written, so all we need is to update 'wpos' to reflect // the space actually used in the buffer. rbuffer_produced(stream->buffer, nread); @@ -187,6 +188,7 @@ static void read_event(void **argv) if (stream->read_cb) { size_t count = (uintptr_t)argv[1]; bool eof = (uintptr_t)argv[2]; + stream->did_eof = eof; stream->read_cb(stream, stream->buffer, count, stream->cb_data, eof); } stream->pending_reqs--; diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h index 70a708ff4d..e713323f5c 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -14,10 +14,7 @@ typedef struct stream Stream; /// /// @param stream The Stream instance /// @param rbuffer The associated RBuffer instance -/// @param count Number of bytes to read. This must be respected if keeping -/// the order of events is a requirement. This is because events -/// may be queued and only processed later when more data is copied -/// into to the buffer, so one read may starve another. +/// @param count Number of bytes that was read. /// @param data User-defined data /// @param eof If the stream reached EOF. typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, @@ -34,6 +31,7 @@ typedef void (*stream_close_cb)(Stream *stream, void *data); struct stream { bool closed; + bool did_eof; union { uv_pipe_t pipe; uv_tcp_t tcp; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index c639dbcf83..d21cfe7ab6 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1080,6 +1080,8 @@ EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full")); EXTERN char_u e_jobspawn[] INIT(= N_( "E903: Process failed to start: %s: \"%s\"")); EXTERN char_u e_channotpty[] INIT(= N_("E904: channel is not a pty")); +EXTERN char_u e_stdiochan2[] INIT(= N_( + "E905: Couldn't open stdio channel: %s")); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); @@ -1190,9 +1192,13 @@ EXTERN char *ignoredp; // If a msgpack-rpc channel should be started over stdin/stdout EXTERN bool embedded_mode INIT(= false); +// Dont try to start an user interface +// or read/write to stdio (unless embedding) +EXTERN bool headless_mode INIT(= false); /// next free id for a job or rpc channel -EXTERN uint64_t next_chan_id INIT(= 1); +/// 1 is reserved for stdio channel +EXTERN uint64_t next_chan_id INIT(= 2); /// Used to track the status of external functions. /// Currently only used for iconv(). diff --git a/src/nvim/main.c b/src/nvim/main.c index 93afe11f3a..9059644fc0 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -103,7 +103,6 @@ typedef struct { bool input_isatty; // stdin is a terminal bool output_isatty; // stdout is a terminal bool err_isatty; // stderr is a terminal - bool headless; // Do not start the builtin UI. int no_swap_file; // "-n" argument used int use_debug_break_level; int window_count; /* number of windows to use */ @@ -299,7 +298,7 @@ int main(int argc, char **argv) cmdline_row = (int)(Rows - p_ch); msg_row = cmdline_row; screenalloc(false); /* allocate screen buffers */ - set_init_2(params.headless); + set_init_2(headless_mode); TIME_MSG("inits 2"); msg_scroll = TRUE; @@ -311,7 +310,7 @@ int main(int argc, char **argv) /* Set the break level after the terminal is initialized. */ debug_break_level = params.use_debug_break_level; - bool reading_input = !params.headless && (params.input_isatty + bool reading_input = !headless_mode && (params.input_isatty || params.output_isatty || params.err_isatty); if (reading_input) { @@ -448,7 +447,7 @@ int main(int argc, char **argv) wait_return(TRUE); } - if (!params.headless) { + if (!headless_mode) { // Stop reading from input stream, the UI layer will take over now. input_stop(); ui_builtin_start(); @@ -809,11 +808,14 @@ static void command_line_scan(mparm_T *parmp) } mch_exit(0); } else if (STRICMP(argv[0] + argv_idx, "headless") == 0) { - parmp->headless = true; + headless_mode = true; } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) { embedded_mode = true; - parmp->headless = true; - channel_from_stdio(); + headless_mode = true; + const char *err; + if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { + abort(); + } } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) parmp->literal = TRUE; @@ -1216,7 +1218,6 @@ static void init_params(mparm_T *paramp, int argc, char **argv) memset(paramp, 0, sizeof(*paramp)); paramp->argc = argc; paramp->argv = argv; - paramp->headless = false; paramp->want_full_screen = true; paramp->use_debug_break_level = -1; paramp->window_count = -1; @@ -1387,7 +1388,7 @@ static void handle_tag(char_u *tagname) // When starting in Ex mode and commands come from a file, set Silent mode. static void check_tty(mparm_T *parmp) { - if (parmp->headless) { + if (headless_mode) { return; } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 42b1f63830..56af4fa791 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -13,7 +13,6 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/server.h" #include "nvim/event/loop.h" #include "nvim/event/libuv_process.h" #include "nvim/event/rstream.h" @@ -30,12 +29,9 @@ #include "nvim/map.h" #include "nvim/log.h" #include "nvim/misc1.h" -#include "nvim/path.h" #include "nvim/lib/kvec.h" #include "nvim/os/input.h" -#define CHANNEL_BUFFER_SIZE 0xffff - #if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL #define log_client_msg(...) #define log_server_msg(...) @@ -66,57 +62,18 @@ void rpc_start(Channel *channel) rpc->next_request_id = 1; kv_init(rpc->call_stack); - Stream *in = channel_instream(channel); - Stream *out = channel_outstream(channel); - - DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, in, out); - - wstream_init(in, 0); - rstream_init(out, CHANNEL_BUFFER_SIZE); - rstream_start(out, receive_msgpack, channel); -} - -/// Creates an API channel from a tcp/pipe socket connection -/// -/// @param watcher The SocketWatcher ready to accept the connection -void channel_from_connection(SocketWatcher *watcher) -{ - Channel *channel = channel_alloc(kChannelStreamSocket); - socket_watcher_accept(watcher, &channel->stream.socket); - channel_incref(channel); // close channel only after the stream is closed - channel->stream.socket.internal_close_cb = close_cb; - channel->stream.socket.internal_data = channel; - rpc_start(channel); -} - -/// TODO: move to eval.c, also support bytes -uint64_t channel_connect(bool tcp, const char *address, - int timeout, const char **error) -{ - if (!tcp) { - char *path = fix_fname(address); - if (server_owns_pipe_address(path)) { - // avoid deadlock - xfree(path); - return channel_create_internal(); - } - xfree(path); - } + if (channel->streamtype != kChannelStreamInternal) { + Stream *out = channel_outstream(channel); +#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL + Stream *in = channel_instream(channel); + DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id, in, out); +#endif - Channel *channel = channel_alloc(kChannelStreamSocket); - if (!socket_connect(&main_loop, &channel->stream.socket, - tcp, address, timeout, error)) { - channel_decref(channel); - return 0; + rstream_start(out, receive_msgpack, channel); } - - channel_incref(channel); // close channel only after the stream is closed - channel->stream.socket.internal_close_cb = close_cb; - channel->stream.socket.internal_data = channel; - rpc_start(channel); - return channel->id; } + static Channel *find_rpc_channel(uint64_t id) { Channel *chan = find_channel(id); @@ -263,29 +220,6 @@ bool channel_close(uint64_t id) return true; } -/// Creates an API channel from stdin/stdout. This is used when embedding -/// Neovim -void channel_from_stdio(void) -{ - Channel *channel = channel_alloc(kChannelStreamStdio); - channel_incref(channel); // stdio channels are only closed on exit - // read stream - rstream_init_fd(&main_loop, &channel->stream.stdio.in, 0, CHANNEL_BUFFER_SIZE); - wstream_init_fd(&main_loop, &channel->stream.stdio.out, 1, 0); - - rpc_start(channel); -} - -/// Creates a loopback channel. This is used to avoid deadlock -/// when an instance connects to its own named pipe. -uint64_t channel_create_internal(void) -{ - Channel *channel = channel_alloc(kChannelStreamInternal); - channel_incref(channel); // internal channel lives until process exit - rpc_start(channel); - return channel->id; -} - void channel_process_exit(uint64_t id, int status) { Channel *channel = pmap_get(uint64_t)(channels, id); @@ -663,11 +597,6 @@ void rpc_free(Channel *channel) kv_destroy(channel->rpc.call_stack); } -static void close_cb(Stream *stream, void *data) -{ - channel_decref(data); -} - static bool is_rpc_response(msgpack_object *obj) { return obj->type == MSGPACK_OBJECT_ARRAY diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index f54fe412ba..ec2ebb9d19 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -227,18 +227,15 @@ static int do_os_system(char **argv, return -1; } - // We want to deal with stream events as fast a possible while queueing - // process events, so reset everything to NULL. It prevents closing the + // Note: unlike process events, stream events are not queued, as we want to + // deal with stream events as fast a possible. It prevents closing the // streams while there's still data in the OS buffer (due to the process // exiting before all data is read). if (input != NULL) { - proc->in.events = NULL; wstream_init(&proc->in, 0); } - proc->out.events = NULL; rstream_init(&proc->out, 0); rstream_start(&proc->out, data_cb, &buf); - proc->err.events = NULL; rstream_init(&proc->err, 0); rstream_start(&proc->err, data_cb, &buf); diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c index 18d453cbe9..df9394fbb2 100644 --- a/src/nvim/rbuffer.c +++ b/src/nvim/rbuffer.c @@ -121,7 +121,7 @@ char *rbuffer_read_ptr(RBuffer *buf, size_t *read_count) FUNC_ATTR_NONNULL_ALL { if (!buf->size) { *read_count = 0; - return NULL; + return buf->read_ptr; } if (buf->read_ptr < buf->write_ptr) { -- cgit From 3e59c1e20d605315d299e17ac9a059ccedd7e9d5 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 16 Jul 2017 14:29:04 +0200 Subject: channels: move away term code from eval.c --- src/nvim/buffer_defs.h | 1 + src/nvim/channel.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++-- src/nvim/eval.c | 61 +++------------------------------------------- src/nvim/event/process.h | 1 - src/nvim/option.c | 6 +++++ src/nvim/option_defs.h | 1 + src/nvim/options.lua | 8 ++++++ 7 files changed, 81 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 559dffb945..f1cbcb2627 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -603,6 +603,7 @@ struct file_buffer { char_u *b_p_bt; ///< 'buftype' int b_has_qf_entry; ///< quickfix exists for buffer int b_p_bl; ///< 'buflisted' + long b_p_channel; ///< 'channel' int b_p_cin; ///< 'cindent' char_u *b_p_cino; ///< 'cinoptions' char_u *b_p_cink; ///< 'cinkeys' diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 5ee4e5be09..c6db2b7b7a 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -421,12 +421,12 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; - if (chan->term && !chan->stream.proc.exited) { - chan->stream.proc.exited = true; + if (chan->term) { char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status); terminal_close(chan->term, msg); } + if (chan->is_rpc) { channel_process_exit(chan->id, status); } @@ -472,3 +472,62 @@ static void on_channel_event(ChannelEvent *ev) tv_clear(&rettv); } + +/// Open terminal for channel +/// +/// Channel `chan` is assumed to be an open pty channel, +/// and curbuf is assumed to be a new, unmodified buffer. +void channel_terminal_open(Channel *chan) +{ + TerminalOptions topts; + topts.data = chan; + topts.width = chan->stream.pty.width; + topts.height = chan->stream.pty.height; + topts.write_cb = term_write; + topts.resize_cb = term_resize; + topts.close_cb = term_close; + curbuf->b_p_channel = (long)chan->id; // 'channel' option + Terminal *term = terminal_open(topts); + chan->term = term; + channel_incref(chan); +} + +static void term_write(char *buf, size_t size, void *data) +{ + Channel *chan = data; + if (chan->stream.proc.in.closed) { + // If the backing stream was closed abruptly, there may be write events + // ahead of the terminal close event. Just ignore the writes. + ILOG("write failed: stream is closed"); + return; + } + WBuffer *wbuf = wstream_new_buffer(xmemdup(buf, size), size, 1, xfree); + wstream_write(&chan->stream.proc.in, wbuf); +} + +static void term_resize(uint16_t width, uint16_t height, void *data) +{ + Channel *chan = data; + pty_process_resize(&chan->stream.pty, width, height); +} + +static inline void term_delayed_free(void **argv) +{ + Channel *chan = argv[0]; + if (chan->stream.proc.in.pending_reqs || chan->stream.proc.out.pending_reqs) { + multiqueue_put(chan->events, term_delayed_free, 1, chan); + return; + } + + terminal_destroy(chan->term); + chan->term = NULL; + channel_decref(chan); +} + +static void term_close(void *data) +{ + Channel *chan = data; + process_stop(&chan->stream.proc); + multiqueue_put(chan->events, term_delayed_free, 1, data); +} + diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c2fd7ac19c..6899474577 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11550,7 +11550,8 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, argvars[2].vval.v_number); + pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, + argvars[2].vval.v_number); rettv->vval.v_number = 1; } @@ -16680,13 +16681,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->vval.v_number <= 0) { return; } - TerminalOptions topts; - topts.data = chan; - topts.width = term_width; - topts.height = curwin->w_height; - topts.write_cb = term_write; - topts.resize_cb = term_resize; - topts.close_cb = term_close; int pid = chan->stream.pty.process.pid; @@ -16699,9 +16693,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) (void)setfname(curbuf, (char_u *)buf, NULL, true); // Save the job id and pid in b:terminal_job_{id,pid} Error err = ERROR_INIT; - dict_set_var(curbuf->b_vars, cstr_as_string("terminal_channel_id"), - INTEGER_OBJ(chan->id), false, false, &err); - // deprecated name: + // deprecated: use 'channel' buffer option dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), INTEGER_OBJ(chan->id), false, false, &err); api_clear_error(&err); @@ -16709,11 +16701,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); - Terminal *term = terminal_open(topts); - chan->term = term; - channel_incref(chan); - - return; + channel_terminal_open(chan); } // "test_garbagecollect_now()" function @@ -22395,47 +22383,6 @@ static inline bool common_job_callbacks(dict_T *vopts, } -static void term_write(char *buf, size_t size, void *d) -{ - Channel *job = d; - if (job->stream.proc.in.closed) { - // If the backing stream was closed abruptly, there may be write events - // ahead of the terminal close event. Just ignore the writes. - ILOG("write failed: stream is closed"); - return; - } - WBuffer *wbuf = wstream_new_buffer(xmemdup(buf, size), size, 1, xfree); - wstream_write(&job->stream.proc.in, wbuf); -} - -static void term_resize(uint16_t width, uint16_t height, void *d) -{ - Channel *data = d; - pty_process_resize(&data->stream.pty, width, height); -} - -static inline void term_delayed_free(void **argv) -{ - Channel *j = argv[0]; - if (j->stream.proc.in.pending_reqs || j->stream.proc.out.pending_reqs) { - multiqueue_put(j->events, term_delayed_free, 1, j); - return; - } - - terminal_destroy(j->term); - channel_decref(j); -} - -static void term_close(void *d) -{ - Channel *data = d; - if (!data->stream.proc.exited) { - data->stream.proc.exited = true; - process_stop((Process *)&data->stream.proc); - } - multiqueue_put(data->events, term_delayed_free, 1, data); -} - static Channel *find_job(uint64_t id, bool show_error) { Channel *data = find_channel(id); diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 0897563c83..a8d066c291 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -26,7 +26,6 @@ struct process { Stream in, out, err; process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; - bool exited; // TODO: redundant bool closed, detach; MultiQueue *events; }; diff --git a/src/nvim/option.c b/src/nvim/option.c index f6f334f432..65ab7a54a6 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -115,6 +115,7 @@ static int p_bomb; static char_u *p_bh; static char_u *p_bt; static int p_bl; +static long p_channel; static int p_ci; static int p_cin; static char_u *p_cink; @@ -4193,6 +4194,9 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, curbuf->b_p_imsearch = B_IMODE_NONE; } p_imsearch = curbuf->b_p_imsearch; + } else if (pp == &p_channel || pp == &curbuf->b_p_channel) { + errmsg = e_invarg; + *pp = old_value; } /* if 'titlelen' has changed, redraw the title */ else if (pp == &p_titlelen) { @@ -5472,6 +5476,7 @@ static char_u *get_varp(vimoption_T *p) case PV_BH: return (char_u *)&(curbuf->b_p_bh); case PV_BT: return (char_u *)&(curbuf->b_p_bt); case PV_BL: return (char_u *)&(curbuf->b_p_bl); + case PV_CHANNEL:return (char_u *)&(curbuf->b_p_channel); case PV_CI: return (char_u *)&(curbuf->b_p_ci); case PV_CIN: return (char_u *)&(curbuf->b_p_cin); case PV_CINK: return (char_u *)&(curbuf->b_p_cink); @@ -5773,6 +5778,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_nf = vim_strsave(p_nf); buf->b_p_mps = vim_strsave(p_mps); buf->b_p_si = p_si; + buf->b_p_channel = 0; buf->b_p_ci = p_ci; buf->b_p_cin = p_cin; buf->b_p_cink = vim_strsave(p_cink); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1f62490ab9..b16f222705 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -695,6 +695,7 @@ enum { , BV_BIN , BV_BL , BV_BOMB + , BV_CHANNEL , BV_CI , BV_CIN , BV_CINK diff --git a/src/nvim/options.lua b/src/nvim/options.lua index cb3e5ad856..dd28a765fd 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -294,6 +294,14 @@ return { varname='p_cedit', defaults={if_true={vi="", vim=macros('CTRL_F_STR')}} }, + { + full_name='channel', + type='number', scope={'buffer'}, + no_mkrc=true, + nodefault=true, + varname='p_channel', + defaults={if_true={vi=0}} + }, { full_name='charconvert', abbreviation='ccv', type='string', scope={'global'}, -- cgit From 90e5cc5484ceeb410ae2a2706e09ed475cade4a5 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 8 Jun 2017 17:15:53 +0200 Subject: channels: generalize jobclose() --- src/nvim/channel.c | 98 ++++++++++++++++++++++++++++++++++--- src/nvim/channel.h | 9 ++++ src/nvim/eval.c | 107 +++++++++++++++++------------------------ src/nvim/eval.lua | 3 +- src/nvim/event/process.c | 34 +++---------- src/nvim/event/stream.c | 7 +++ src/nvim/globals.h | 3 ++ src/nvim/msgpack_rpc/channel.c | 62 ++++-------------------- src/nvim/os/shell.c | 4 -- 9 files changed, 172 insertions(+), 155 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index c6db2b7b7a..416e0a1fb6 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -35,10 +35,99 @@ void channel_teardown(void) Channel *channel; map_foreach_value(channels, channel, { - (void)channel; // close_channel(channel); + channel_close(channel->id, kChannelPartAll, NULL); }); } +/// Closes a channel +/// +/// @param id The channel id +/// @return true if successful, false otherwise +bool channel_close(uint64_t id, ChannelPart part, const char **error) +{ + Channel *chan; + Process *proc; + + const char *dummy; + if (!error) { + error = &dummy; + } + + if (!(chan = find_channel(id))) { + if (id < next_chan_id) { + // allow double close, even though we can't say what parts was valid. + return true; + } + *error = (const char *)e_invchan; + return false; + } + + bool close_main = false; + if (part == kChannelPartRpc || part == kChannelPartAll) { + close_main = true; + if (chan->is_rpc) { + rpc_close(chan); + } else if (part == kChannelPartRpc) { + *error = (const char *)e_invstream; + return false; + } + } else if ((part == kChannelPartStdin || part == kChannelPartStdout) + && chan->is_rpc) { + // EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); + *error = (const char *)e_invstreamrpc; + return false; + } + + switch (chan->streamtype) { + case kChannelStreamSocket: + if (!close_main) { + *error = (const char *)e_invstream; + return false; + } + stream_may_close(&chan->stream.socket); + break; + + case kChannelStreamProc: + proc = (Process *)&chan->stream.proc; + if (part == kChannelPartStdin || close_main) { + stream_may_close(&proc->in); + } + if (part == kChannelPartStdout || close_main) { + stream_may_close(&proc->out); + } + if (part == kChannelPartStderr || part == kChannelPartAll) { + stream_may_close(&proc->err); + } + if (proc->type == kProcessTypePty && part == kChannelPartAll) { + pty_process_close_master(&chan->stream.pty); + } + + break; + + case kChannelStreamStdio: + if (part == kChannelPartStdin || close_main) { + stream_may_close(&chan->stream.stdio.in); + } + if (part == kChannelPartStdout || close_main) { + stream_may_close(&chan->stream.stdio.out); + } + if (part == kChannelPartStderr) { + *error = (const char *)e_invstream; + return false; + } + break; + + case kChannelStreamInternal: + if (!close_main) { + *error = (const char *)e_invstream; + return false; + } + break; + } + + return true; +} + /// Initializes the module void channel_init(void) { @@ -239,7 +328,6 @@ uint64_t channel_connect(bool tcp, const char *address, return 0; } - channel_incref(channel); // close channel only after the stream is closed channel->stream.socket.internal_close_cb = close_cb; channel->stream.socket.internal_data = channel; wstream_init(&channel->stream.socket, 0); @@ -264,7 +352,6 @@ void channel_from_connection(SocketWatcher *watcher) { Channel *channel = channel_alloc(kChannelStreamSocket); socket_watcher_accept(watcher, &channel->stream.socket); - channel_incref(channel); // close channel only after the stream is closed channel->stream.socket.internal_close_cb = close_cb; channel->stream.socket.internal_data = channel; wstream_init(&channel->stream.socket, 0); @@ -277,7 +364,6 @@ void channel_from_connection(SocketWatcher *watcher) static uint64_t channel_create_internal_rpc(void) { Channel *channel = channel_alloc(kChannelStreamInternal); - channel_incref(channel); // internal channel lives until process exit rpc_start(channel); return channel->id; } @@ -427,10 +513,6 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) terminal_close(chan->term, msg); } - if (chan->is_rpc) { - channel_process_exit(chan->id, status); - } - if (chan->status_ptr) { *chan->status_ptr = status; } diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 8ead6749a6..eaf0fd92d0 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -16,6 +16,15 @@ typedef enum { kChannelStreamInternal } ChannelStreamType; +typedef enum { + kChannelPartStdin, + kChannelPartStdout, + kChannelPartStderr, + kChannelPartRpc, + kChannelPartAll +} ChannelPart; + + typedef struct { Stream in; Stream out; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6899474577..ba356f28b9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7322,6 +7322,45 @@ static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = curbuf->b_u_seq_cur; } +// "chanclose(id[, stream])" function +static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING + && argvars[1].v_type != VAR_UNKNOWN)) { + EMSG(_(e_invarg)); + return; + } + + ChannelPart part = kChannelPartAll; + if (argvars[1].v_type == VAR_STRING) { + char *stream = (char *)argvars[1].vval.v_string; + if (!strcmp(stream, "stdin")) { + part = kChannelPartStdin; + } else if (!strcmp(stream, "stdout")) { + part = kChannelPartStdout; + } else if (!strcmp(stream, "stderr")) { + part = kChannelPartStderr; + } else if (!strcmp(stream, "rpc")) { + part = kChannelPartRpc; + } else { + EMSG2(_("Invalid channel stream \"%s\""), stream); + return; + } + } + const char *error; + rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); + if (!rettv->vval.v_number) { + EMSG(error); + } +} + /* * "char2nr(string)" function */ @@ -11391,67 +11430,6 @@ static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) dict_list(argvars, rettv, 2); } -// "jobclose(id[, stream])" function -static void f_jobclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING - && argvars[1].v_type != VAR_UNKNOWN)) { - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - Process *proc = (Process *)&data->stream.proc; - - if (argvars[1].v_type == VAR_STRING) { - char *stream = (char *)argvars[1].vval.v_string; - if (!strcmp(stream, "stdin")) { - if (data->is_rpc) { - EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); - } else { - process_close_in(proc); - } - } else if (!strcmp(stream, "stdout")) { - if (data->is_rpc) { - EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); - } else { - process_close_out(proc); - } - } else if (!strcmp(stream, "stderr")) { - process_close_err(proc); - } else if (!strcmp(stream, "rpc")) { - if (data->is_rpc) { - channel_close(data->id); - } else { - EMSG(_("Invalid job stream: Not an rpc job")); - } - } else { - EMSG2(_("Invalid job stream \"%s\""), stream); - } - } else { - if (data->is_rpc) { - channel_close(data->id); - process_close_err(proc); - } else { - process_close_streams(proc); - if (proc->type == kProcessTypePty) { - pty_process_close_master(&data->stream.pty); - } - } - } -} - // "jobpid(id)" function static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -13933,7 +13911,12 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (find_job(id, false)) { f_jobstop(argvars, rettv, NULL); } else { - rettv->vval.v_number = channel_close(id); + const char *error; + rettv->vval.v_number = channel_close(argvars[0].vval.v_number, + kChannelPartRpc, &error); + if (!rettv->vval.v_number) { + EMSG(error); + } } } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 3150f26df6..bb03691fd4 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -55,6 +55,7 @@ return { call={args={2, 3}}, ceil={args=1, func="float_op_wrapper", data="&ceil"}, changenr={}, + chanclose={args={1, 2}}, char2nr={args={1, 2}}, cindent={args=1}, clearmatches={}, @@ -173,7 +174,7 @@ return { islocked={args=1}, id={args=1}, items={args=1}, - jobclose={args={1, 2}}, + jobclose={args={1, 2}, func="f_chanclose"}, jobpid={args=1}, jobresize={args=3}, jobsend={args=2}, diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 8946f049e2..34be291aef 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -25,13 +25,6 @@ // For pty processes SIGTERM is sent first (in case SIGHUP was not enough). #define KILL_TIMEOUT_MS 2000 -#define CLOSE_PROC_STREAM(proc, stream) \ - do { \ - if (!proc->stream.closed) { \ - stream_close(&proc->stream, NULL, NULL); \ - } \ - } while (0) - static bool process_is_tearing_down = false; /// @returns zero on success, or negative error code @@ -140,27 +133,11 @@ void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL pty_process_teardown(loop); } -// Wrappers around `stream_close` that protect against double-closing. void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL { - process_close_in(proc); - process_close_out(proc); - process_close_err(proc); -} - -void process_close_in(Process *proc) FUNC_ATTR_NONNULL_ALL -{ - CLOSE_PROC_STREAM(proc, in); -} - -void process_close_out(Process *proc) FUNC_ATTR_NONNULL_ALL -{ - CLOSE_PROC_STREAM(proc, out); -} - -void process_close_err(Process *proc) FUNC_ATTR_NONNULL_ALL -{ - CLOSE_PROC_STREAM(proc, err); + stream_may_close(&proc->in); + stream_may_close(&proc->out); + stream_may_close(&proc->err); } /// Synchronously wait for a process to finish @@ -237,8 +214,9 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL switch (proc->type) { case kProcessTypeUv: // Close the process's stdin. If the process doesn't close its own - // stdout/stderr, they will be closed when it exits (voluntarily or not). - process_close_in(proc); + // stdout/stderr, they will be closed when it exits(possibly due to being + // terminated after a timeout) + stream_may_close(&proc->in); ILOG("Sending SIGTERM to pid %d", proc->pid); uv_kill(proc->pid, SIGTERM); break; diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 7c865bfe1e..ba25b76ec7 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -92,6 +92,13 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data) } } +void stream_may_close(Stream *stream) +{ + if (!stream->closed) { + stream_close(stream, NULL, NULL); + } +} + void stream_close_handle(Stream *stream) FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index d21cfe7ab6..d1b0ad0ed3 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1082,6 +1082,9 @@ EXTERN char_u e_jobspawn[] INIT(= N_( EXTERN char_u e_channotpty[] INIT(= N_("E904: channel is not a pty")); EXTERN char_u e_stdiochan2[] INIT(= N_( "E905: Couldn't open stdio channel: %s")); +EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel")); +EXTERN char_u e_invstreamrpc[] INIT(= N_( + "E906: invalid stream for rpc channel, use 'rpc'")); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 56af4fa791..32781cf4d9 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -54,6 +54,7 @@ void rpc_init(void) void rpc_start(Channel *channel) { + channel_incref(channel); channel->is_rpc = true; RpcState *rpc = &channel->rpc; rpc->closed = false; @@ -204,31 +205,6 @@ void rpc_unsubscribe(uint64_t id, char *event) unsubscribe(channel, event); } -/// Closes a channel -/// -/// @param id The channel id -/// @return true if successful, false otherwise -bool channel_close(uint64_t id) -{ - Channel *channel; - - if (!(channel = find_rpc_channel(id))) { - return false; - } - - close_channel(channel); - return true; -} - -void channel_process_exit(uint64_t id, int status) -{ - Channel *channel = pmap_get(uint64_t)(channels, id); - - // channel_decref(channel); remove?? - channel->rpc.closed = true; -} - -// rstream.c:read_event() invokes this as stream->read_cb(). static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, bool eof) { @@ -236,7 +212,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, channel_incref(channel); if (eof) { - close_channel(channel); + channel_close(channel->id, kChannelPartRpc, NULL); char buf[256]; snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", channel->id); @@ -540,43 +516,25 @@ static void unsubscribe(Channel *channel, char *event) xfree(event_string); } -/// Close the channel streams/process and free the channel resources. -/// TODO: move to channel.h -static void close_channel(Channel *channel) + +/// Mark rpc state as closed, and release its reference to the channel. +/// Don't call this directly, call channel_close(id, kChannelPartRpc, &error) +void rpc_close(Channel *channel) { if (channel->rpc.closed) { return; } channel->rpc.closed = true; + channel_decref(channel); - switch (channel->streamtype) { - case kChannelStreamSocket: - stream_close(&channel->stream.socket, NULL, NULL); - break; - case kChannelStreamProc: - // Only close the rpc channel part, - // there could be an error message on the stderr stream - process_close_in(&channel->stream.proc); - process_close_out(&channel->stream.proc); - break; - case kChannelStreamStdio: - stream_close(&channel->stream.stdio.in, NULL, NULL); - stream_close(&channel->stream.stdio.out, NULL, NULL); - multiqueue_put(main_loop.fast_events, exit_event, 1, channel); - return; - case kChannelStreamInternal: - // nothing to free. - break; + if (channel->streamtype == kChannelStreamStdio) { + multiqueue_put(main_loop.fast_events, exit_event, 0); } - - channel_decref(channel); } static void exit_event(void **argv) { - channel_decref(argv[0]); - if (!exiting) { mch_exit(0); } @@ -642,7 +600,7 @@ static void call_set_error(Channel *channel, char *msg, int loglevel) frame->result = STRING_OBJ(cstr_to_string(msg)); } - close_channel(channel); + channel_close(channel->id, kChannelPartRpc, NULL); } static WBuffer *serialize_request(uint64_t channel_id, diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index ec2ebb9d19..e32c6e05d2 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -677,10 +677,6 @@ static void shell_write_cb(Stream *stream, void *data, int status) msg_schedule_emsgf(_("E5677: Error writing input to shell-command: %s"), uv_err_name(status)); } - if (stream->closed) { // Process may have exited before this write. - WLOG("stream was already closed"); - return; - } stream_close(stream, NULL, NULL); } -- cgit From 5af47031773fc647de867444693d1598d0da458d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 9 Jun 2017 08:40:24 +0200 Subject: channels: stderr channel --- src/nvim/channel.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++-- src/nvim/channel.h | 11 ++++++++ src/nvim/eval.c | 79 +++++++++++++++++++++++------------------------------- src/nvim/eval.h | 1 + src/nvim/eval.lua | 3 ++- src/nvim/globals.h | 4 --- src/nvim/main.c | 7 ++--- 7 files changed, 120 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 416e0a1fb6..4d9304472b 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -14,6 +14,12 @@ static bool did_stdio = false; PMap(uint64_t) *channels = NULL; +/// next free id for a job or rpc channel +/// 1 is reserved for stdio channel +/// 2 is reserved for stderr channel +static uint64_t next_chan_id = CHAN_STDERR+1; + + typedef struct { Channel *data; Callback *callback; @@ -73,7 +79,6 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error) } } else if ((part == kChannelPartStdin || part == kChannelPartStdout) && chan->is_rpc) { - // EMSG(_("Invalid stream on rpc job, use jobclose(id, 'rpc')")); *error = (const char *)e_invstreamrpc; return false; } @@ -117,6 +122,21 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error) } break; + case kChannelStreamStderr: + if (part != kChannelPartAll && part != kChannelPartStderr) { + *error = (const char *)e_invstream; + return false; + } + if (!chan->stream.err.closed) { + chan->stream.err.closed = true; + // Don't close on exit, in case late error messages + if (!exiting) { + fclose(stderr); + } + channel_decref(chan); + } + break; + case kChannelStreamInternal: if (!close_main) { *error = (const char *)e_invstream; @@ -132,6 +152,7 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error) void channel_init(void) { channels = pmap_new(uint64_t)(); + channel_alloc(kChannelStreamStderr); rpc_init(); remote_ui_init(); } @@ -143,7 +164,13 @@ void channel_init(void) static Channel *channel_alloc(ChannelStreamType type) { Channel *chan = xcalloc(1, sizeof(*chan)); - chan->id = type == kChannelStreamStdio ? 1 : next_chan_id++; + if (type == kChannelStreamStdio) { + chan->id = CHAN_STDIO; + } else if (type == kChannelStreamStderr) { + chan->id = CHAN_STDERR; + } else { + chan->id = next_chan_id++; + } chan->events = multiqueue_new_child(main_loop.events); chan->refcount = 1; chan->streamtype = type; @@ -403,6 +430,46 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, return channel->id; } +/// @param data will be consumed +size_t channel_send(uint64_t id, char *data, size_t len, const char **error) +{ + Channel *chan = find_channel(id); + if (!chan) { + EMSG(_(e_invchan)); + goto err; + } + + if (chan->streamtype == kChannelStreamStderr) { + if (chan->stream.err.closed) { + *error = _("Can't send data to closed stream"); + goto err; + } + // unbuffered write + size_t written = fwrite(data, len, 1, stderr); + xfree(data); + return len * written; + } + + + Stream *in = channel_instream(chan); + if (in->closed) { + *error = _("Can't send data to closed stream"); + goto err; + } + + if (chan->is_rpc) { + *error = _("Can't send raw data to rpc channel"); + goto err; + } + + WBuffer *buf = wstream_new_buffer(data, len, 1, xfree); + return wstream_write(in, buf) ? len : 0; + +err: + xfree(data); + return 0; +} + // vimscript job callbacks must be executed on Nvim main loop static inline void process_channel_event(Channel *chan, Callback *callback, const char *type, char *buf, diff --git a/src/nvim/channel.h b/src/nvim/channel.h index eaf0fd92d0..ee119756c0 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -9,10 +9,14 @@ #include "nvim/eval/typval.h" #include "nvim/msgpack_rpc/channel_defs.h" +#define CHAN_STDIO 1 +#define CHAN_STDERR 2 + typedef enum { kChannelStreamProc, kChannelStreamSocket, kChannelStreamStdio, + kChannelStreamStderr, kChannelStreamInternal } ChannelStreamType; @@ -30,6 +34,10 @@ typedef struct { Stream out; } StdioPair; +typedef struct { + bool closed; +} StderrState; + typedef struct { Callback cb; garray_T buffer; @@ -56,6 +64,7 @@ struct Channel { PtyProcess pty; Stream socket; StdioPair stdio; + StderrState err; } stream; bool is_rpc; @@ -95,6 +104,7 @@ static inline Stream *channel_instream(Channel *chan) return &chan->stream.stdio.out; case kChannelStreamInternal: + case kChannelStreamStderr: abort(); } abort(); @@ -114,6 +124,7 @@ static inline Stream *channel_outstream(Channel *chan) return &chan->stream.stdio.in; case kChannelStreamInternal: + case kChannelStreamStderr: abort(); } abort(); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ba356f28b9..f92e2d8d65 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -366,6 +366,7 @@ static struct vimvar { VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), + VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_REG, "register", VAR_STRING, VV_RO), VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), @@ -586,6 +587,7 @@ void eval_init(void) v_event->dv_lock = VAR_FIXED; set_vim_var_dict(VV_EVENT, v_event); set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); set_vim_var_nr(VV_COUNT1, 1); @@ -7361,6 +7363,37 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "chansend(id, data)" function +static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { + // First argument is the channel id and second is the data to write + EMSG(_(e_invarg)); + return; + } + + ptrdiff_t input_len = 0; + char *input = save_tv_as_string(&argvars[1], &input_len, false); + if (!input) { + // Either the error has been handled by save_tv_as_string(), + // or there is no input to send. + return; + } + uint64_t id = argvars[0].vval.v_number; + const char *error = NULL; + rettv->vval.v_number = channel_send(id, input, input_len, &error); + if (error) { + EMSG(error); + } +} + /* * "char2nr(string)" function */ @@ -11454,52 +11487,6 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = proc->pid; } -// "jobsend()" function -static void f_jobsend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) { - // First argument is the job id and second is the string or list to write - // to the job's stdin - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_channel(argvars[0].vval.v_number); - if (!data) { - EMSG(_(e_invchan)); - return; - } - - Stream *in = channel_instream(data); - if (in->closed) { - EMSG(_("Can't send data to the job: stdin is closed")); - return; - } - - if (data->is_rpc) { - EMSG(_("Can't send raw data to rpc channel")); - return; - } - - ptrdiff_t input_len = 0; - char *input = save_tv_as_string(&argvars[1], &input_len, false); - if (!input) { - // Either the error has been handled by save_tv_as_string(), or there is no - // input to send. - return; - } - - WBuffer *buf = wstream_new_buffer(input, input_len, 1, xfree); - rettv->vval.v_number = wstream_write(in, buf); -} - // "jobresize(job, width, height)" function static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 7814a086fa..0c0a6881f6 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -56,6 +56,7 @@ typedef enum { VV_DYING, VV_EXCEPTION, VV_THROWPOINT, + VV_STDERR, VV_REG, VV_CMDBANG, VV_INSERTMODE, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index bb03691fd4..54cbc54d78 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -56,6 +56,7 @@ return { ceil={args=1, func="float_op_wrapper", data="&ceil"}, changenr={}, chanclose={args={1, 2}}, + chansend={args=2}, char2nr={args={1, 2}}, cindent={args=1}, clearmatches={}, @@ -177,7 +178,7 @@ return { jobclose={args={1, 2}, func="f_chanclose"}, jobpid={args=1}, jobresize={args=3}, - jobsend={args=2}, + jobsend={args=2, func="f_chansend"}, jobstart={args={1, 2}}, jobstop={args=1}, jobwait={args={1, 2}}, diff --git a/src/nvim/globals.h b/src/nvim/globals.h index d1b0ad0ed3..dcb8b40973 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1199,10 +1199,6 @@ EXTERN bool embedded_mode INIT(= false); // or read/write to stdio (unless embedding) EXTERN bool headless_mode INIT(= false); -/// next free id for a job or rpc channel -/// 1 is reserved for stdio channel -EXTERN uint64_t next_chan_id INIT(= 2); - /// Used to track the status of external functions. /// Currently only used for iconv(). typedef enum { diff --git a/src/nvim/main.c b/src/nvim/main.c index 9059644fc0..aa57913f7c 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -297,7 +297,7 @@ int main(int argc, char **argv) assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX); cmdline_row = (int)(Rows - p_ch); msg_row = cmdline_row; - screenalloc(false); /* allocate screen buffers */ + screenalloc(false); // allocate screen buffers set_init_2(headless_mode); TIME_MSG("inits 2"); @@ -310,8 +310,9 @@ int main(int argc, char **argv) /* Set the break level after the terminal is initialized. */ debug_break_level = params.use_debug_break_level; - bool reading_input = !headless_mode && (params.input_isatty - || params.output_isatty || params.err_isatty); + bool reading_input = !headless_mode + && (params.input_isatty || params.output_isatty + || params.err_isatty); if (reading_input) { // One of the startup commands (arguments, sourced scripts or plugins) may -- cgit From 5517d2323ba359d5ed0cb9f0e9abdfc2a9871894 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 23 Jul 2017 19:23:02 +0200 Subject: channels: reimplement logging (as stub for proper event) --- src/nvim/channel.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/eval.c | 19 +++++++++++++----- 2 files changed, 73 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 4d9304472b..e2ac79794f 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -178,6 +178,62 @@ static Channel *channel_alloc(ChannelStreamType type) return chan; } +/// Not implemented, only logging for now +void channel_create_event(Channel *chan, char *ext_source) +{ +#if MIN_LOG_LEVEL <= INFO_LOG_LEVEL + char *stream_desc, *mode_desc, *source; + + switch (chan->streamtype) { + case kChannelStreamProc: + if (chan->stream.proc.type == kProcessTypePty) { + stream_desc = "pty job"; + } else { + stream_desc = "job"; + } + break; + + case kChannelStreamStdio: + stream_desc = "stdio"; + break; + + case kChannelStreamSocket: + stream_desc = "socket"; + break; + + case kChannelStreamInternal: + stream_desc = "socket (internal)"; + break; + + default: + stream_desc = "?"; + } + + if (chan->is_rpc) { + mode_desc = ", rpc"; + } else if (chan->term) { + mode_desc = ", terminal"; + } else { + mode_desc = ""; + } + + if (ext_source) { + // TODO(bfredl): in a future improved traceback solution, + // external events should be included. + source = ext_source; + } else { + eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); + source = (char *)IObuff; + } + + ILOG("new channel %" PRIu64 " (%s%s): %s", chan->id, stream_desc, + mode_desc, source); +#else + (void)chan; + (void)ext_source; +#endif +} + void channel_incref(Channel *channel) { channel->refcount++; @@ -329,6 +385,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, rstream_init(&proc->err, 0); rstream_start(&proc->err, on_job_stderr, chan); } + *status_out = (varnumber_T)chan->id; return chan; } @@ -369,6 +426,7 @@ uint64_t channel_connect(bool tcp, const char *address, rstream_start(&channel->stream.socket, on_socket_output, channel); } + channel_create_event(channel, NULL); return channel->id; } @@ -384,6 +442,7 @@ void channel_from_connection(SocketWatcher *watcher) wstream_init(&channel->stream.socket, 0); rstream_init(&channel->stream.socket, 0); rpc_start(channel); + channel_create_event(channel, watcher->addr); } /// Creates a loopback channel. This is used to avoid deadlock diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f92e2d8d65..40ee3545b6 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11643,8 +11643,12 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) term_name = tv_dict_get_string(job_opts, "TERM", true); } - channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, rpc, detach, - cwd, width, height, term_name, &rettv->vval.v_number); + Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, + rpc, detach, cwd, width, height, term_name, + &rettv->vval.v_number); + if (chan) { + channel_create_event(chan, NULL); + } } // "jobstop()" function @@ -13872,9 +13876,13 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) // The last item of argv must be NULL argv[i] = NULL; - channel_job_start(argv, CALLBACK_READER_INIT, CALLBACK_READER_INIT, - CALLBACK_NONE, false, true, false, NULL, 0, 0, NULL, - &rettv->vval.v_number); + Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, + CALLBACK_READER_INIT, CALLBACK_NONE, + false, true, false, NULL, 0, 0, NULL, + &rettv->vval.v_number); + if (chan) { + channel_create_event(chan, NULL); + } } // "rpcstop()" function @@ -16672,6 +16680,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) api_clear_error(&err); channel_terminal_open(chan); + channel_create_event(chan, NULL); } // "test_garbagecollect_now()" function -- cgit From f629f8312d2a830ce7999a6612203977ec83daf8 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 25 Jul 2017 11:59:08 +0200 Subject: channels: refactor jobwait --- src/nvim/channel.c | 8 +++--- src/nvim/channel.h | 2 -- src/nvim/eval.c | 71 ++++++++++++++++++++++-------------------------- src/nvim/event/process.c | 11 ++++---- src/nvim/event/process.h | 2 +- 5 files changed, 43 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index e2ac79794f..019bd1545f 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -639,12 +639,12 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) terminal_close(chan->term, msg); } - if (chan->status_ptr) { - *chan->status_ptr = status; + // if status is -1 the process did not really exit, + // we just closed the handle onto a detached process + if (status >= 0) { + process_channel_event(chan, &chan->on_exit, "exit", NULL, 0, status); } - process_channel_event(chan, &chan->on_exit, "exit", NULL, 0, status); - channel_decref(chan); } diff --git a/src/nvim/channel.h b/src/nvim/channel.h index ee119756c0..48c21e37f8 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -74,8 +74,6 @@ struct Channel { CallbackReader on_stdout; CallbackReader on_stderr; Callback on_exit; - - varnumber_T *status_ptr; // TODO: refactor? }; EXTERN PMap(uint64_t) *channels; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 40ee3545b6..5fa92cedbd 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11693,28 +11693,31 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + list_T *args = argvars[0].vval.v_list; - list_T *rv = tv_list_alloc(); + Channel **jobs = xcalloc(args->lv_len, sizeof(*jobs)); ui_busy_start(); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); // For each item in the input list append an integer to the output list. -3 // is used to represent an invalid job id, -2 is for a interrupted job and // -1 for jobs that were skipped or timed out. - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - Channel *data = NULL; + + int i = 0; + for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next, i++) { + Channel *chan = NULL; if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number, false))) { - tv_list_append_number(rv, -3); + || !(chan = find_job(arg->li_tv.vval.v_number, false))) { + jobs[i] = NULL; } else { - // append the list item and set the status pointer so we'll collect the - // status code when the job exits - tv_list_append_number(rv, -1); - data->status_ptr = &rv->lv_last->li_tv.vval.v_number; - // Process any pending events for the job because we'll temporarily - // replace the parent queue - multiqueue_process_events(data->events); - multiqueue_replace_parent(data->events, waiting_jobs); + jobs[i] = chan; + channel_incref(chan); + if (chan->stream.proc.status < 0) { + // Process any pending events for the job because we'll temporarily + // replace the parent queue + multiqueue_process_events(chan->events); + multiqueue_replace_parent(chan->events, waiting_jobs); + } } } @@ -11725,25 +11728,21 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) before = os_hrtime(); } - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - Channel *data = NULL; + for (i = 0; i < args->lv_len; i++) { if (remaining == 0) { // timed out break; } - if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number, false))) { + + // if the job already exited, but wasn't freed yet + if (jobs[i] == NULL || jobs[i]->stream.proc.status >= 0) { continue; } - int status = process_wait((Process *)&data->stream.proc, remaining, + + int status = process_wait(&jobs[i]->stream.proc, remaining, waiting_jobs); if (status < 0) { // interrupted or timed out, skip remaining jobs. - if (status == -2) { - // set the status so the user can distinguish between interrupted and - // skipped/timeout jobs. - *data->status_ptr = -2; - } break; } if (remaining > 0) { @@ -11756,30 +11755,24 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - Channel *data = NULL; - if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number, false))) { - continue; - } - // remove the status pointer because the list may be freed before the - // job exits - data->status_ptr = NULL; - } + list_T *rv = tv_list_alloc(); // restore the parent queue for any jobs still alive - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - Channel *data = NULL; - if (arg->li_tv.v_type != VAR_NUMBER - || !(data = find_job(arg->li_tv.vval.v_number, false))) { + for (i = 0; i < args->lv_len; i++) { + if (jobs[i] == NULL) { + tv_list_append_number(rv, -3); continue; } // restore the parent queue for the job - multiqueue_process_events(data->events); - multiqueue_replace_parent(data->events, main_loop.events); + multiqueue_process_events(jobs[i]->events); + multiqueue_replace_parent(jobs[i]->events, main_loop.events); + + tv_list_append_number(rv, jobs[i]->stream.proc.status); + channel_decref(jobs[i]); } multiqueue_free(waiting_jobs); + xfree(jobs); ui_busy_stop(); rv->lv_refcount++; rettv->v_type = VAR_LIST; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 34be291aef..4eb2dd0baf 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -145,16 +145,15 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL /// @param process Process instance /// @param ms Time in milliseconds to wait for the process. /// 0 for no wait. -1 to wait until the process quits. -/// @return Exit code of the process. +/// @return Exit code of the process. proc->status will have the same value. /// -1 if the timeout expired while the process is still running. /// -2 if the user interruped the wait. int process_wait(Process *proc, int ms, MultiQueue *events) FUNC_ATTR_NONNULL_ARG(1) { - int status = -1; // default bool interrupted = false; if (!proc->refcount) { - status = proc->status; + int status = proc->status; LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0); return status; } @@ -190,7 +189,9 @@ int process_wait(Process *proc, int ms, MultiQueue *events) if (proc->refcount == 1) { // Job exited, collect status and manually invoke close_cb to free the job // resources - status = interrupted ? -2 : proc->status; + if (interrupted) { + proc->status = -2; + } decref(proc); if (events) { // the decref call created an exit event, process it now @@ -200,7 +201,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events) proc->refcount--; } - return status; + return proc->status; } /// Ask a process to terminate and eventually kill if it doesn't respond diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index a8d066c291..033ce3604b 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -39,7 +39,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) .loop = loop, .events = NULL, .pid = 0, - .status = 0, + .status = -1, .refcount = 0, .stopped_time = 0, .cwd = NULL, -- cgit From fee367a74f3269fd0543bae128c8aaee21f5e592 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 27 Aug 2017 12:42:26 +0200 Subject: channels: more consistent event handling terminal: libvterm now receives data in async context. This was "almost" safe already, as redraws were queued anyway. --- src/nvim/channel.c | 45 ++++++++++++++++++++------------------------- src/nvim/terminal.c | 5 ++--- 2 files changed, 22 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 019bd1545f..1c34b2ffce 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -21,7 +21,7 @@ static uint64_t next_chan_id = CHAN_STDERR+1; typedef struct { - Channel *data; + Channel *chan; Callback *callback; const char *type; list_T *received; @@ -348,9 +348,6 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, has_err = callback_reader_set(chan->on_stderr); } int status = process_spawn(proc, true, has_out, has_err); - if (has_err) { - proc->err.events = chan->events; - } if (status) { EMSG3(_(e_jobspawn), os_strerror(status), cmd); xfree(cmd); @@ -372,10 +369,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, // the rpc takes over the in and out streams rpc_start(chan); } else { - proc->in.events = chan->events; if (has_out) { callback_reader_start(&chan->on_stdout); - proc->out.events = chan->events; rstream_start(&proc->out, on_job_stdout, chan); } } @@ -422,7 +417,6 @@ uint64_t channel_connect(bool tcp, const char *address, } else { channel->on_stdout = on_output; callback_reader_start(&channel->on_stdout); - channel->stream.socket.events = channel->events; rstream_start(&channel->stream.socket, on_socket_output, channel); } @@ -481,8 +475,6 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, } else { channel->on_stdout = on_output; callback_reader_start(&channel->on_stdout); - channel->stream.stdio.in.events = channel->events; - channel->stream.stdio.out.events = channel->events; rstream_start(&channel->stream.stdio.in, on_stdio_input, channel); } @@ -534,10 +526,11 @@ static inline void process_channel_event(Channel *chan, Callback *callback, const char *type, char *buf, size_t count, int status) { - ChannelEvent event_data; - event_data.received = NULL; + assert(callback); + ChannelEvent *event_data = xmalloc(sizeof(*event_data)); + event_data->received = NULL; if (buf) { - event_data.received = tv_list_alloc(); + event_data->received = tv_list_alloc(); char *ptr = buf; size_t remaining = count; size_t off = 0; @@ -545,7 +538,7 @@ static inline void process_channel_event(Channel *chan, Callback *callback, while (off < remaining) { // append the line if (ptr[off] == NL) { - tv_list_append_string(event_data.received, ptr, (ssize_t)off); + tv_list_append_string(event_data->received, ptr, (ssize_t)off); size_t skip = off + 1; ptr += skip; remaining -= skip; @@ -558,14 +551,16 @@ static inline void process_channel_event(Channel *chan, Callback *callback, } off++; } - tv_list_append_string(event_data.received, ptr, (ssize_t)off); + tv_list_append_string(event_data->received, ptr, (ssize_t)off); } else { - event_data.status = status; + event_data->status = status; } - event_data.data = chan; - event_data.callback = callback; - event_data.type = type; - on_channel_event(&event_data); + channel_incref(chan); // Hold on ref to callback + event_data->chan = chan; + event_data->callback = callback; + event_data->type = type; + + multiqueue_put(chan->events, on_channel_event, 1, event_data); } void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, @@ -608,7 +603,7 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, if (eof) { if (reader->buffered) { process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, - (size_t)reader->buffer.ga_len, 0); + (size_t)reader->buffer.ga_len, 0); ga_clear(&reader->buffer); } else if (callback_reader_set(*reader)) { process_channel_event(chan, &reader->cb, type, ptr, 0, 0); @@ -648,17 +643,15 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) channel_decref(chan); } -static void on_channel_event(ChannelEvent *ev) +static void on_channel_event(void **args) { - if (!ev->callback) { - return; - } + ChannelEvent *ev = (ChannelEvent *)args[0]; typval_T argv[4]; argv[0].v_type = VAR_NUMBER; argv[0].v_lock = VAR_UNLOCKED; - argv[0].vval.v_number = (varnumber_T)ev->data->id; + argv[0].vval.v_number = (varnumber_T)ev->chan->id; if (ev->received) { argv[1].v_type = VAR_LIST; @@ -678,6 +671,8 @@ static void on_channel_event(ChannelEvent *ev) typval_T rettv = TV_INITIAL_VALUE; callback_call(ev->callback, 3, argv, &rettv); tv_clear(&rettv); + channel_decref(ev->chan); + xfree(ev); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 1dac9c69bd..dfa758f41e 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1094,11 +1094,12 @@ static void refresh_terminal(Terminal *term) // Calls refresh_terminal() on all invalidated_terminals. static void refresh_timer_cb(TimeWatcher *watcher, void *data) { + refresh_pending = false; if (exiting // Cannot redraw (requires event loop) during teardown/exit. // WM_LIST (^D) is not redrawn, unlike the normal wildmenu. So we must // skip redraws to keep it visible. || wild_menu_showing == WM_LIST) { - goto end; + return; } Terminal *term; void *stub; (void)(stub); @@ -1113,8 +1114,6 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) if (any_visible) { redraw(true); } -end: - refresh_pending = false; } static void refresh_size(Terminal *term, buf_T *buf) -- cgit From a97cdff14df1bb788a4b659e0db94e2b2ba1f539 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 17 Sep 2017 16:23:39 +0200 Subject: channels: improvements to buffering --- src/nvim/channel.c | 70 +++++++++++++++++++++++++++++++++--------------------- src/nvim/channel.h | 4 +++- src/nvim/eval.c | 25 ++++++++++++++++++- 3 files changed, 70 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 1c34b2ffce..40af470bde 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -257,7 +257,8 @@ void callback_reader_free(CallbackReader *reader) void callback_reader_start(CallbackReader *reader) { if (reader->buffered) { - ga_init(&reader->buffer, sizeof(char *), 1); + ga_init(&reader->buffer, sizeof(char *), 32); + ga_grow(&reader->buffer, 32); } } @@ -521,6 +522,34 @@ err: return 0; } +/// NB: mutates buf in place! +static list_T *buffer_to_tv_list(char *buf, size_t count) +{ + list_T *ret = tv_list_alloc(); + char *ptr = buf; + size_t remaining = count; + size_t off = 0; + + while (off < remaining) { + // append the line + if (ptr[off] == NL) { + tv_list_append_string(ret, ptr, (ssize_t)off); + size_t skip = off + 1; + ptr += skip; + remaining -= skip; + off = 0; + continue; + } + if (ptr[off] == NUL) { + // Translate NUL to NL + ptr[off] = NL; + } + off++; + } + tv_list_append_string(ret, ptr, (ssize_t)off); + return ret; +} + // vimscript job callbacks must be executed on Nvim main loop static inline void process_channel_event(Channel *chan, Callback *callback, const char *type, char *buf, @@ -530,28 +559,7 @@ static inline void process_channel_event(Channel *chan, Callback *callback, ChannelEvent *event_data = xmalloc(sizeof(*event_data)); event_data->received = NULL; if (buf) { - event_data->received = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(event_data->received, ptr, (ssize_t)off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(event_data->received, ptr, (ssize_t)off); + event_data->received = buffer_to_tv_list(buf, count); } else { event_data->status = status; } @@ -602,10 +610,18 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, if (eof) { if (reader->buffered) { - process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, - (size_t)reader->buffer.ga_len, 0); - ga_clear(&reader->buffer); - } else if (callback_reader_set(*reader)) { + if (reader->cb.type != kCallbackNone) { + process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, + (size_t)reader->buffer.ga_len, 0); + ga_clear(&reader->buffer); + } else if (reader->self) { + list_T *data = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); + tv_dict_add_list(reader->self, type, strlen(type), data); + } else { + abort(); + } + } else if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, ptr, 0, 0); } return; diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 48c21e37f8..b856d197f1 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -40,16 +40,18 @@ typedef struct { typedef struct { Callback cb; + dict_T *self; garray_T buffer; bool buffered; } CallbackReader; #define CALLBACK_READER_INIT ((CallbackReader){ .cb = CALLBACK_NONE, \ + .self = NULL, \ .buffer = GA_EMPTY_INIT_VALUE, \ .buffered = false }) static inline bool callback_reader_set(CallbackReader reader) { - return reader.cb.type != kCallbackNone; + return reader.cb.type != kCallbackNone || reader.self; } struct Channel { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5fa92cedbd..577aa67c60 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -15090,6 +15090,9 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } on_data.buffered = tv_dict_get_number(opts, "data_buffered"); + if (on_data.buffered && on_data.cb.type == kCallbackNone) { + on_data.self = opts; + } } const char *error = NULL; @@ -15490,6 +15493,10 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { return; } + on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); + if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { + on_stdin.self = opts; + } const char *error; uint64_t id = channel_from_stdio(rpc, on_stdin, &error); @@ -16764,7 +16771,17 @@ static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack) { - return set_ref_in_callback(&reader->cb, copyID, ht_stack, list_stack); + if (set_ref_in_callback(&reader->cb, copyID, ht_stack, list_stack)) { + return true; + } + + if (reader->self) { + typval_T tv; + tv.v_type = VAR_DICT; + tv.vval.v_dict = reader->self; + return set_ref_in_item(&tv, copyID, ht_stack, list_stack); + } + return false; } static void add_timer_info(typval_T *rettv, timer_T *timer) @@ -22344,6 +22361,12 @@ static inline bool common_job_callbacks(dict_T *vopts, && tv_dict_get_callback(vopts, S_LEN("on_exit"), on_exit)) { on_stdout->buffered = tv_dict_get_number(vopts, "stdout_buffered"); on_stderr->buffered = tv_dict_get_number(vopts, "stderr_buffered"); + if (on_stdout->buffered && on_stdout->cb.type == kCallbackNone) { + on_stdout->self = vopts; + } + if (on_stderr->buffered && on_stderr->cb.type == kCallbackNone) { + on_stderr->self = vopts; + } vopts->dv_refcount++; return true; } -- cgit From 9acd7bfe25b5ea2b31ffbbdbd201f5f09afc4237 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 10 Jul 2017 01:58:54 +0200 Subject: tui: job-control: use saved termios for pty jobs On startup, if running in a terminal, save the termios properties. Use the saved termios for `:terminal` and `jobstart()` pty jobs. This won't affect nvim spawned outside of a terminal. questions: - This affects `:terminal` and `jobstart({'pty':v:true})`. Should we be more conservative for `jobstart({'pty':v:true})` (e.g. pass NULL to forkpty() and let the OS defaults prevail)? - Note: `iutf8` would not be set in that case. --- src/nvim/main.c | 11 +++++++++++ src/nvim/os/pty_process_unix.c | 21 +++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index aa57913f7c..0346414697 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -73,6 +73,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/handle.h" #include "nvim/api/private/dispatch.h" +#ifndef WIN32 +# include "nvim/os/pty_process_unix.h" +#endif /* Maximum number of commands from + or -c arguments. */ #define MAX_ARG_CMDS 10 @@ -1247,6 +1250,14 @@ static void check_and_set_isatty(mparm_T *paramp) stdout_isatty = paramp->output_isatty = os_isatty(fileno(stdout)); paramp->err_isatty = os_isatty(fileno(stderr)); + int tty_fd = paramp->input_isatty + ? OS_STDIN_FILENO + : (paramp->output_isatty + ? OS_STDOUT_FILENO + : (paramp->err_isatty ? OS_STDERR_FILENO : -1)); +#ifndef WIN32 + pty_process_save_termios(tty_fd); +#endif TIME_MSG("window checked"); } diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index e39f837c1a..53301e4b53 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -36,13 +36,26 @@ # include "os/pty_process_unix.c.generated.h" #endif +/// termios saved at startup (for TUI) or initialized by pty_process_spawn(). +static struct termios termios_default; + +/// Saves the termios properties associated with `tty_fd`. +/// +/// @param tty_fd TTY file descriptor, or -1 if not in a terminal. +void pty_process_save_termios(int tty_fd) +{ + if (tty_fd == -1 || tcgetattr(tty_fd, &termios_default) != 0) { + return; + } +} + /// @returns zero on success, or negative error code int pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { - static struct termios termios; - if (!termios.c_cflag) { - init_termios(&termios); + if (!termios_default.c_cflag) { + // TODO(jkeyes): We could pass NULL to forkpty() instead ... + init_termios(&termios_default); } int status = 0; // zero or negative error code (libuv convention) @@ -52,7 +65,7 @@ int pty_process_spawn(PtyProcess *ptyproc) ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 }; uv_disable_stdio_inheritance(); int master; - int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize); + int pid = forkpty(&master, NULL, &termios_default, &ptyproc->winsize); if (pid < 0) { status = -errno; -- cgit From 85bc6630c0a8259c713383c8787e65f92e24e600 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 6 Sep 2017 08:16:59 +0200 Subject: input: only change mode of input fd if there is an input fd --- src/nvim/if_cscope.c | 1 - src/nvim/misc1.c | 5 ++++- src/nvim/os/input.c | 2 +- src/nvim/os_unix.c | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 0f9ecdf2d7..6834e7a802 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -778,7 +778,6 @@ err_closing: if (execl("/bin/sh", "sh", "-c", cmd, (char *)NULL) == -1) PERROR(_("cs_create_connection exec failed")); - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) exit(127); /* NOTREACHED */ default: /* parent. */ diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 137de84953..f7ee2950ef 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2622,7 +2622,10 @@ void preserve_exit(void) // Prevent repeated calls into this method. if (really_exiting) { - stream_set_blocking(input_global_fd(), true); //normalize stream (#2598) + if (input_global_fd() >= 0) { + // normalize stream (#2598) + stream_set_blocking(input_global_fd(), true); + } exit(2); } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 31e06ce404..7d6f2abd7f 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -37,7 +37,7 @@ typedef enum { static Stream read_stream = {.closed = true}; static RBuffer *input_buffer = NULL; static bool input_eof = false; -static int global_fd = 0; +static int global_fd = -1; static int events_enabled = 0; static bool blocking = false; diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 692bcc97f4..d7ba675b68 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -144,7 +144,9 @@ void mch_exit(int r) FUNC_ATTR_NORETURN if (!event_teardown() && r == 0) { r = 1; // Exit with error if main_loop did not teardown gracefully. } - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + if (input_global_fd() >= 0) { + stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + } #ifdef EXITFREE free_all_mem(); -- cgit From b1a4db0b69e4744f9a4b4e4844021bb9026f03a6 Mon Sep 17 00:00:00 2001 From: nate Date: Thu, 9 Nov 2017 13:33:37 -0800 Subject: :highlight : avoid redraw on error do_highlight() should not redraw if a validation error occurred. closes #7489 --- src/nvim/syntax.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index d28e996581..bc7362af72 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6648,7 +6648,7 @@ do_highlight(char_u *line, int forceit, int init) { if (error && idx == highlight_ga.ga_len) { syn_unadd_group(); } else { - if (is_normal_group) { + if (!error && is_normal_group) { // Need to update all groups, because they might be using "bg" and/or // "fg", which have been changed now. highlight_attr_set_all(); -- cgit