From 792c2903435ceda05e68007d7bee344f65ee3a4f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 16 Sep 2019 21:02:49 -0400 Subject: vim-patch:8.0.1523: cannot write and read terminal screendumps Problem: Cannot write and read terminal screendumps. Solution: Add term_dumpwrite(), term_dumpread() and term_dumpdiff(). Also add assert_equalfile(). https://github.com/vim/vim/commit/d96ff165113ce5fe62107add590997660e3d4802 --- src/nvim/eval.c | 56 ++++++++++++++++++++++++++++++++++++++++ src/nvim/eval.lua | 1 + src/nvim/testdir/test_assert.vim | 35 +++++++++++++++++++++++++ 3 files changed, 92 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1f753608d2..1a2bdcf88f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6961,6 +6961,56 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype) } } +static void assert_equalfile(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); + garray_T ga; + + if (fname1 == NULL || fname2 == NULL) { + return; + } + + IObuff[0] = NUL; + FILE *const fd1 = os_fopen(fname1, READBIN); + if (fd1 == NULL) { + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + } else { + FILE *const fd2 = os_fopen(fname2, READBIN); + if (fd2 == NULL) { + fclose(fd1); + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); + } else { + for (int64_t count = 0; ; count++) { + const int c1 = fgetc(fd1); + const int c2 = fgetc(fd2); + if (c1 == EOF) { + if (c2 != EOF) { + STRCPY(IObuff, "first file is shorter"); + } + break; + } else if (c2 == EOF) { + STRCPY(IObuff, "second file is shorter"); + break; + } else if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64, count); + break; + } + } + } + } + if (IObuff[0] != NUL) { + prepare_assert_error(&ga); + ga_concat(&ga, IObuff); + assert_error(&ga); + ga_clear(&ga); + } +} + static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); @@ -6988,6 +7038,12 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) assert_equal_common(argvars, ASSERT_EQUAL); } +// "assert_equalfile(fname-one, fname-two)" function +static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + assert_equalfile(argvars); +} + // "assert_notequal(expected, actual[, msg])" function static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index ab5ff57c2f..8efbcc71f1 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -28,6 +28,7 @@ return { asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1, 2}}, assert_equal={args={2, 3}}, + assert_equalfile={args=2}, assert_exception={args={1, 2}}, assert_fails={args={1, 2}}, assert_false={args={1, 2}}, diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index a4c8ce7e43..cbb65ffc01 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -1,5 +1,40 @@ " Test that the methods used for testing work. +func Test_assert_equalfile() + call assert_equalfile('abcabc', 'xyzxyz') + call assert_match("E485: Can't read file abcabc", v:errors[0]) + call remove(v:errors, 0) + + let goodtext = ["one", "two", "three"] + call writefile(goodtext, 'Xone') + call assert_equalfile('Xone', 'xyzxyz') + call assert_match("E485: Can't read file xyzxyz", v:errors[0]) + call remove(v:errors, 0) + + call writefile(goodtext, 'Xtwo') + call assert_equalfile('Xone', 'Xtwo') + + call writefile([goodtext[0]], 'Xone') + call assert_equalfile('Xone', 'Xtwo') + call assert_match("first file is shorter", v:errors[0]) + call remove(v:errors, 0) + + call writefile(goodtext, 'Xone') + call writefile([goodtext[0]], 'Xtwo') + call assert_equalfile('Xone', 'Xtwo') + call assert_match("second file is shorter", v:errors[0]) + call remove(v:errors, 0) + + call writefile(['1234X89'], 'Xone') + call writefile(['1234Y89'], 'Xtwo') + call assert_equalfile('Xone', 'Xtwo') + call assert_match("difference at byte 4", v:errors[0]) + call remove(v:errors, 0) + + call delete('Xone') + call delete('Xtwo') +endfunc + func Test_assert_fails_in_try_block() try call assert_equal(0, assert_fails('throw "error"')) -- cgit From 8db9e82e3e1aa094ca9224b01384da1b07fda410 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 16 Sep 2019 22:26:41 -0400 Subject: vim-patch:8.0.1770: assert functions don't return anything Problem: Assert functions don't return anything. Solution: Return non-zero when the assertion fails. https://github.com/vim/vim/commit/65a5464985f980d2bbbf4e14d39d416dce065ec7 --- src/nvim/eval.c | 93 ++++++++++++++++++++++++++-------------- src/nvim/testdir/test_assert.vim | 12 +++--- 2 files changed, 68 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1a2bdcf88f..248edfc9cc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6947,7 +6947,8 @@ static void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -static void assert_equal_common(typval_T *argvars, assert_type_T atype) +static int assert_equal_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -6958,10 +6959,12 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype) &argvars[0], &argvars[1], atype); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; } -static void assert_equalfile(typval_T *argvars) +static int assert_equalfile(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -6971,7 +6974,7 @@ static void assert_equalfile(typval_T *argvars) garray_T ga; if (fname1 == NULL || fname2 == NULL) { - return; + return 0; } IObuff[0] = NUL; @@ -7008,13 +7011,16 @@ static void assert_equalfile(typval_T *argvars) ga_concat(&ga, IObuff); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; } static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; + int ret = 0; called_vim_beep = false; suppress_errthrow = true; @@ -7026,28 +7032,30 @@ static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_concat(&ga, (const char_u *)cmd); assert_error(&ga); ga_clear(&ga); + ret = 1; } suppress_errthrow = false; emsg_on_display = false; + rettv->vval.v_number = ret; } // "assert_equal(expected, actual[, msg])" function static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_equal_common(argvars, ASSERT_EQUAL); + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } // "assert_equalfile(fname-one, fname-two)" function static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_equalfile(argvars); + rettv->vval.v_number = assert_equalfile(argvars); } // "assert_notequal(expected, actual[, msg])" function static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_equal_common(argvars, ASSERT_NOTEQUAL); + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); } /// "assert_report(msg) @@ -7059,27 +7067,13 @@ static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); assert_error(&ga); ga_clear(&ga); + rettv->vval.v_number = 1; } /// "assert_exception(string[, msg])" function static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, (char_u *)"v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - } + rettv->vval.v_number = assert_exception(argvars); } /// "assert_fails(cmd [, error])" function @@ -7087,6 +7081,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; + int ret = 0; int save_trylevel = trylevel; // trylevel must be zero for a ":throw" command to be considered failed @@ -7102,6 +7097,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_concat(&ga, (const char_u *)cmd); assert_error(&ga); ga_clear(&ga); + ret = 1; } else if (argvars[1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; const char *const error = tv_get_string_buf_chk(&argvars[1], buf); @@ -7113,6 +7109,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); + ret = 1; } } @@ -7122,9 +7119,11 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg_silent = false; emsg_on_display = false; set_vim_var_string(VV_ERRMSG, NULL, 0); + rettv->vval.v_number = ret; } -void assert_inrange(typval_T *argvars) +static int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { bool error = false; const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); @@ -7132,7 +7131,7 @@ void assert_inrange(typval_T *argvars) const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); if (error) { - return; + return 0; } if (actual < lower || actual > upper) { garray_T ga; @@ -7146,11 +7145,14 @@ void assert_inrange(typval_T *argvars) ASSERT_INRANGE); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; } // Common for assert_true() and assert_false(). -static void assert_bool(typval_T *argvars, bool is_true) +static int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL { bool error = false; garray_T ga; @@ -7169,16 +7171,43 @@ static void assert_bool(typval_T *argvars, bool is_true) NULL, &argvars[0], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; +} + +static int assert_exception(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + + const char *const error = tv_get_string_chk(&argvars[0]); + if (vimvars[VV_EXCEPTION].vv_str == NULL) { + prepare_assert_error(&ga); + ga_concat(&ga, (char_u *)"v:exception is not set"); + assert_error(&ga); + ga_clear(&ga); + return 1; + } else if (error != NULL + && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; } // "assert_false(actual[, msg])" function static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_bool(argvars, false); + rettv->vval.v_number = assert_bool(argvars, false); } -static void assert_match_common(typval_T *argvars, assert_type_T atype) +static int assert_match_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -7194,31 +7223,33 @@ static void assert_match_common(typval_T *argvars, assert_type_T atype) fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); assert_error(&ga); ga_clear(&ga); + return 1; } + return 0; } /// "assert_inrange(lower, upper[, msg])" function static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_inrange(argvars); + rettv->vval.v_number = assert_inrange(argvars); } /// "assert_match(pattern, actual[, msg])" function static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_match_common(argvars, ASSERT_MATCH); + rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); } /// "assert_notmatch(pattern, actual[, msg])" function static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_match_common(argvars, ASSERT_NOTMATCH); + rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); } // "assert_true(actual[, msg])" function static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - assert_bool(argvars, true); + rettv->vval.v_number = assert_bool(argvars, true); } /* diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index cbb65ffc01..4cc90eca7a 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -1,33 +1,33 @@ " Test that the methods used for testing work. func Test_assert_equalfile() - call assert_equalfile('abcabc', 'xyzxyz') + call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz')) call assert_match("E485: Can't read file abcabc", v:errors[0]) call remove(v:errors, 0) let goodtext = ["one", "two", "three"] call writefile(goodtext, 'Xone') - call assert_equalfile('Xone', 'xyzxyz') + call assert_equal(1, assert_equalfile('Xone', 'xyzxyz')) call assert_match("E485: Can't read file xyzxyz", v:errors[0]) call remove(v:errors, 0) call writefile(goodtext, 'Xtwo') - call assert_equalfile('Xone', 'Xtwo') + call assert_equal(0, assert_equalfile('Xone', 'Xtwo')) call writefile([goodtext[0]], 'Xone') - call assert_equalfile('Xone', 'Xtwo') + call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) call assert_match("first file is shorter", v:errors[0]) call remove(v:errors, 0) call writefile(goodtext, 'Xone') call writefile([goodtext[0]], 'Xtwo') - call assert_equalfile('Xone', 'Xtwo') + call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) call assert_match("second file is shorter", v:errors[0]) call remove(v:errors, 0) call writefile(['1234X89'], 'Xone') call writefile(['1234Y89'], 'Xtwo') - call assert_equalfile('Xone', 'Xtwo') + call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) call assert_match("difference at byte 4", v:errors[0]) call remove(v:errors, 0) -- cgit From 1070c092c7bf989f261047b861165e61e94c1941 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 18 Sep 2019 18:22:38 +0200 Subject: win_update: fix redraw regression (#11027) Before 6e9ea5adc `win_ins_lines` would return `FAIL` for `i/line_count == 0`. Handle this by checking it in the outer `if`. Ref: https://github.com/neovim/neovim/commit/6e9ea5ad#commitcomment-35084669 --- src/nvim/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 5bcd2c808d..25dd3aad7e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -871,7 +871,7 @@ static void win_update(win_T *wp) if (wp->w_lines[0].wl_lnum != wp->w_topline) i += diff_check_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; - if (i < wp->w_grid.Rows - 2) { // less than a screen off + if (i != 0 && i < wp->w_grid.Rows - 2) { // less than a screen off // Try to insert the correct number of lines. // If not the last window, delete the lines at the bottom. // win_ins_lines may fail when the terminal can't do it. -- cgit From 828a6e75681143a04d60be8d4f8a35dc40b6cd60 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 20 Sep 2019 09:37:43 +0200 Subject: screen: fix vcol counting with virtual text. Fixes #9941 --- src/nvim/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 25dd3aad7e..f4aa10ecf5 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -4007,7 +4007,7 @@ win_line ( break; } - ++vcol; + vcol += cells; } } -- cgit From 690cd4f012cef0ed1817ad55db84dc3d0a51e2d0 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 18 Sep 2019 21:52:19 -0400 Subject: vim-patch:8.1.1783: MS-Windows: compiler test may fail when using %:S Problem: MS-Windows: compiler test may fail when using %:S. Solution: Reset 'shellslash'. https://github.com/vim/vim/commit/dff2adc8ddcb6c8f3390a82c321362f8d6756fb8 --- src/nvim/testdir/test_compiler.vim | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 46c14d8bc3..f561e84a38 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -10,6 +10,10 @@ func Test_compiler() unlet $LANG endif + " %:S does not work properly with 'shellslash' set + let save_shellslash = &shellslash + set noshellslash + e Xfoo.pl compiler perl call assert_equal('perl', b:current_compiler) @@ -27,6 +31,7 @@ func Test_compiler() call assert_match("\n 1 Xfoo.pl:3: Global symbol \"\$foo\" " \ . "requires explicit package name", a) + let &shellslash = save_shellslash call delete('Xfoo.pl') bw! endfunc -- cgit From 1c71a3c657ed7668de0d0fc3fae928d8857a62cb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 18 Sep 2019 21:52:37 -0400 Subject: vim-patch:8.1.2054: compiler test for Perl may fail Problem: Compiler test for Perl may fail. Solution: Accept any error line number. (James McCoy, closes vim/vim#4944) https://github.com/vim/vim/commit/cebfcffa40c058119bc2f92f0db02dffd3f6affe --- src/nvim/testdir/test_compiler.vim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index f561e84a38..40d3cdbdae 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -28,8 +28,9 @@ func Test_compiler() w! call feedkeys(":make\\", 'tx') let a=execute('clist') - call assert_match("\n 1 Xfoo.pl:3: Global symbol \"\$foo\" " - \ . "requires explicit package name", a) + call assert_match('\n \d\+ Xfoo.pl:3: Global symbol "$foo" ' + \ . 'requires explicit package name', a) + let &shellslash = save_shellslash call delete('Xfoo.pl') -- cgit From 42a05130955829847e68c1af5add386596b697fd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 19 Sep 2019 20:41:08 -0400 Subject: vim-patch:8.1.2058: function for ex command is named inconsistently Problem: Function for ex command is named inconsistently. Solution: Rename do_marks() to ex_marks(). https://github.com/vim/vim/commit/4bd782339e370bde82c2a8976df9f335cc12eba9 --- src/nvim/ex_cmds.lua | 2 +- src/nvim/mark.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 8c0d22809f..a709acd4ef 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1632,7 +1632,7 @@ return { command='marks', flags=bit.bor(EXTRA, TRLBAR, CMDWIN), addr_type=ADDR_LINES, - func='do_marks', + func='ex_marks', }, { command='match', diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e103d3cb55..e8f1651a6e 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -616,7 +616,7 @@ static char_u *mark_line(pos_T *mp, int lead_len) /* * print the marks */ -void do_marks(exarg_T *eap) +void ex_marks(exarg_T *eap) { char_u *arg = eap->arg; int i; -- cgit From b853b6e4ea1269ab7ae766bd71d9bafd54dc2b98 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Sep 2019 01:31:01 -0400 Subject: vim-patch:8.0.1109: timer causes error on exit from Ex mode Problem: Timer causes error on exit from Ex mode. (xtal8) Solution: save and restore the ex_pressedreturn flag. (Christian Brabandt, closes vim/vim#2079) https://github.com/vim/vim/commit/f5291f301e9322545f0621b2157e93050d1d4fb3 --- src/nvim/eval.c | 2 ++ src/nvim/ex_docmd.c | 17 ++++++++++++++--- src/nvim/testdir/test_timers.vim | 11 +++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 248edfc9cc..04204e45d0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18437,6 +18437,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data) timer_T *timer = (timer_T *)data; int save_did_emsg = did_emsg; int save_called_emsg = called_emsg; + const bool save_ex_pressedreturn = get_pressedreturn(); if (timer->stopped || timer->paused) { return; @@ -18465,6 +18466,7 @@ static void timer_due_cb(TimeWatcher *tw, void *data) } did_emsg = save_did_emsg; called_emsg = save_called_emsg; + set_pressedreturn(save_ex_pressedreturn); if (timer->emsg_count >= 3) { timer_stop(timer); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 72d39adb3e..a6931f3acd 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -77,7 +77,7 @@ #include "nvim/api/private/helpers.h" static int quitmore = 0; -static int ex_pressedreturn = FALSE; +static bool ex_pressedreturn = false; /// Whether ":lcd" or ":tcd" was produced for a session. static int did_lcd; @@ -1278,14 +1278,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, || getline_equal(fgetline, cookie, getexline)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { ea.cmd = (char_u *)"+"; - ex_pressedreturn = TRUE; + ex_pressedreturn = true; } /* ignore comment and empty lines */ if (*ea.cmd == '"') goto doend; if (*ea.cmd == NUL) { - ex_pressedreturn = TRUE; + ex_pressedreturn = true; goto doend; } @@ -10131,6 +10131,17 @@ static void ex_folddo(exarg_T *eap) ml_clearmarked(); // clear rest of the marks } +bool get_pressedreturn(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return ex_pressedreturn; +} + +void set_pressedreturn(bool val) +{ + ex_pressedreturn = val; +} + static void ex_terminal(exarg_T *eap) { char ex_cmd[1024]; diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 24c735865c..ab5d89d675 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -252,4 +252,15 @@ func Test_peek_and_get_char() call timer_stop(intr) endfunc +func Test_ex_mode() + " Function with an empty line. + func Foo(...) + + endfunc + let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) + " This used to throw error E749. + exe "normal Qsleep 100m\rvi\r" + call timer_stop(timer) +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From ca116625153d806bd192f0f533346cc9536904a9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Sep 2019 02:18:38 -0400 Subject: vim-patch:8.0.1539: no test for the popup menu positioning Problem: No test for the popup menu positioning. Solution: Add a screendump test for the popup menu. https://github.com/vim/vim/commit/6bb2cdfe604e51eec216cbe23bb6e8fb47810347 --- src/nvim/testdir/test_popup.vim | 33 +++++++++++++++++++++++++++++++++ src/nvim/testdir/test_syntax.vim | 3 ++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 53df30bb19..efef91789d 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -1,6 +1,7 @@ " Test for completion menu source shared.vim +source screendump.vim let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] let g:setting = '' @@ -735,6 +736,38 @@ func Test_popup_and_preview_autocommand() bw! endfunc +func Test_popup_position() + if !CanRunVimInTerminal() + return + endif + call writefile([ + \ '123456789_123456789_123456789_a', + \ '123456789_123456789_123456789_b', + \ ' 123', + \ ], 'Xtest') + let buf = RunVimInTerminal('Xtest', {}) + call term_sendkeys(buf, ":vsplit\") + + " default pumwidth in left window: overlap in right window + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_popup_position_01', {'rows': 8}) + call term_sendkeys(buf, "\u") + + " default pumwidth: fill until right of window + call term_sendkeys(buf, "\l") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_popup_position_02', {'rows': 8}) + + " larger pumwidth: used as minimum width + call term_sendkeys(buf, "\u") + call term_sendkeys(buf, ":set pumwidth=30\") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_popup_position_03', {'rows': 8}) + + call term_sendkeys(buf, "\u") + call StopVimInTerminal(buf) + call delete('Xtest') +endfunc func Test_popup_complete_backwards() new diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 598a00476c..b9310e2168 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -1,6 +1,7 @@ " Test for syntax and syntax iskeyword option source view_util.vim +source screendump.vim func GetSyntaxItem(pat) let c = '' @@ -526,7 +527,7 @@ func Test_syntax_c() let $COLORFGBG = '15;0' let buf = RunVimInTerminal('Xtest.c', {}) - call VerifyScreenDump(buf, 'Test_syntax_c_01') + call VerifyScreenDump(buf, 'Test_syntax_c_01', {}) call StopVimInTerminal(buf) let $COLORFGBG = '' -- cgit From 7cffc87868db846d9306bbc8f055630967c72c21 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Sep 2019 18:41:15 -0400 Subject: vim-patch:8.0.1733: incomplete testing for completion fix Problem: Incomplete testing for completion fix. (Lifepillar) Solution: Add a test with CTRL-P. https://github.com/vim/vim/commit/bad0ce7b26be5eed8524347018f4c835b212f8d1 --- src/nvim/testdir/test_popup.vim | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index efef91789d..4806bb1855 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -779,6 +779,16 @@ func Test_popup_complete_backwards() bwipe! endfunc +func Test_popup_complete_backwards_ctrl_p() + new + call setline(1, ['Post', 'Port', 'Po']) + let expected=['Post', 'Port', 'Port'] + call cursor(3,2) + call feedkeys("A\\rt\", 'tx') + call assert_equal(expected, getline(1,'$')) + bwipe! +endfunc + fun! Test_complete_o_tab() throw 'skipped: Nvim does not support test_override()' let s:o_char_pressed = 0 -- cgit From 3878b0822e4a7e76fcc741d739bd399915920a4e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Sep 2019 19:08:01 -0400 Subject: vim-patch:8.0.1771: in tests, when WaitFor() fails it doesn't say why Problem: In tests, when WaitFor() fails it doesn't say why. (James McCoy) Solution: Add WaitForAssert(), which produces an assert error when it fails. https://github.com/vim/vim/commit/50182fa84e20a0547f3e2bd6683ef799fcd27855 --- src/nvim/testdir/shared.vim | 77 ++++++++++++++++++++++++++-------- src/nvim/testdir/test_autocmd.vim | 4 +- src/nvim/testdir/test_clientserver.vim | 13 +++--- 3 files changed, 66 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index df512e2e3f..84f636077d 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -135,39 +135,80 @@ endfunc " Wait for up to five seconds for "expr" to become true. "expr" can be a " stringified expression to evaluate, or a funcref without arguments. +" Using a lambda works best. Example: +" call WaitFor({-> status == "ok"}) " " A second argument can be used to specify a different timeout in msec. " -" Return time slept in milliseconds. With the +reltime feature this can be -" more than the actual waiting time. Without +reltime it can also be less. +" When successful the time slept is returned. +" When running into the timeout an exception is thrown, thus the function does +" not return. func WaitFor(expr, ...) let timeout = get(a:000, 0, 5000) + let slept = s:WaitForCommon(a:expr, v:null, timeout) + if slept < 0 + throw 'WaitFor() timed out after ' . timeout . ' msec' + endif + return slept +endfunc + +" Wait for up to five seconds for "assert" to return zero. "assert" must be a +" (lambda) function containing one assert function. Example: +" call WaitForAssert({-> assert_equal("dead", job_status(job)}) +" +" A second argument can be used to specify a different timeout in msec. +" +" Return zero for success, one for failure (like the assert function). +func WaitForAssert(assert, ...) + let timeout = get(a:000, 0, 5000) + if s:WaitForCommon(v:null, a:assert, timeout) < 0 + return 1 + endif + return 0 +endfunc + +" Common implementation of WaitFor() and WaitForAssert(). +" Either "expr" or "assert" is not v:null +" Return the waiting time for success, -1 for failure. +func s:WaitForCommon(expr, assert, timeout) " using reltime() is more accurate, but not always available + let slept = 0 if has('reltime') let start = reltime() - else - let slept = 0 endif - if type(a:expr) == v:t_func - let Test = a:expr - else - let Test = {-> eval(a:expr) } - endif - for i in range(timeout / 10) - if Test() - if has('reltime') - return float2nr(reltimefloat(reltime(start)) * 1000) - endif + + while 1 + if type(a:expr) == v:t_func + let success = a:expr() + elseif type(a:assert) == v:t_func + let success = a:assert() == 0 + else + let success = eval(a:expr) + endif + if success return slept endif - if !has('reltime') - let slept += 10 + + if slept >= a:timeout + break + endif + if type(a:assert) == v:t_func + " Remove the error added by the assert function. + call remove(v:errors, -1) endif + sleep 10m - endfor - throw 'WaitFor() timed out after ' . timeout . ' msec' + if has('reltime') + let slept = float2nr(reltimefloat(reltime(start)) * 1000) + else + let slept += 10 + endif + endwhile + + return -1 " timed out endfunc + " Wait for up to a given milliseconds. " With the +timers feature this waits for key-input by getchar(), Resume() " feeds key-input and resumes process. Return time waited in milliseconds. diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5848940a2b..4dd48beef4 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1344,11 +1344,11 @@ func Test_Changed_FirstTime() let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3}) call assert_equal('running', term_getstatus(buf)) " Wait for the ruler (in the status line) to be shown. - call WaitFor({-> term_getline(buf, 3) =~# '\ assert_match('\ call writefile(['No'], 'Xchanged.txt')\") call term_sendkeys(buf, "\\:qa!\") - call WaitFor({-> term_getstatus(buf) == 'finished'}) + call WaitForAssert({-> assert_equal('finished', term_getstatus(buf))}) call assert_equal([''], readfile('Xchanged.txt')) " clean up diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index 813cb338a5..3377f86126 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -28,12 +28,11 @@ func Test_client_server() let name = 'XVIMTEST' let cmd .= ' --servername ' . name let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'}) - call WaitFor({-> job_status(job) == "run"}) + call WaitForAssert({-> assert_equal("run", job_status(job))}) " Takes a short while for the server to be active. " When using valgrind it takes much longer. - call WaitFor('serverlist() =~ "' . name . '"') - call assert_match(name, serverlist()) + call WaitForAssert({-> assert_match(name, serverlist())}) call remote_foreground(name) @@ -54,12 +53,10 @@ func Test_client_server() endif " Wait for the server to be up and answering requests. sleep 100m - call WaitFor('remote_expr("' . name . '", "v:version", "", 1) != ""') - call assert_true(remote_expr(name, "v:version", "", 1) != "") + call WaitForAssert({-> assert_true(remote_expr(name, "v:version", "", 1) != "")}) call remote_send(name, ":let testvar = 'maybe'\") - call WaitFor('remote_expr("' . name . '", "testvar", "", 1) == "maybe"') - call assert_equal('maybe', remote_expr(name, "testvar", "", 2)) + call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))}) endif call assert_fails('call remote_send("XXX", ":let testvar = ''yes''\")', 'E241') @@ -94,7 +91,7 @@ func Test_client_server() call remote_send(name, ":qa!\") try - call WaitFor({-> job_status(job) == "dead"}) + call WaitForAssert({-> assert_equal("dead", job_status(job))}) finally if job_status(job) != 'dead' call assert_report('Server did not exit') -- cgit From 2a7ffc6567097232f5e12b6d3dc6483748eaad0a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Sep 2019 19:19:46 -0400 Subject: vim-patch:8.0.1776: in tests, when WaitFor() fails it doesn't say why Problem: In tests, when WaitFor() fails it doesn't say why. Solution: Turn a few more WaitFor() into WaitForAssert(). https://github.com/vim/vim/commit/0e9d1ae3216a5940b36bb56d155fb300b2e55b00 --- src/nvim/testdir/test_popup.vim | 9 +++------ src/nvim/testdir/test_quotestar.vim | 22 ++++++++++------------ src/nvim/testdir/test_timers.vim | 6 +++--- 3 files changed, 16 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 4806bb1855..c63269e5d2 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -681,18 +681,15 @@ func Test_popup_and_window_resize() call term_sendkeys(buf, "\") call term_wait(buf, 100) " popup first entry "!" must be at the top - call WaitFor({-> term_getline(buf, 1) =~ "^!"}) - call assert_match('^!\s*$', term_getline(buf, 1)) + call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, 1))}) exe 'resize +' . (h - 1) call term_wait(buf, 100) redraw! " popup shifted down, first line is now empty - call WaitFor({-> term_getline(buf, 1) == ""}) - call assert_equal('', term_getline(buf, 1)) + call WaitForAssert({-> assert_equal('', term_getline(buf, 1))}) sleep 100m " popup is below cursor line and shows first match "!" - call WaitFor({-> term_getline(buf, term_getcursor(buf)[0] + 1) =~ "^!"}) - call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1)) + call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0] + 1))}) " cursor line also shows ! call assert_match('^!\s*$', term_getline(buf, term_getcursor(buf)[0])) bwipe! diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index b83fbe40e8..ce5a9ee827 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -54,34 +54,33 @@ func Do_test_quotestar_for_x11() " Make sure a previous server has exited try call remote_send(name, ":qa!\") - call WaitFor('serverlist() !~ "' . name . '"') catch /E241:/ endtry - call assert_notmatch(name, serverlist()) + call WaitForAssert({-> assert_notmatch(name, serverlist())}) let cmd .= ' --servername ' . name let job = job_start(cmd, {'stoponexit': 'kill', 'out_io': 'null'}) - call WaitFor({-> job_status(job) == "run"}) + call WaitForAssert({-> assert_equal("run", job_status(job))}) " Takes a short while for the server to be active. - call WaitFor('serverlist() =~ "' . name . '"') + call WaitForAssert({-> assert_match(name, serverlist())}) " Wait for the server to be up and answering requests. One second is not " always sufficient. - call WaitFor('remote_expr("' . name . '", "v:version", "", 2) != ""') + call WaitForAssert({-> assert_notequal('', remote_expr(name, "v:version", "", 2))}) " Clear the *-register of this vim instance and wait for it to be picked up " by the server. let @* = 'no' call remote_foreground(name) - call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "no"') + call WaitForAssert({-> assert_equal("no", remote_expr(name, "@*", "", 1))}) " Set the * register on the server. call remote_send(name, ":let @* = 'yes'\") - call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "yes"') + call WaitForAssert({-> assert_equal("yes", remote_expr(name, "@*", "", 1))}) " Check that the *-register of this vim instance is changed as expected. - call WaitFor('@* == "yes"') + call WaitForAssert({-> assert_equal("yes", @*)}) " Handle the large selection over 262040 byte. let length = 262044 @@ -109,18 +108,17 @@ func Do_test_quotestar_for_x11() call remote_send(name, ":gui -f\") endif " Wait for the server in the GUI to be up and answering requests. - call WaitFor('remote_expr("' . name . '", "has(\"gui_running\")", "", 1) =~ "1"') + call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}) call remote_send(name, ":let @* = 'maybe'\") - call WaitFor('remote_expr("' . name . '", "@*", "", 1) == "maybe"') - call assert_equal('maybe', remote_expr(name, "@*", "", 2)) + call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))}) call assert_equal('maybe', @*) endif call remote_send(name, ":qa!\") try - call WaitFor({-> job_status(job) == "dead"}) + call WaitForAssert({-> assert_equal("dead", job_status(job))}) finally if job_status(job) != 'dead' call assert_report('Server did not exit') diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index ab5d89d675..bd63d94729 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -141,7 +141,7 @@ endfunc func Test_delete_myself() let g:called = 0 let t = timer_start(10, 'StopMyself', {'repeat': -1}) - call WaitFor('g:called == 2') + call WaitForAssert({-> assert_equal(2, g:called)}) call assert_equal(2, g:called) call assert_equal([], timer_info(t)) endfunc @@ -208,7 +208,7 @@ func Test_timer_errors() let g:call_count = 0 let timer = timer_start(10, 'FuncWithError', {'repeat': -1}) " Timer will be stopped after failing 3 out of 3 times. - call WaitFor('g:call_count == 3') + call WaitForAssert({-> assert_equal(3, g:call_count)}) sleep 50m call assert_equal(3, g:call_count) endfunc @@ -226,7 +226,7 @@ func Test_timer_catch_error() let g:call_count = 0 let timer = timer_start(10, 'FuncWithCaughtError', {'repeat': 4}) " Timer will not be stopped. - call WaitFor('g:call_count == 4') + call WaitForAssert({-> assert_equal(4, g:call_count)}) sleep 50m call assert_equal(4, g:call_count) endfunc -- cgit From eb3888a322304434848ca2f6b1b12b821c9788f4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Sep 2019 21:58:56 -0400 Subject: vim-patch:8.0.1529: assert_equalfile() does not close file descriptors Problem: Assert_equalfile() does not close file descriptors. (Coverity) Solution: Close the file descriptors. https://github.com/vim/vim/commit/3049418f3dbc571463a04d068069f6c5b7a8ccf1 --- src/nvim/eval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 04204e45d0..e409d57bfd 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7004,6 +7004,8 @@ static int assert_equalfile(typval_T *argvars) break; } } + fclose(fd1); + fclose(fd2); } } if (IObuff[0] != NUL) { -- cgit From 111d34849a0670842b56c17c3922dbf0576bb39b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 21 Sep 2019 11:07:07 -0400 Subject: vim-patch:8.0.1621: using invalid default value for highlight attribute Problem: Using invalid default value for highlight attribute. Solution: Use zero instead of -1. https://github.com/vim/vim/commit/6185903e3d07eb53326fc1403fc2de97ca31b775 --- 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 5a61238d8c..675d484d67 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7569,8 +7569,8 @@ void highlight_changed(void) { int id; char_u userhl[30]; // use 30 to avoid compiler warning - int id_SNC = -1; int id_S = -1; + int id_SNC = 0; int hlcnt; need_highlight_changed = FALSE; -- cgit From b3e56957f8e9468497e5db508d97d7b560ccfe85 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 21 Sep 2019 17:03:46 -0400 Subject: vim-patch:8.1.0460: assert_fails() message argument #11051 Problem: assert_fails() does not take a message argument Solution: Add the argument. https://github.com/vim/vim/commit/1307d1c003b01b4f67524c95feb07c3d91c7c428 --- src/nvim/eval.c | 11 +++++++++-- src/nvim/eval.lua | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 248edfc9cc..7d45787fae 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7076,7 +7076,7 @@ static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = assert_exception(argvars); } -/// "assert_fails(cmd [, error])" function +/// "assert_fails(cmd [, error [, msg]])" function static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); @@ -7094,7 +7094,14 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!called_emsg) { prepare_assert_error(&ga); ga_concat(&ga, (const char_u *)"command did not fail: "); - ga_concat(&ga, (const char_u *)cmd); + if (argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + } else { + ga_concat(&ga, (const char_u *)cmd); + } assert_error(&ga); ga_clear(&ga); ret = 1; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 8efbcc71f1..0ae250e626 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -30,7 +30,7 @@ return { assert_equal={args={2, 3}}, assert_equalfile={args=2}, assert_exception={args={1, 2}}, - assert_fails={args={1, 2}}, + assert_fails={args={1, 3}}, assert_false={args={1, 2}}, assert_inrange={args={3, 4}}, assert_match={args={2, 3}}, -- cgit From ad0f97f4123b3b84e0f0883afce305d20aec954a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 21 Sep 2019 23:18:56 +0200 Subject: vim-patch:8.1.2055: profile: adjust line format #11058 Problem: Not easy to jump to function line from profile. Solution: Use "file:99" instead of "file line 99" so that "gf" works. (Daniel Hahler, closes vim/vim#4951) https://github.com/vim/vim/commit/181d4f58cc421f2e6d3b16333d4cb70d35ad1342 --- src/nvim/eval.c | 2 +- src/nvim/testdir/test_profile.vim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index be761afa1f..2ddcd389fe 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -22270,7 +22270,7 @@ void func_dump_profile(FILE *fd) .channel_id = 0, }; char_u *p = get_scriptname(last_set, &should_free); - fprintf(fd, " Defined: %s line %" PRIdLINENR "\n", + fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", p, fp->uf_script_ctx.sc_lnum); if (should_free) { xfree(p); diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 7e853eeac3..b677ac3704 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -54,7 +54,7 @@ func Test_profile_func() call assert_equal(30, len(lines)) call assert_equal('FUNCTION Foo1()', lines[0]) - call assert_match('Defined:.*Xprofile_func.vim', lines[1]) + call assert_match('Defined:.*Xprofile_func.vim:3', lines[1]) call assert_equal('Called 2 times', lines[2]) call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[3]) call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[4]) -- cgit From 18e5869f56aab8a52d84185e5bd043799c36ae2d Mon Sep 17 00:00:00 2001 From: Zach Wegner Date: Sun, 15 Sep 2019 14:16:44 -0500 Subject: Fix "precedes" listchar behavior in wrap mode Previously, the "precedes" character would be rendered on every row when w_skipcol > 0 (i.e., when viewing a single line longer than the entire screen), instead of just on the first row. Make sure to only render it on the first row in this case. Add a test for this behavior. Fix documentation for the "precedes" character, which erroneously stated that it was only active when wrap mode was off. --- src/nvim/screen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f4aa10ecf5..f107985df7 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3763,7 +3763,7 @@ win_line ( */ if (lcs_prec_todo != NUL && wp->w_p_list - && (wp->w_p_wrap ? wp->w_skipcol > 0 : wp->w_leftcol > 0) + && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) && filler_todo <= 0 && draw_state > WL_NR && c != NUL) { -- cgit From 16549324988be0717b59f7e5fec818ee9ad70f52 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 21 Sep 2019 20:29:15 -0700 Subject: vim-patch:8.1.2060: "precedes" in 'listchars' not used properly (Credit: Zach Wegner, https://github.com/neovim/neovim/pull/11034) Problem: "precedes" in 'listchars' not used properly. Solution: Correctly handle the "precedes" char in list mode for long lines. https://github.com/vim/vim/commit/bffba7f7042f6082e75b42484b15f66087b01941 --- src/nvim/screen.c | 8 +++--- src/nvim/testdir/test_display.vim | 55 +++++++++++++++++++++++++++++++++++++++ src/nvim/testdir/view_util.vim | 1 + 3 files changed, 59 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index f107985df7..a866901b78 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3756,11 +3756,9 @@ win_line ( char_attr = hl_combine_attr(char_attr, extra_attr); } - /* - * Handle the case where we are in column 0 but not on the first - * character of the line and the user wants us to show us a - * special character (via 'listchars' option "precedes:". - */ + // Handle the case where we are in column 0 but not on the first + // character of the line and the user wants us to show us a + // special character (via 'listchars' option "precedes:". if (lcs_prec_todo != NUL && wp->w_p_list && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 5feb59eef1..66c13ded82 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -69,3 +69,58 @@ func! Test_display_foldtext_mbyte() set foldtext& fillchars& foldmethod& fdc& bw! endfunc + +func Test_display_listchars_precedes() + call NewWindow(10, 10) + " Need a physical line that wraps over the complete + " window size + call append(0, repeat('aaa aaa aa ', 10)) + call append(1, repeat(['bbb bbb bbb bbb'], 2)) + " remove blank trailing line + $d + set list nowrap + call cursor(1, 1) + " move to end of line and scroll 2 characters back + norm! $2zh + let lines=ScreenLines([1,4], winwidth(0)+1) + let expect = [ + \ " aaa aa $ |", + \ "$ |", + \ "$ |", + \ "~ |", + \ ] + call assert_equal(expect, lines) + set list listchars+=precedes:< nowrap + call cursor(1, 1) + " move to end of line and scroll 2 characters back + norm! $2zh + let lines = ScreenLines([1,4], winwidth(0)+1) + let expect = [ + \ " Date: Sun, 22 Sep 2019 16:02:28 +0900 Subject: env: use putenv_s for LC_ALL, LANG, etc. #11050 Problem: ":lang messages en_US.UTF-8" no longer overrides the language detected from the environment (at startup). Solution: In os_setenv, special-case "LC_ALL", "LANG", et al. to use putenv_s instead of uv_os_setenv. fixes #11045 --- src/nvim/os/env.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index f5dbf0694e..54fdd7961c 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -135,7 +135,16 @@ int os_setenv(const char *name, const char *value, int overwrite) } #endif uv_mutex_lock(&mutex); - int r = uv_os_setenv(name, value); + int r; +#ifdef WIN32 + // libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s(). + if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE") + || striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) { + r = _putenv_s(name, value); // NOLINT + assert(r == 0); + } +#endif + r = uv_os_setenv(name, value); assert(r != UV_EINVAL); // Destroy the old map item. Do this AFTER uv_os_setenv(), because `value` // could be a previous os_getenv() result. -- cgit From f316916758c487054138762d66a966430e38e612 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 17 Sep 2019 20:26:43 +0200 Subject: screen: missing redraw/highlight for ruler in message area --- src/nvim/screen.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a866901b78..17a91f69d5 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5101,6 +5101,8 @@ win_redr_custom ( win_T *ewp; int p_crb_save; + ScreenGrid *grid = &default_grid; + /* There is a tiny chance that this gets called recursively: When * redrawing a status line triggers redrawing the ruler or tabline. * Avoid trouble by not allowing recursion. */ @@ -5140,10 +5142,11 @@ win_redr_custom ( } maxwidth = wp->w_width - col; if (!wp->w_status_height) { + grid = &msg_grid_adj; row = Rows - 1; maxwidth--; // writing in last column may cause scrolling fillchar = ' '; - attr = 0; + attr = HL_ATTR(HLF_MSG); } use_sandbox = was_set_insecurely((char_u *)"rulerformat", 0); @@ -5193,13 +5196,13 @@ win_redr_custom ( /* * Draw each snippet with the specified highlighting. */ - grid_puts_line_start(&default_grid, row); + grid_puts_line_start(grid, row); curattr = attr; p = buf; for (n = 0; hltab[n].start != NULL; n++) { int textlen = (int)(hltab[n].start - p); - grid_puts_len(&default_grid, p, textlen, row, col, curattr); + grid_puts_len(grid, p, textlen, row, col, curattr); col += vim_strnsize(p, textlen); p = hltab[n].start; @@ -5213,7 +5216,7 @@ win_redr_custom ( curattr = highlight_user[hltab[n].userhl - 1]; } // Make sure to use an empty string instead of p, if p is beyond buf + len. - grid_puts(&default_grid, p >= buf + len ? (char_u *)"" : p, row, col, + grid_puts(grid, p >= buf + len ? (char_u *)"" : p, row, col, curattr); grid_puts_line_flush(false); @@ -7058,7 +7061,7 @@ static void win_redr_ruler(win_T *wp, int always) } else { row = Rows - 1; fillchar = ' '; - attr = 0; + attr = HL_ATTR(HLF_MSG); width = Columns; off = 0; } -- cgit From 97b82553e09b949cae90c6e755d218d5cdca8f12 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 22 Sep 2019 19:49:03 +0200 Subject: vim-patch:8.1.2052: using "x" before a closed fold may delete that fold Problem: Using "x" before a closed fold may delete that fold. Solution: Do not translate 'x' do "dl". (Christian Brabandt, closes vim/vim#4927) https://github.com/vim/vim/commit/7a9bd7c1e0ce1baf5a02daf36eeae3638aa315c7 --- src/nvim/normal.c | 9 ++++++++- src/nvim/testdir/test_fold.vim | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e7e6d2b365..ffdc8e23d7 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6248,7 +6248,14 @@ static void nv_optrans(cmdarg_T *cap) if (cap->count0) { stuffnumReadbuff(cap->count0); } - stuffReadbuff(ar[strchr(str, (char)cap->cmdchar) - str]); + // If on an empty line and using 'x' and "l" is included in the + // whichwrap option, do not delete the next line. + if (cap->cmdchar == 'x' && vim_strchr(p_ww, 'l') != NULL + && gchar_cursor() == NUL) { + stuffReadbuff((char *)"dd"); + } else { + stuffReadbuff(ar[strchr(str, (char)cap->cmdchar) - str]); + } } cap->opcount = 0; } diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 3cb42579be..03723b3cb5 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -756,3 +756,15 @@ func Test_fold_delete_with_marker() bwipe! bwipe! endfunc + +func Test_fold_delete_with_marker_and_whichwrap() + new + let content1 = [''] + let content2 = ['folded line 1 "{{{1', ' test', ' test2', ' test3', '', 'folded line 2 "{{{1', ' test', ' test2', ' test3'] + call setline(1, content1 + content2) + set fdm=marker ww+=l + normal! x + call assert_equal(content2, getline(1, '$')) + set fdm& ww& + bwipe! +endfunc -- cgit From 6c3d34e4dfc7fd3f810dd78f9dec016540c13bda Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 22 Sep 2019 20:04:34 +0200 Subject: vim-patch:8.1.2059: fix for "x" deleting a fold has side effects Problem: Fix for "x" deleting a fold has side effects. Solution: Fix it where the fold is included. https://github.com/vim/vim/commit/56ebbabea1d8409ba67127b9674f6c714739c8e0 --- src/nvim/normal.c | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index ffdc8e23d7..4dfde96e94 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1553,9 +1553,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (!VIsual_active) { if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) oap->start.col = 0; - if (hasFolding(curwin->w_cursor.lnum, NULL, - &curwin->w_cursor.lnum)) + if ((curwin->w_cursor.col > 0 || oap->inclusive) + && hasFolding(curwin->w_cursor.lnum, NULL, + &curwin->w_cursor.lnum)) { curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + } } oap->end = curwin->w_cursor; curwin->w_cursor = oap->start; @@ -5125,14 +5127,10 @@ static void nv_right(cmdarg_T *cap) break; } else if (PAST_LINE) { curwin->w_set_curswant = true; - if (virtual_active()) + if (virtual_active()) { oneright(); - else { - if (has_mbyte) - curwin->w_cursor.col += - (*mb_ptr2len)(get_cursor_pos_ptr()); - else - ++curwin->w_cursor.col; + } else { + curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr()); } } } @@ -6248,14 +6246,7 @@ static void nv_optrans(cmdarg_T *cap) if (cap->count0) { stuffnumReadbuff(cap->count0); } - // If on an empty line and using 'x' and "l" is included in the - // whichwrap option, do not delete the next line. - if (cap->cmdchar == 'x' && vim_strchr(p_ww, 'l') != NULL - && gchar_cursor() == NUL) { - stuffReadbuff((char *)"dd"); - } else { - stuffReadbuff(ar[strchr(str, (char)cap->cmdchar) - str]); - } + stuffReadbuff(ar[strchr(str, (char)cap->cmdchar) - str]); } cap->opcount = 0; } -- cgit From 3626d2107eae23433b88612b01bba7015951d37d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 22 Sep 2019 21:51:55 +0200 Subject: terminfo_start: flush buffer #11074 This aligns with `terminfo_stop`, which also flushes the buffer after disabling things. This ensures Neovim gets the response to the terminal background query before exiting (`nvim -u NONE -cq` with e.g. urxvt or kitty). Caveats: * With kitty this causes some "flickering", likely since the alternate screen is being setup with `nvim -u NONE -cq`, whereas it would not be processed otherwise before quitting (as with the background query). * tmux after this patch may print ^[[I (CSI I / FocusGained) after `nvim -u NONE -cq`. Fixes https://github.com/neovim/neovim/issues/11062 --- src/nvim/tui/tui.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 791756e5c5..0b3bed1c76 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -312,6 +312,7 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } + flush_buf(ui); } static void terminfo_stop(UI *ui) -- cgit From d9032308fbfd90eb71266bfe6d9a11930045f745 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Sep 2019 21:56:31 -0400 Subject: vim-patch:8.0.1812: refactor qf_jump_to_usable_window() #11078 Problem: The qf_jump_to_usable_window() function is too long. Solution: Split it in parts. (Yegappan Lakshmanan, closes vim/vim#2891) https://github.com/vim/vim/commit/7a2b0e55e9460493c4a949bda8be70950dbb8f85 --- src/nvim/quickfix.c | 350 +++++++++++++++++++++++++++++----------------------- 1 file changed, 199 insertions(+), 151 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 301f72fa79..b476a665c1 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1795,26 +1795,24 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr) } } -/* - * Check in which directory of the directory stack the given file can be - * found. - * Returns a pointer to the directory name or NULL if not found. - * Cleans up intermediate directory entries. - * - * TODO: How to solve the following problem? - * If we have the this directory tree: - * ./ - * ./aa - * ./aa/bb - * ./bb - * ./bb/x.c - * and make says: - * making all in aa - * making all in bb - * x.c:9: Error - * Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. - * qf_guess_filepath will return NULL. - */ +// Check in which directory of the directory stack the given file can be +// found. +// Returns a pointer to the directory name or NULL if not found. +// Cleans up intermediate directory entries. +// +// TODO(vim): How to solve the following problem? +// If we have this directory tree: +// ./ +// ./aa +// ./aa/bb +// ./bb +// ./bb/x.c +// and make says: +// making all in aa +// making all in bb +// x.c:9: Error +// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. +// qf_guess_filepath will return NULL. static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename) { struct dir_stack_T *ds_ptr; @@ -1876,7 +1874,7 @@ static bool qflist_valid(win_T *wp, unsigned int qf_id) /// When loading a file from the quickfix, the autocommands may modify it. /// This may invalidate the current quickfix entry. This function checks -/// whether a entry is still present in the quickfix. +/// whether an entry is still present in the quickfix list. /// Similar to location list. static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) { @@ -2005,6 +2003,18 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, return qf_ptr; } +// Find a window displaying a Vim help file. +static win_T *qf_find_help_win(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (bt_help(wp->w_buffer)) { + return wp; + } + } + return NULL; +} + /// Find a help window or open one. static int jump_to_help_window(qf_info_T *qi, int *opened_window) { @@ -2013,12 +2023,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) if (cmdmod.tab != 0) { wp = NULL; } else { - FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { - if (bt_help(wp2->w_buffer)) { - wp = wp2; - break; - } - } + wp = qf_find_help_win(); } if (wp != NULL && wp->w_buffer->b_nwindows > 0) { @@ -2061,146 +2066,194 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) return OK; } -/// Find a suitable window for opening a file (qf_fnum) and jump to it. -/// If the file is already opened in a window, jump to it. -static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) +// Find a non-quickfix window using the given location list. +// Returns NULL if a matching window is not found. +static win_T *qf_find_win_with_loclist(const qf_info_T *ll) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - win_T *usable_win_ptr = NULL; - int usable_win; - int flags; - win_T *win; - win_T *altwin; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_llist == ll && !bt_quickfix(wp->w_buffer)) { + return wp; + } + } + return NULL; +} - usable_win = 0; +// Find a window containing a normal buffer +static win_T *qf_find_win_with_normal_buf(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer->b_p_bt[0] == NUL) { + return wp; + } + } + return NULL; +} - qf_info_T *ll_ref = curwin->w_llist_ref; +// Go to a window in any tabpage containing the specified file. Returns TRUE +// if successfully jumped to the window. Otherwise returns FALSE. +static bool qf_goto_tabwin_with_file(int fnum) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer->b_fnum == fnum) { + goto_tabpage_win(tp, wp); + return true; + } + } + return false; +} + +// Create a new window to show a file above the quickfix window. Called when +// only the quickfix window is present. +static int qf_open_new_file_win(qf_info_T *ll_ref) +{ + int flags = WSP_ABOVE; + if (ll_ref != NULL) { + flags |= WSP_NEWLOC; + } + if (win_split(0, flags) == FAIL) { + return FAIL; // not enough room for window + } + p_swb = empty_option; // don't split again + swb_flags = 0; + RESET_BINDING(curwin); if (ll_ref != NULL) { - // Find a window using the same location list that is not a - // quickfix window. - FOR_ALL_WINDOWS_IN_TAB(usable_win_ptr2, curtab) { - if (usable_win_ptr2->w_llist == ll_ref - && !bt_quickfix(usable_win_ptr2->w_buffer)) { - usable_win_ptr = usable_win_ptr2; - usable_win = 1; + // The new window should use the location list from the + // location list window + curwin->w_llist = ll_ref; + ll_ref->qf_refcount++; + } + return OK; +} + +// Go to a window that shows the right buffer. If the window is not found, go +// to the window just above the location list window. This is used for opening +// a file from a location window and not from a quickfix window. If some usable +// window is previously found, then it is supplied in 'use_win'. +static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, + qf_info_T *ll_ref) +{ + win_T *win = use_win; + + if (win == NULL) { + // Find the window showing the selected file + FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { + if (win2->w_buffer->b_fnum == qf_fnum) { + win = win2; break; } } + if (win == NULL) { + // Find a previous usable window + win = curwin; + do { + if (win->w_buffer->b_p_bt[0] == NUL) { + break; + } + if (win->w_prev == NULL) { + win = lastwin; // wrap around the top + } else { + win = win->w_prev; // go to previous window + } + } while (win != curwin); + } + } + win_goto(win); + + // If the location list for the window is not set, then set it + // to the location list from the location window + if (win->w_llist == NULL) { + win->w_llist = ll_ref; + ll_ref->qf_refcount++; + } +} + +// Go to a window that shows the specified file. If a window is not found, go +// to the window just above the quickfix window. This is used for opening a +// file from a quickfix window and not from a location window. +static void qf_goto_win_with_qfl_file(int qf_fnum) +{ + win_T *win = curwin; + win_T *altwin = NULL; + for (;;) { + if (win->w_buffer->b_fnum == qf_fnum) { + break; + } + if (win->w_prev == NULL) { + win = lastwin; // wrap around the top + } else { + win = win->w_prev; // go to previous window + } + + if (IS_QF_WINDOW(win)) { + // Didn't find it, go to the window before the quickfix + // window. + if (altwin != NULL) { + win = altwin; + } else if (curwin->w_prev != NULL) { + win = curwin->w_prev; + } else { + win = curwin->w_next; + } + break; + } + + // Remember a usable window. + if (altwin == NULL + && !win->w_p_pvw + && win->w_buffer->b_p_bt[0] == NUL) { + altwin = win; + } + } + + win_goto(win); +} + +// Find a suitable window for opening a file (qf_fnum) from the +// quickfix/location list and jump to it. If the file is already opened in a +// window, jump to it. Otherwise open a new window to display the file. This is +// called from either a quickfix or a location list window. +static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) +{ + win_T *usable_win_ptr = NULL; + bool usable_win = false; + + qf_info_T *ll_ref = curwin->w_llist_ref; + if (ll_ref != NULL) { + // Find a non-quickfix window with this location list + usable_win_ptr = qf_find_win_with_loclist(ll_ref); + if (usable_win_ptr != NULL) { + usable_win = true; + } } if (!usable_win) { // Locate a window showing a normal buffer - FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { - if (win2->w_buffer->b_p_bt[0] == NUL) { - usable_win = 1; - break; - } + win_T *win = qf_find_win_with_normal_buf(); + if (win != NULL) { + usable_win = true; } } // If no usable window is found and 'switchbuf' contains "usetab" // then search in other tabs. if (!usable_win && (swb_flags & SWB_USETAB)) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer->b_fnum == qf_fnum) { - goto_tabpage_win(tp, wp); - usable_win = 1; - goto win_found; - } - } + usable_win = qf_goto_tabwin_with_file(qf_fnum); } -win_found: // If there is only one window and it is the quickfix window, create a // new one above the quickfix window. if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { - flags = WSP_ABOVE; - if (ll_ref != NULL) { - flags |= WSP_NEWLOC; - } - if (win_split(0, flags) == FAIL) { - return FAIL; // not enough room for window + if (qf_open_new_file_win(ll_ref) != OK) { + return FAIL; } *opened_window = true; // close it when fail - p_swb = empty_option; // don't split again - swb_flags = 0; - RESET_BINDING(curwin); - if (ll_ref != NULL) { - // The new window should use the location list from the - // location list window - curwin->w_llist = ll_ref; - ll_ref->qf_refcount++; - } } else { - if (curwin->w_llist_ref != NULL) { - // In a location window - win = usable_win_ptr; - if (win == NULL) { - // Find the window showing the selected file - FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { - if (win2->w_buffer->b_fnum == qf_fnum) { - win = win2; - break; - } - } - if (win == NULL) { - // Find a previous usable window - win = curwin; - do { - if (win->w_buffer->b_p_bt[0] == NUL) { - break; - } - if (win->w_prev == NULL) { - win = lastwin; // wrap around the top - } else { - win = win->w_prev; // go to previous window - } - } while (win != curwin); - } - } - win_goto(win); - - // If the location list for the window is not set, then set it - // to the location list from the location window - if (win->w_llist == NULL) { - win->w_llist = ll_ref; - if (ll_ref != NULL) { - ll_ref->qf_refcount++; - } - } - } else { - // Try to find a window that shows the right buffer. - // Default to the window just above the quickfix buffer. - win = curwin; - altwin = NULL; - for (;;) { - if (win->w_buffer->b_fnum == qf_fnum) { - break; - } - if (win->w_prev == NULL) { - win = lastwin; // wrap around the top - } else { - win = win->w_prev; // go to previous window - } - if (IS_QF_WINDOW(win)) { - // Didn't find it, go to the window before the quickfix window. - if (altwin != NULL) { - win = altwin; - } else if (curwin->w_prev != NULL) { - win = curwin->w_prev; - } else { - win = curwin->w_next; - } - break; - } - - // Remember a usable window. - if (altwin == NULL && !win->w_p_pvw - && win->w_buffer->b_p_bt[0] == NUL) { - altwin = win; - } - } - - win_goto(win); + if (curwin->w_llist_ref != NULL) { // In a location window + qf_goto_win_with_ll_file(usable_win_ptr, qf_fnum, ll_ref); + } else { // In a quickfix window + qf_goto_win_with_qfl_file(qf_fnum); } } @@ -2258,8 +2311,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, return retval; } -/// Goto the error line in the current file using either line/column number or a -/// search pattern. +/// Go to the error line in the current file using either line/column number or +/// a search pattern. static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, char_u *qf_pattern) { @@ -5177,7 +5230,7 @@ static int qf_setprop_context(qf_info_T *qi, int qf_idx, dictitem_T *di) // Set quickfix/location list properties (title, items, context). // Also used to add items from parsing a list of lines. -// Used by the setqflist() and setloclist() VimL functions. +// Used by the setqflist() and setloclist() Vim script functions. static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char_u *title) FUNC_ATTR_NONNULL_ALL @@ -5585,12 +5638,7 @@ void ex_helpgrep(exarg_T *eap) wp = curwin; } else { // Find an existing help window - FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { - if (bt_help(wp2->w_buffer)) { - wp = wp2; - break; - } - } + wp = qf_find_help_win(); } if (wp == NULL) { // Help window not found -- cgit From 6d213593ed0af948d23e9d0e8c6d993577344b48 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 01:28:04 -0400 Subject: vim-patch:8.0.1754: ex_helpgrep() is too long #11084 Problem: ex_helpgrep() is too long. Solution: Refactor the function. (Yegappan Lakshmanan, closes vim/vim#2766) https://github.com/vim/vim/commit/2225ebb48644f3924311b8df02a1319ab7675d42 --- src/nvim/quickfix.c | 258 +++++++++++++++++++++---------------- src/nvim/testdir/test_quickfix.vim | 6 + 2 files changed, 155 insertions(+), 109 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index b476a665c1..528829e63d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5593,26 +5593,149 @@ void ex_cexpr(exarg_T *eap) } } -/* - * ":helpgrep {pattern}" - */ -void ex_helpgrep(exarg_T *eap) +// Get the location list for ":lhelpgrep" +static qf_info_T *hgr_get_ll(bool *new_ll) + FUNC_ATTR_NONNULL_ALL +{ + win_T *wp; + qf_info_T *qi; + + // If the current window is a help window, then use it + if (bt_help(curwin->w_buffer)) { + wp = curwin; + } else { + // Find an existing help window + wp = qf_find_help_win(); + } + + if (wp == NULL) { // Help window not found + qi = NULL; + } else { + qi = wp->w_llist; + } + if (qi == NULL) { + // Allocate a new location list for help text matches + if ((qi = ll_new_list()) == NULL) { + return NULL; + } + *new_ll = true; + } + + return qi; +} + +// Search for a pattern in a help file. +static void hgr_search_file( + qf_info_T *qi, + char_u *fname, + regmatch_T *p_regmatch) + FUNC_ATTR_NONNULL_ARG(1, 3) +{ + FILE *const fd = os_fopen((char *)fname, "r"); + if (fd == NULL) { + return; + } + + long lnum = 1; + while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { + char_u *line = IObuff; + + if (vim_regexec(p_regmatch, line, (colnr_T)0)) { + int l = (int)STRLEN(line); + + // remove trailing CR, LF, spaces, etc. + while (l > 0 && line[l - 1] <= ' ') { + line[--l] = NUL; + } + + if (qf_add_entry(qi, + qi->qf_curlist, + NULL, // dir + fname, + NULL, + 0, + line, + lnum, + (int)(p_regmatch->startp[0] - line) + 1, // col + false, // vis_col + NULL, // search pattern + 0, // nr + 1, // type + true // valid + ) == FAIL) { + got_int = true; + if (line != IObuff) { + xfree(line); + } + break; + } + } + if (line != IObuff) { + xfree(line); + } + lnum++; + line_breakcheck(); + } + fclose(fd); +} + +// Search for a pattern in all the help files in the doc directory under +// the given directory. +static void hgr_search_files_in_dir( + qf_info_T *qi, + char_u *dirname, + regmatch_T *p_regmatch, + const char_u *lang) + FUNC_ATTR_NONNULL_ARG(1, 2, 3) { - regmatch_T regmatch; - char_u *save_cpo; - char_u *p; int fcount; - char_u **fnames; - FILE *fd; - int fi; - long lnum; - char_u *lang; - qf_info_T *qi = &ql_info; - int new_qi = FALSE; - char_u *au_name = NULL; + char_u **fnames; + + // Find all "*.txt" and "*.??x" files in the "doc" directory. + add_pathsep((char *)dirname); + STRCAT(dirname, "doc/*.\\(txt\\|??x\\)"); // NOLINT + if (gen_expand_wildcards(1, &dirname, &fcount, + &fnames, EW_FILE|EW_SILENT) == OK + && fcount > 0) { + for (int fi = 0; fi < fcount && !got_int; fi++) { + // Skip files for a different language. + if (lang != NULL + && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0 + && !(STRNICMP(lang, "en", 2) == 0 + && STRNICMP("txt", fnames[fi] + STRLEN(fnames[fi]) - 3, 3) + == 0)) { + continue; + } - /* Check for a specified language */ - lang = check_help_lang(eap->arg); + hgr_search_file(qi, fnames[fi], p_regmatch); + } + FreeWild(fcount, fnames); + } +} + +// Search for a pattern in all the help files in the 'runtimepath'. +static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch, + char_u *arg) + FUNC_ATTR_NONNULL_ALL +{ + // Check for a specified language + char_u *const lang = check_help_lang(arg); + + // Go through all directories in 'runtimepath' + char_u *p = p_rtp; + while (*p != NUL && !got_int) { + copy_option_part(&p, NameBuff, MAXPATHL, ","); + + hgr_search_files_in_dir(qi, NameBuff, p_regmatch, lang); + } +} + +// ":helpgrep {pattern}" +void ex_helpgrep(exarg_T *eap) +{ + qf_info_T *qi = &ql_info; + bool new_qi = false; + char_u *au_name = NULL; switch (eap->cmdidx) { case CMD_helpgrep: au_name = (char_u *)"helpgrep"; break; @@ -5626,109 +5749,26 @@ void ex_helpgrep(exarg_T *eap) } } - /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ - save_cpo = p_cpo; + // Make 'cpoptions' empty, the 'l' flag should not be used here. + char_u *const save_cpo = p_cpo; p_cpo = empty_option; if (eap->cmdidx == CMD_lhelpgrep) { - win_T *wp = NULL; - - // If the current window is a help window, then use it - if (bt_help(curwin->w_buffer)) { - wp = curwin; - } else { - // Find an existing help window - wp = qf_find_help_win(); - } - - if (wp == NULL) { // Help window not found - qi = NULL; - } else { - qi = wp->w_llist; - } + qi = hgr_get_ll(&new_qi); if (qi == NULL) { - /* Allocate a new location list for help text matches */ - qi = ll_new_list(); - new_qi = TRUE; + return; } } - regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); - regmatch.rm_ic = FALSE; + regmatch_T regmatch = { + .regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING), + .rm_ic = false, + }; if (regmatch.regprog != NULL) { // Create a new quickfix list. qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep)); - // Go through all the directories in 'runtimepath' - p = p_rtp; - while (*p != NUL && !got_int) { - copy_option_part(&p, NameBuff, MAXPATHL, ","); - - /* Find all "*.txt" and "*.??x" files in the "doc" directory. */ - add_pathsep((char *)NameBuff); - STRCAT(NameBuff, "doc/*.\\(txt\\|??x\\)"); - - // Note: We cannot just do `&NameBuff` because it is a statically sized array - // so `NameBuff == &NameBuff` according to C semantics. - char_u *buff_list[1] = {NameBuff}; - if (gen_expand_wildcards(1, buff_list, &fcount, - &fnames, EW_FILE|EW_SILENT) == OK - && fcount > 0) { - for (fi = 0; fi < fcount && !got_int; fi++) { - // Skip files for a different language. - if (lang != NULL - && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0 - && !(STRNICMP(lang, "en", 2) == 0 - && STRNICMP("txt", fnames[fi] - + STRLEN(fnames[fi]) - 3, 3) == 0)) { - continue; - } - fd = os_fopen((char *)fnames[fi], "r"); - if (fd != NULL) { - lnum = 1; - while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { - char_u *line = IObuff; - if (vim_regexec(®match, line, (colnr_T)0)) { - int l = (int)STRLEN(line); - - /* remove trailing CR, LF, spaces, etc. */ - while (l > 0 && line[l - 1] <= ' ') - line[--l] = NUL; - - if (qf_add_entry(qi, - qi->qf_curlist, - NULL, // dir - fnames[fi], - NULL, - 0, - line, - lnum, - (int)(regmatch.startp[0] - line) - + 1, // col - false, // vis_col - NULL, // search pattern - 0, // nr - 1, // type - true) // valid - == FAIL) { - got_int = true; - if (line != IObuff) { - xfree(line); - } - break; - } - } - if (line != IObuff) - xfree(line); - ++lnum; - line_breakcheck(); - } - fclose(fd); - } - } - FreeWild(fcount, fnames); - } - } + hgr_search_in_rtp(qi, ®match, eap->arg); vim_regfree(regmatch.regprog); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index b7f45aeeb1..83ef3c2fce 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2317,6 +2317,12 @@ func XvimgrepTests(cchar) call assert_equal('Xtestfile2', bufname('')) call assert_equal('Editor:Emacs EmAcS', l[0].text) + " Test for unloading a buffer after vimgrep searched the buffer + %bwipe + Xvimgrep /Editor/j Xtestfile* + call assert_equal(0, getbufinfo('Xtestfile1')[0].loaded) + call assert_equal([], getbufinfo('Xtestfile2')) + call delete('Xtestfile1') call delete('Xtestfile2') endfunc -- cgit From 445f2f409676f6e788861af439ce0b823aab3f37 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 24 Sep 2019 08:34:00 +0200 Subject: tui: flush ui buffer in tui_terminal_after_startup (#11083) This avoids having a dummy event to tickle the main loop. Confirmed using `nvim -u NONE -c 'au FocusGained * q'` in tmux (with `:set -g focus-events on`): without the flushing it would only exit after pressing a key. Moves the flushing done recently in 3626d2107. `nvim -u NONE -cq` is still working (i.e. consuming the response for the terminal background query itself), and the flickering mentioned in 3626d2107 is reduced again. Reverts part of bfb21f3e0 (#7729). --- src/nvim/event/loop.c | 4 ---- src/nvim/tui/tui.c | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index a01bbc9888..c2be472acd 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -162,10 +162,6 @@ size_t loop_size(Loop *loop) return rv; } -void loop_dummy_event(void **argv) -{ -} - static void async_cb(uv_async_t *handle) { Loop *l = handle->loop->data; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0b3bed1c76..55c4a955c2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -312,7 +312,6 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } - flush_buf(ui); } static void terminfo_stop(UI *ui) @@ -364,6 +363,7 @@ static void tui_terminal_after_startup(UI *ui) // Emit this after Nvim startup, not during. This works around a tmux // 2.3 bug(?) which caused slow drawing during startup. #7649 unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); + flush_buf(ui); } static void tui_terminal_stop(UI *ui) @@ -431,9 +431,6 @@ static void tui_main(UIBridgeData *bridge, UI *ui) } if (!tui_is_stopped(ui)) { tui_terminal_after_startup(ui); - // Tickle `main_loop` with a dummy event, else the initial "focus-gained" - // terminal response may not get processed until user hits a key. - loop_schedule_deferred(&main_loop, event_create(loop_dummy_event, 0)); } // "Passive" (I/O-driven) loop: TUI thread "main loop". while (!tui_is_stopped(ui)) { -- cgit From bb6b1267e7532e0c2e065c4e9b5552623062c70f Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 11 Sep 2019 13:48:09 +0200 Subject: Revert "win/os_env_exists(): workaround libuv bug #10734" This reverts commit 278c5d452c2cbc436a9cc317407ae6021a226c3a. --- src/nvim/os/env.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'src') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 54fdd7961c..13853016d1 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -102,9 +102,6 @@ bool os_env_exists(const char *name) assert(r != UV_EINVAL); if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) { ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); -#ifdef WIN32 - return (r == UV_UNKNOWN); -#endif } return (r == 0 || r == UV_ENOBUFS); } -- cgit From 0571145c40b0a2ae106acc43891c2680c76b8383 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 25 Sep 2019 09:15:33 +0200 Subject: paste: fix handling of "<" in cmdline (#11094) Fixes https://github.com/neovim/neovim/issues/11088. --- src/nvim/lua/vim.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index a03e97490d..b1a684b977 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -189,7 +189,7 @@ paste = (function() if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. got_line1 = (#lines > 1) vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1, _ = string.gsub(lines[1], '[\r\n\012\027]', ' ') -- Scrub. + local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks. -- cgit From cb252071718a58c2d9747177ebeb81ff1210ddd1 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 17 Jun 2019 22:35:07 +0200 Subject: vim-patch:8.0.0914: highlight attributes are always combined (#10256) Problem: Highlight attributes are always combined. Solution: Add the 'nocombine' value to replace attributes instead of combining them. (scauligi, closes vim/vim#1963) https://github.com/vim/vim/commit/0cd2a94a4030f6bd12eaec44db92db108e33c913 Closes https://github.com/neovim/neovim/pull/10256. --- src/nvim/highlight.c | 12 ++++++++++-- src/nvim/highlight_defs.h | 1 + src/nvim/syntax.c | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 093cc4923b..83ee89b2a1 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -308,8 +308,16 @@ int hl_combine_attr(int char_attr, int prim_attr) // start with low-priority attribute, and override colors if present below. HlAttrs new_en = char_aep; - new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr; - new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr; + if (spell_aep.cterm_ae_attr & HL_NOCOMBINE) { + new_en.cterm_ae_attr = spell_aep.cterm_ae_attr; + } else { + new_en.cterm_ae_attr |= spell_aep.cterm_ae_attr; + } + if (spell_aep.rgb_ae_attr & HL_NOCOMBINE) { + new_en.rgb_ae_attr = spell_aep.rgb_ae_attr; + } else { + new_en.rgb_ae_attr |= spell_aep.rgb_ae_attr; + } if (spell_aep.cterm_fg_color > 0) { new_en.cterm_fg_color = spell_aep.cterm_fg_color; diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index f9c2c03fc6..255699c8e0 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -18,6 +18,7 @@ typedef enum { HL_UNDERCURL = 0x10, HL_STANDOUT = 0x20, HL_STRIKETHROUGH = 0x40, + HL_NOCOMBINE = 0x80, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 675d484d67..ac8ace9fff 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -116,10 +116,10 @@ static int include_link = 0; /* when 2 include "nvim/link" and "clear" */ /// following names, separated by commas (but no spaces!). static char *(hl_name_table[]) = { "bold", "standout", "underline", "undercurl", - "italic", "reverse", "inverse", "strikethrough", "NONE" }; + "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; static int hl_attr_table[] = { HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, - HL_INVERSE, HL_STRIKETHROUGH, 0 }; + HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; // The patterns that are being searched for are stored in a syn_pattern. // A match item consists of one pattern. -- cgit From 0d9a3c86a1c7143187398e6cb6005ed06a5e2fde Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 28 Sep 2019 00:32:22 +0200 Subject: vim-patch:8.1.2083: multi-byte chars do not work properly with "%.*S" in printf() (#11106) Problem: Multi-byte chars do not work properly with "%.*S" in printf(). Solution: Use mb_ptr2cells(). Daniel Hahler, closes vim/vim#4989) https://github.com/vim/vim/commit/ce0fac28977af31f1dec411d3535b4de2c3169b3 --- src/nvim/strings.c | 14 ++++++++++---- src/nvim/testdir/test_expr.vim | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 3ba9354c67..2f5491fda5 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -953,11 +953,17 @@ int vim_vsnprintf_typval( - mb_string2cells((char_u *)str_arg)); } if (precision) { - const char *p1 = str_arg; - for (size_t i = 0; i < precision && *p1; i++) { - p1 += mb_ptr2len((const char_u *)p1); + char_u *p1; + size_t i = 0; + + for (p1 = (char_u *)str_arg; *p1; + p1 += mb_ptr2len(p1)) { + i += (size_t)utf_ptr2cells(p1); + if (i > precision) { + break; + } } - str_arg_l = precision = (size_t)(p1 - str_arg); + str_arg_l = precision = (size_t)(p1 - (char_u *)str_arg); } } break; diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 4f99625e73..dd546dbf71 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -279,6 +279,9 @@ function Test_printf_misc() call assert_equal('abc ', printf('%-4s', 'abc')) call assert_equal('abc ', printf('%-4S', 'abc')) + call assert_equal('🐍', printf('%.2S', '🐍🐍')) + call assert_equal('', printf('%.1S', '🐍🐍')) + call assert_equal('1%', printf('%d%%', 1)) endfunc -- cgit From 3bddf050230635febc16aabe0ba4f73abeed6663 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 6 Jun 2019 10:34:01 +0200 Subject: tree-sitter: vendor tree-sitter runtime tree-sitter/tree-sitter commit 7685b7861ca475664b6ef57e14d1da9acf741275 Included files are: lib/include/tree-sitter/*.h lib/src/*.[ch] LICENSE --- src/tree_sitter/LICENSE | 21 + src/tree_sitter/alloc.h | 81 ++ src/tree_sitter/api.h | 660 ++++++++++++ src/tree_sitter/array.h | 142 +++ src/tree_sitter/atomic.h | 42 + src/tree_sitter/clock.h | 141 +++ src/tree_sitter/error_costs.h | 11 + src/tree_sitter/get_changed_ranges.c | 482 +++++++++ src/tree_sitter/get_changed_ranges.h | 36 + src/tree_sitter/language.c | 107 ++ src/tree_sitter/language.h | 138 +++ src/tree_sitter/length.h | 44 + src/tree_sitter/lexer.c | 322 ++++++ src/tree_sitter/lexer.h | 48 + src/tree_sitter/lib.c | 20 + src/tree_sitter/node.c | 673 ++++++++++++ src/tree_sitter/parser.c | 1887 ++++++++++++++++++++++++++++++++++ src/tree_sitter/parser.h | 220 ++++ src/tree_sitter/point.h | 53 + src/tree_sitter/reduce_action.h | 34 + src/tree_sitter/reusable_node.h | 88 ++ src/tree_sitter/stack.c | 846 +++++++++++++++ src/tree_sitter/stack.h | 135 +++ src/tree_sitter/subtree.c | 996 ++++++++++++++++++ src/tree_sitter/subtree.h | 281 +++++ src/tree_sitter/tree.c | 149 +++ src/tree_sitter/tree.h | 34 + src/tree_sitter/tree_cursor.c | 302 ++++++ src/tree_sitter/tree_cursor.h | 20 + src/tree_sitter/utf16.c | 33 + src/tree_sitter/utf16.h | 21 + 31 files changed, 8067 insertions(+) create mode 100644 src/tree_sitter/LICENSE create mode 100644 src/tree_sitter/alloc.h create mode 100644 src/tree_sitter/api.h create mode 100644 src/tree_sitter/array.h create mode 100644 src/tree_sitter/atomic.h create mode 100644 src/tree_sitter/clock.h create mode 100644 src/tree_sitter/error_costs.h create mode 100644 src/tree_sitter/get_changed_ranges.c create mode 100644 src/tree_sitter/get_changed_ranges.h create mode 100644 src/tree_sitter/language.c create mode 100644 src/tree_sitter/language.h create mode 100644 src/tree_sitter/length.h create mode 100644 src/tree_sitter/lexer.c create mode 100644 src/tree_sitter/lexer.h create mode 100644 src/tree_sitter/lib.c create mode 100644 src/tree_sitter/node.c create mode 100644 src/tree_sitter/parser.c create mode 100644 src/tree_sitter/parser.h create mode 100644 src/tree_sitter/point.h create mode 100644 src/tree_sitter/reduce_action.h create mode 100644 src/tree_sitter/reusable_node.h create mode 100644 src/tree_sitter/stack.c create mode 100644 src/tree_sitter/stack.h create mode 100644 src/tree_sitter/subtree.c create mode 100644 src/tree_sitter/subtree.h create mode 100644 src/tree_sitter/tree.c create mode 100644 src/tree_sitter/tree.h create mode 100644 src/tree_sitter/tree_cursor.c create mode 100644 src/tree_sitter/tree_cursor.h create mode 100644 src/tree_sitter/utf16.c create mode 100644 src/tree_sitter/utf16.h (limited to 'src') diff --git a/src/tree_sitter/LICENSE b/src/tree_sitter/LICENSE new file mode 100644 index 0000000000..971b81f9a8 --- /dev/null +++ b/src/tree_sitter/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Max Brunsfeld + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h new file mode 100644 index 0000000000..c8fe6c6e6d --- /dev/null +++ b/src/tree_sitter/alloc.h @@ -0,0 +1,81 @@ +#ifndef TREE_SITTER_ALLOC_H_ +#define TREE_SITTER_ALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#if defined(TREE_SITTER_TEST) + +void *ts_record_malloc(size_t); +void *ts_record_calloc(size_t, size_t); +void *ts_record_realloc(void *, size_t); +void ts_record_free(void *); +bool ts_toggle_allocation_recording(bool); + +static inline void *ts_malloc(size_t size) { + return ts_record_malloc(size); +} + +static inline void *ts_calloc(size_t count, size_t size) { + return ts_record_calloc(count, size); +} + +static inline void *ts_realloc(void *buffer, size_t size) { + return ts_record_realloc(buffer, size); +} + +static inline void ts_free(void *buffer) { + ts_record_free(buffer); +} + +#else + +#include + +static inline bool ts_toggle_allocation_recording(bool value) { + return false; +} + +static inline void *ts_malloc(size_t size) { + void *result = malloc(size); + if (size > 0 && !result) { + fprintf(stderr, "tree-sitter failed to allocate %lu bytes", size); + exit(1); + } + return result; +} + +static inline void *ts_calloc(size_t count, size_t size) { + void *result = calloc(count, size); + if (count > 0 && !result) { + fprintf(stderr, "tree-sitter failed to allocate %lu bytes", count * size); + exit(1); + } + return result; +} + +static inline void *ts_realloc(void *buffer, size_t size) { + void *result = realloc(buffer, size); + if (size > 0 && !result) { + fprintf(stderr, "tree-sitter failed to reallocate %lu bytes", size); + exit(1); + } + return result; +} + +static inline void ts_free(void *buffer) { + free(buffer); +} + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ALLOC_H_ diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h new file mode 100644 index 0000000000..d39d0521ee --- /dev/null +++ b/src/tree_sitter/api.h @@ -0,0 +1,660 @@ +#ifndef TREE_SITTER_API_H_ +#define TREE_SITTER_API_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +/****************************/ +/* Section - ABI Versioning */ +/****************************/ + +#define TREE_SITTER_LANGUAGE_VERSION 11 +#define TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION 9 + +/*******************/ +/* Section - Types */ +/*******************/ + +typedef uint16_t TSSymbol; +typedef uint16_t TSFieldId; +typedef struct TSLanguage TSLanguage; +typedef struct TSParser TSParser; +typedef struct TSTree TSTree; + +typedef enum { + TSInputEncodingUTF8, + TSInputEncodingUTF16, +} TSInputEncoding; + +typedef enum { + TSSymbolTypeRegular, + TSSymbolTypeAnonymous, + TSSymbolTypeAuxiliary, +} TSSymbolType; + +typedef struct { + uint32_t row; + uint32_t column; +} TSPoint; + +typedef struct { + TSPoint start_point; + TSPoint end_point; + uint32_t start_byte; + uint32_t end_byte; +} TSRange; + +typedef struct { + void *payload; + const char *(*read)(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read); + TSInputEncoding encoding; +} TSInput; + +typedef enum { + TSLogTypeParse, + TSLogTypeLex, +} TSLogType; + +typedef struct { + void *payload; + void (*log)(void *payload, TSLogType, const char *); +} TSLogger; + +typedef struct { + uint32_t start_byte; + uint32_t old_end_byte; + uint32_t new_end_byte; + TSPoint start_point; + TSPoint old_end_point; + TSPoint new_end_point; +} TSInputEdit; + +typedef struct { + uint32_t context[4]; + const void *id; + const TSTree *tree; +} TSNode; + +typedef struct { + const void *tree; + const void *id; + uint32_t context[2]; +} TSTreeCursor; + +/********************/ +/* Section - Parser */ +/********************/ + +/** + * Create a new parser. + */ +TSParser *ts_parser_new(void); + +/** + * Delete the parser, freeing all of the memory that it used. + */ +void ts_parser_delete(TSParser *parser); + +/** + * Set the language that the parser should use for parsing. + * + * Returns a boolean indicating whether or not the language was successfully + * assigned. True means assignment succeeded. False means there was a version + * mismatch: the language was generated with an incompatible version of the + * Tree-sitter CLI. Check the language's version using `ts_language_version` + * and compare it to this library's `TREE_SITTER_LANGUAGE_VERSION` and + * `TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION` constants. + */ +bool ts_parser_set_language(TSParser *self, const TSLanguage *language); + +/** + * Get the parser's current language. + */ +const TSLanguage *ts_parser_language(const TSParser *self); + +/** + * Set the spans of text that the parser should include when parsing. + * + * By default, the parser will always include entire documents. This function + * allows you to parse only a *portion* of a document but still return a syntax + * tree whose ranges match up with the document as a whole. You can also pass + * multiple disjoint ranges. + * + * The second and third parameters specify the location and length of an array + * of ranges. The parser does *not* take ownership of these ranges; it copies + * the data, so it doesn't matter how these ranges are allocated. + */ +void ts_parser_set_included_ranges( + TSParser *self, + const TSRange *ranges, + uint32_t length +); + +/** + * Get the ranges of text that the parser will include when parsing. + * + * The returned pointer is owned by the parser. The caller should not free it + * or write to it. The length of the array will be written to the given + * `length` pointer. + */ +const TSRange *ts_parser_included_ranges( + const TSParser *self, + uint32_t *length +); + +/** + * Use the parser to parse some source code and create a syntax tree. + * + * If you are parsing this document for the first time, pass `NULL` for the + * `old_tree` parameter. Otherwise, if you have already parsed an earlier + * version of this document and the document has since been edited, pass the + * previous syntax tree so that the unchanged parts of it can be reused. + * This will save time and memory. For this to work correctly, you must have + * already edited the old syntax tree using the `ts_tree_edit` function in a + * way that exactly matches the source code changes. + * + * The `TSInput` parameter lets you specify how to read the text. It has the + * following three fields: + * 1. `read`: A function to retrieve a chunk of text at a given byte offset + * and (row, column) position. The function should return a pointer to the + * text and write its length to the the `bytes_read` pointer. The parser + * does not take ownership of this buffer; it just borrows it until it has + * finished reading it. The function should write a zero value to the + * `bytes_read` pointer to indicate the end of the document. + * 2. `payload`: An arbitrary pointer that will be passed to each invocation + * of the `read` function. + * 3. `encoding`: An indication of how the text is encoded. Either + * `TSInputEncodingUTF8` or `TSInputEncodingUTF16`. + * + * This function returns a syntax tree on success, and `NULL` on failure. There + * are three possible reasons for failure: + * 1. The parser does not have a language assigned. Check for this using the + `ts_parser_language` function. + * 2. Parsing was cancelled due to a timeout that was set by an earlier call to + * the `ts_parser_set_timeout_micros` function. You can resume parsing from + * where the parser left out by calling `ts_parser_parse` again with the + * same arguments. Or you can start parsing from scratch by first calling + * `ts_parser_reset`. + * 3. Parsing was cancelled using a cancellation flag that was set by an + * earlier call to `ts_parser_set_cancellation_flag`. You can resume parsing + * from where the parser left out by calling `ts_parser_parse` again with + * the same arguments. + */ +TSTree *ts_parser_parse( + TSParser *self, + const TSTree *old_tree, + TSInput input +); + +/** + * Use the parser to parse some source code stored in one contiguous buffer. + * The first two parameters are the same as in the `ts_parser_parse` function + * above. The second two parameters indicate the location of the buffer and its + * length in bytes. + */ +TSTree *ts_parser_parse_string( + TSParser *self, + const TSTree *old_tree, + const char *string, + uint32_t length +); + +/** + * Use the parser to parse some source code stored in one contiguous buffer with + * a given encoding. The first four parameters work the same as in the + * `ts_parser_parse_string` method above. The final parameter indicates whether + * the text is encoded as UTF8 or UTF16. + */ +TSTree *ts_parser_parse_string_encoding( + TSParser *self, + const TSTree *old_tree, + const char *string, + uint32_t length, + TSInputEncoding encoding +); + +/** + * Instruct the parser to start the next parse from the beginning. + * + * If the parser previously failed because of a timeout or a cancellation, then + * by default, it will resume where it left off on the next call to + * `ts_parser_parse` or other parsing functions. If you don't want to resume, + * and instead intend to use this parser to parse some other document, you must + * call this `ts_parser_reset` first. + */ +void ts_parser_reset(TSParser *self); + +/** + * Set the maximum duration in microseconds that parsing should be allowed to + * take before halting. If parsing takes longer than this, it will halt early, + * returning NULL. See `ts_parser_parse` for more information. + */ +void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout); + +/** + * Get the duration in microseconds that parsing is allowed to take. + */ +uint64_t ts_parser_timeout_micros(const TSParser *self); + +/** + * Set the parser's current cancellation flag pointer. If a non-null pointer is + * assigned, then the parser will periodically read from this pointer during + * parsing. If it reads a non-zero value, it will halt early, returning NULL. + * See `ts_parser_parse` for more information. + */ +void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag); + +/** + * Get the parser's current cancellation flag pointer. + */ +const size_t *ts_parser_cancellation_flag(const TSParser *self); + +/** + * Set the logger that a parser should use during parsing. + * + * The parser does not take ownership over the logger payload. If a logger was + * previously assigned, the caller is responsible for releasing any memory + * owned by the previous logger. + */ +void ts_parser_set_logger(TSParser *self, TSLogger logger); + +/** + * Get the parser's current logger. + */ +TSLogger ts_parser_logger(const TSParser *self); + +/** + * Set the file descriptor to which the parser should write debugging graphs + * during parsing. The graphs are formatted in the DOT language. You may want + * to pipe these graphs directly to a `dot(1)` process in order to generate + * SVG output. You can turn off this logging by passing a negative number. + */ +void ts_parser_print_dot_graphs(TSParser *self, int file); + +/** + * Set whether or not the parser should halt immediately upon detecting an + * error. This will generally result in a syntax tree with an error at the + * root, and one or more partial syntax trees within the error. This behavior + * may not be supported long-term. + */ +void ts_parser_halt_on_error(TSParser *self, bool halt); + +/******************/ +/* Section - Tree */ +/******************/ + +/** + * Create a shallow copy of the syntax tree. This is very fast. + * + * You need to copy a syntax tree in order to use it on more than one thread at + * a time, as syntax trees are not thread safe. + */ +TSTree *ts_tree_copy(const TSTree *self); + +/** + * Delete the syntax tree, freeing all of the memory that it used. + */ +void ts_tree_delete(TSTree *self); + +/** + * Get the root node of the syntax tree. + */ +TSNode ts_tree_root_node(const TSTree *self); + +/** + * Get the language that was used to parse the syntax tree. + */ +const TSLanguage *ts_tree_language(const TSTree *); + +/** + * Edit the syntax tree to keep it in sync with source code that has been + * edited. + * + * You must describe the edit both in terms of byte offsets and in terms of + * (row, column) coordinates. + */ +void ts_tree_edit(TSTree *self, const TSInputEdit *edit); + +/** + * Compare a new syntax tree to a previous syntax tree representing the same + * document, returning an array of ranges whose syntactic structure has changed. + * + * For this to work correctly, the old syntax tree must have been edited such + * that its ranges match up to the new tree. Generally, you'll want to call + * this function right after calling one of the `ts_parser_parse` functions, + * passing in the new tree that was returned from `ts_parser_parse` and the old + * tree that was passed as a parameter. + * + * The returned array is allocated using `malloc` and the caller is responsible + * for freeing it using `free`. The length of the array will be written to the + * given `length` pointer. + */ +TSRange *ts_tree_get_changed_ranges( + const TSTree *self, + const TSTree *old_tree, + uint32_t *length +); + +/** + * Write a DOT graph describing the syntax tree to the given file. + */ +void ts_tree_print_dot_graph(const TSTree *, FILE *); + +/******************/ +/* Section - Node */ +/******************/ + +/** + * Get the node's type as a null-terminated string. + */ +const char *ts_node_type(TSNode); + +/** + * Get the node's type as a numerical id. + */ +TSSymbol ts_node_symbol(TSNode); + +/** + * Get the node's start byte. + */ +uint32_t ts_node_start_byte(TSNode); + +/** + * Get the node's start position in terms of rows and columns. + */ +TSPoint ts_node_start_point(TSNode); + +/** + * Get the node's end byte. + */ +uint32_t ts_node_end_byte(TSNode); + +/** + * Get the node's end position in terms of rows and columns. + */ +TSPoint ts_node_end_point(TSNode); + +/** + * Get an S-expression representing the node as a string. + * + * This string is allocated with `malloc` and the caller is responsible for + * freeing it using `free`. + */ +char *ts_node_string(TSNode); + +/** + * Check if the node is null. Functions like `ts_node_child` and + * `ts_node_next_sibling` will return a null node to indicate that no such node + * was found. + */ +bool ts_node_is_null(TSNode); + +/** + * Check if the node is *named*. Named nodes correspond to named rules in the + * grammar, whereas *anonymous* nodes correspond to string literals in the + * grammar. + */ +bool ts_node_is_named(TSNode); + +/** + * Check if the node is *missing*. Missing nodes are inserted by the parser in + * order to recover from certain kinds of syntax errors. + */ +bool ts_node_is_missing(TSNode); + +/** + * Check if the node is *missing*. Missing nodes are inserted by the parser in + * order to recover from certain kinds of syntax errors. + */ +bool ts_node_is_extra(TSNode); + +/** + * Check if a syntax node has been edited. + */ +bool ts_node_has_changes(TSNode); + +/** + * Check if the node is a syntax error or contains any syntax errors. + */ +bool ts_node_has_error(TSNode); + +/** + * Get the node's immediate parent. + */ +TSNode ts_node_parent(TSNode); + +/** + * Get the node's child at the given index, where zero represents the first + * child. + */ +TSNode ts_node_child(TSNode, uint32_t); + +/** + * Get the node's number of children. + */ +uint32_t ts_node_child_count(TSNode); + +/** + * Get the node's *named* child at the given index. + * + * See also `ts_node_is_named`. + */ +TSNode ts_node_named_child(TSNode, uint32_t); + +/** + * Get the node's number of *named* children. + * + * See also `ts_node_is_named`. + */ +uint32_t ts_node_named_child_count(TSNode); + +/** + * Get the node's child with the given field name. + */ +TSNode ts_node_child_by_field_name( + TSNode self, + const char *field_name, + uint32_t field_name_length +); + +/** + * Get the node's child with the given numerical field id. + * + * You can convert a field name to an id using the + * `ts_language_field_id_for_name` function. + */ +TSNode ts_node_child_by_field_id(TSNode, TSFieldId); + +/** + * Get the node's next / previous sibling. + */ +TSNode ts_node_next_sibling(TSNode); +TSNode ts_node_prev_sibling(TSNode); + +/** + * Get the node's next / previous *named* sibling. + */ +TSNode ts_node_next_named_sibling(TSNode); +TSNode ts_node_prev_named_sibling(TSNode); + +/** + * Get the node's first child that extends beyond the given byte offset. + */ +TSNode ts_node_first_child_for_byte(TSNode, uint32_t); + +/** + * Get the node's first named child that extends beyond the given byte offset. + */ +TSNode ts_node_first_named_child_for_byte(TSNode, uint32_t); + +/** + * Get the smallest node within this node that spans the given range of bytes + * or (row, column) positions. + */ +TSNode ts_node_descendant_for_byte_range(TSNode, uint32_t, uint32_t); +TSNode ts_node_descendant_for_point_range(TSNode, TSPoint, TSPoint); + +/** + * Get the smallest named node within this node that spans the given range of + * bytes or (row, column) positions. + */ +TSNode ts_node_named_descendant_for_byte_range(TSNode, uint32_t, uint32_t); +TSNode ts_node_named_descendant_for_point_range(TSNode, TSPoint, TSPoint); + +/** + * Edit the node to keep it in-sync with source code that has been edited. + * + * This function is only rarely needed. When you edit a syntax tree with the + * `ts_tree_edit` function, all of the nodes that you retrieve from the tree + * afterward will already reflect the edit. You only need to use `ts_node_edit` + * when you have a `TSNode` instance that you want to keep and continue to use + * after an edit. + */ +void ts_node_edit(TSNode *, const TSInputEdit *); + +/** + * Check if two nodes are identical. + */ +bool ts_node_eq(TSNode, TSNode); + +/************************/ +/* Section - TreeCursor */ +/************************/ + +/** + * Create a new tree cursor starting from the given node. + * + * A tree cursor allows you to walk a syntax tree more efficiently than is + * possible using the `TSNode` functions. It is a mutable object that is always + * on a certain syntax node, and can be moved imperatively to different nodes. + */ +TSTreeCursor ts_tree_cursor_new(TSNode); + +/** + * Delete a tree cursor, freeing all of the memory that it used. + */ +void ts_tree_cursor_delete(TSTreeCursor *); + +/** + * Re-initialize a tree cursor to start at a different ndoe. + */ +void ts_tree_cursor_reset(TSTreeCursor *, TSNode); + +/** + * Get the tree cursor's current node. + */ +TSNode ts_tree_cursor_current_node(const TSTreeCursor *); + +/** + * Get the field name of the tree cursor's current node. + * + * This returns `NULL` if the current node doesn't have a field. + * See also `ts_node_child_by_field_name`. + */ +const char *ts_tree_cursor_current_field_name(const TSTreeCursor *); + +/** + * Get the field name of the tree cursor's current node. + * + * This returns zero if the current node doesn't have a field. + * See also `ts_node_child_by_field_id`, `ts_language_field_id_for_name`. + */ +TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *); + +/** + * Move the cursor to the parent of its current node. + * + * This returns `true` if the cursor successfully moved, and returns `false` + * if there was no parent node (the cursor was already on the root node). + */ +bool ts_tree_cursor_goto_parent(TSTreeCursor *); + +/** + * Move the cursor to the next sibling of its current node. + * + * This returns `true` if the cursor successfully moved, and returns `false` + * if there was no next sibling node. + */ +bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); + +/** + * Move the cursor to the first schild of its current node. + * + * This returns `true` if the cursor successfully moved, and returns `false` + * if there were no children. + */ +bool ts_tree_cursor_goto_first_child(TSTreeCursor *); + +/** + * Move the cursor to the first schild of its current node that extends beyond + * the given byte offset. + * + * This returns the index of the child node if one was found, and returns -1 + * if no such child was found. + */ +int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t); + +TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *); + +/**********************/ +/* Section - Language */ +/**********************/ + +/** + * Get the number of distinct node types in the language. + */ +uint32_t ts_language_symbol_count(const TSLanguage *); + +/** + * Get a node type string for the given numerical id. + */ +const char *ts_language_symbol_name(const TSLanguage *, TSSymbol); + +/** + * Get the numerical id for the given node type string. + */ +TSSymbol ts_language_symbol_for_name(const TSLanguage *, const char *); + +/** + * Get the number of distinct field names in the language. + */ +uint32_t ts_language_field_count(const TSLanguage *); + +/** + * Get the field name string for the given numerical id. + */ +const char *ts_language_field_name_for_id(const TSLanguage *, TSFieldId); + +/** + * Get the numerical id for the given field name string. + */ +TSFieldId ts_language_field_id_for_name(const TSLanguage *, const char *, uint32_t); + +/** + * Check whether the given node type id belongs to named nodes, anonymous nodes, + * or a hidden nodes. + * + * See also `ts_node_is_named`. Hidden nodes are never returned from the API. + */ +TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol); + +/** + * Get the ABI version number for this language. This version number is used + * to ensure that languages were generated by a compatible version of + * Tree-sitter. + * + * See also `ts_parser_set_language`. + */ +uint32_t ts_language_version(const TSLanguage *); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_API_H_ diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h new file mode 100644 index 0000000000..bc77e687bf --- /dev/null +++ b/src/tree_sitter/array.h @@ -0,0 +1,142 @@ +#ifndef TREE_SITTER_ARRAY_H_ +#define TREE_SITTER_ARRAY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include "./alloc.h" + +#define Array(T) \ + struct { \ + T *contents; \ + uint32_t size; \ + uint32_t capacity; \ + } + +#define array_init(self) \ + ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) + +#define array_new() \ + { NULL, 0, 0 } + +#define array_get(self, index) \ + (assert((uint32_t)index < (self)->size), &(self)->contents[index]) + +#define array_front(self) array_get(self, 0) + +#define array_back(self) array_get(self, (self)->size - 1) + +#define array_clear(self) ((self)->size = 0) + +#define array_reserve(self, new_capacity) \ + array__reserve((VoidArray *)(self), array__elem_size(self), new_capacity) + +#define array_erase(self, index) \ + array__erase((VoidArray *)(self), array__elem_size(self), index) + +#define array_delete(self) array__delete((VoidArray *)self) + +#define array_push(self, element) \ + (array__grow((VoidArray *)(self), 1, array__elem_size(self)), \ + (self)->contents[(self)->size++] = (element)) + +#define array_grow_by(self, count) \ + (array__grow((VoidArray *)(self), count, array__elem_size(self)), \ + memset((self)->contents + (self)->size, 0, (count) * array__elem_size(self)), \ + (self)->size += (count)) + +#define array_push_all(self, other) \ + array_splice((self), (self)->size, 0, (other)->size, (other)->contents) + +#define array_splice(self, index, old_count, new_count, new_contents) \ + array__splice((VoidArray *)(self), array__elem_size(self), index, old_count, \ + new_count, new_contents) + +#define array_insert(self, index, element) \ + array__splice((VoidArray *)(self), array__elem_size(self), index, 0, 1, &element) + +#define array_pop(self) ((self)->contents[--(self)->size]) + +#define array_assign(self, other) \ + array__assign((VoidArray *)(self), (const VoidArray *)(other), array__elem_size(self)) + +// Private + +typedef Array(void) VoidArray; + +#define array__elem_size(self) sizeof(*(self)->contents) + +static inline void array__delete(VoidArray *self) { + ts_free(self->contents); + self->contents = NULL; + self->size = 0; + self->capacity = 0; +} + +static inline void array__erase(VoidArray *self, size_t element_size, + uint32_t index) { + assert(index < self->size); + char *contents = (char *)self->contents; + memmove(contents + index * element_size, contents + (index + 1) * element_size, + (self->size - index - 1) * element_size); + self->size--; +} + +static inline void array__reserve(VoidArray *self, size_t element_size, uint32_t new_capacity) { + if (new_capacity > self->capacity) { + if (self->contents) { + self->contents = ts_realloc(self->contents, new_capacity * element_size); + } else { + self->contents = ts_calloc(new_capacity, element_size); + } + self->capacity = new_capacity; + } +} + +static inline void array__assign(VoidArray *self, const VoidArray *other, size_t element_size) { + array__reserve(self, element_size, other->size); + self->size = other->size; + memcpy(self->contents, other->contents, self->size * element_size); +} + +static inline void array__grow(VoidArray *self, size_t count, size_t element_size) { + size_t new_size = self->size + count; + if (new_size > self->capacity) { + size_t new_capacity = self->capacity * 2; + if (new_capacity < 8) new_capacity = 8; + if (new_capacity < new_size) new_capacity = new_size; + array__reserve(self, element_size, new_capacity); + } +} + +static inline void array__splice(VoidArray *self, size_t element_size, + uint32_t index, uint32_t old_count, + uint32_t new_count, const void *elements) { + uint32_t new_size = self->size + new_count - old_count; + uint32_t old_end = index + old_count; + uint32_t new_end = index + new_count; + assert(old_end <= self->size); + + array__reserve(self, element_size, new_size); + + char *contents = (char *)self->contents; + if (self->size > old_end) + memmove(contents + new_end * element_size, contents + old_end * element_size, + (self->size - old_end) * element_size); + if (new_count > 0) + memcpy((contents + index * element_size), elements, + new_count * element_size); + self->size += new_count - old_count; +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_ARRAY_H_ diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h new file mode 100644 index 0000000000..301ee36700 --- /dev/null +++ b/src/tree_sitter/atomic.h @@ -0,0 +1,42 @@ +#ifndef TREE_SITTER_ATOMIC_H_ +#define TREE_SITTER_ATOMIC_H_ + +#include + +#ifdef _WIN32 + +#include + +static inline size_t atomic_load(const volatile size_t *p) { + return *p; +} + +static inline uint32_t atomic_inc(volatile uint32_t *p) { + return InterlockedIncrement(p); +} + +static inline uint32_t atomic_dec(volatile uint32_t *p) { + return InterlockedDecrement(p); +} + +#else + +static inline size_t atomic_load(const volatile size_t *p) { +#ifdef __ATOMIC_RELAXED + return __atomic_load_n(p, __ATOMIC_RELAXED); +#else + return __sync_fetch_and_add((volatile size_t *)p, 0); +#endif +} + +static inline uint32_t atomic_inc(volatile uint32_t *p) { + return __sync_add_and_fetch(p, 1u); +} + +static inline uint32_t atomic_dec(volatile uint32_t *p) { + return __sync_sub_and_fetch(p, 1u); +} + +#endif + +#endif // TREE_SITTER_ATOMIC_H_ diff --git a/src/tree_sitter/clock.h b/src/tree_sitter/clock.h new file mode 100644 index 0000000000..94545f3566 --- /dev/null +++ b/src/tree_sitter/clock.h @@ -0,0 +1,141 @@ +#ifndef TREE_SITTER_CLOCK_H_ +#define TREE_SITTER_CLOCK_H_ + +#include + +typedef uint64_t TSDuration; + +#ifdef _WIN32 + +// Windows: +// * Represent a time as a performance counter value. +// * Represent a duration as a number of performance counter ticks. + +#include +typedef uint64_t TSClock; + +static inline TSDuration duration_from_micros(uint64_t micros) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return micros * (uint64_t)frequency.QuadPart / 1000000; +} + +static inline uint64_t duration_to_micros(TSDuration self) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return self * 1000000 / (uint64_t)frequency.QuadPart; +} + +static inline TSClock clock_null(void) { + return 0; +} + +static inline TSClock clock_now(void) { + LARGE_INTEGER result; + QueryPerformanceCounter(&result); + return (uint64_t)result.QuadPart; +} + +static inline TSClock clock_after(TSClock base, TSDuration duration) { + return base + duration; +} + +static inline bool clock_is_null(TSClock self) { + return !self; +} + +static inline bool clock_is_gt(TSClock self, TSClock other) { + return self > other; +} + +#elif defined(CLOCK_MONOTONIC) && !defined(__APPLE__) + +// POSIX with monotonic clock support (Linux) +// * Represent a time as a monotonic (seconds, nanoseconds) pair. +// * Represent a duration as a number of microseconds. +// +// On these platforms, parse timeouts will correspond accurately to +// real time, regardless of what other processes are running. + +#include +typedef struct timespec TSClock; + +static inline TSDuration duration_from_micros(uint64_t micros) { + return micros; +} + +static inline uint64_t duration_to_micros(TSDuration self) { + return self; +} + +static inline TSClock clock_now(void) { + TSClock result; + clock_gettime(CLOCK_MONOTONIC, &result); + return result; +} + +static inline TSClock clock_null(void) { + return (TSClock) {0, 0}; +} + +static inline TSClock clock_after(TSClock base, TSDuration duration) { + TSClock result = base; + result.tv_sec += duration / 1000000; + result.tv_nsec += (duration % 1000000) * 1000; + return result; +} + +static inline bool clock_is_null(TSClock self) { + return !self.tv_sec; +} + +static inline bool clock_is_gt(TSClock self, TSClock other) { + if (self.tv_sec > other.tv_sec) return true; + if (self.tv_sec < other.tv_sec) return false; + return self.tv_nsec > other.tv_nsec; +} + +#else + +// macOS or POSIX without monotonic clock support +// * Represent a time as a process clock value. +// * Represent a duration as a number of process clock ticks. +// +// On these platforms, parse timeouts may be affected by other processes, +// which is not ideal, but is better than using a non-monotonic time API +// like `gettimeofday`. + +#include +typedef uint64_t TSClock; + +static inline TSDuration duration_from_micros(uint64_t micros) { + return micros * (uint64_t)CLOCKS_PER_SEC / 1000000; +} + +static inline uint64_t duration_to_micros(TSDuration self) { + return self * 1000000 / (uint64_t)CLOCKS_PER_SEC; +} + +static inline TSClock clock_null(void) { + return 0; +} + +static inline TSClock clock_now(void) { + return (uint64_t)clock(); +} + +static inline TSClock clock_after(TSClock base, TSDuration duration) { + return base + duration; +} + +static inline bool clock_is_null(TSClock self) { + return !self; +} + +static inline bool clock_is_gt(TSClock self, TSClock other) { + return self > other; +} + +#endif + +#endif // TREE_SITTER_CLOCK_H_ diff --git a/src/tree_sitter/error_costs.h b/src/tree_sitter/error_costs.h new file mode 100644 index 0000000000..32d3666a66 --- /dev/null +++ b/src/tree_sitter/error_costs.h @@ -0,0 +1,11 @@ +#ifndef TREE_SITTER_ERROR_COSTS_H_ +#define TREE_SITTER_ERROR_COSTS_H_ + +#define ERROR_STATE 0 +#define ERROR_COST_PER_RECOVERY 500 +#define ERROR_COST_PER_MISSING_TREE 110 +#define ERROR_COST_PER_SKIPPED_TREE 100 +#define ERROR_COST_PER_SKIPPED_LINE 30 +#define ERROR_COST_PER_SKIPPED_CHAR 1 + +#endif diff --git a/src/tree_sitter/get_changed_ranges.c b/src/tree_sitter/get_changed_ranges.c new file mode 100644 index 0000000000..5bd1d814bd --- /dev/null +++ b/src/tree_sitter/get_changed_ranges.c @@ -0,0 +1,482 @@ +#include "./get_changed_ranges.h" +#include "./subtree.h" +#include "./language.h" +#include "./error_costs.h" +#include "./tree_cursor.h" +#include + +// #define DEBUG_GET_CHANGED_RANGES + +static void ts_range_array_add(TSRangeArray *self, Length start, Length end) { + if (self->size > 0) { + TSRange *last_range = array_back(self); + if (start.bytes <= last_range->end_byte) { + last_range->end_byte = end.bytes; + last_range->end_point = end.extent; + return; + } + } + + if (start.bytes < end.bytes) { + TSRange range = { start.extent, end.extent, start.bytes, end.bytes }; + array_push(self, range); + } +} + +bool ts_range_array_intersects(const TSRangeArray *self, unsigned start_index, + uint32_t start_byte, uint32_t end_byte) { + for (unsigned i = start_index; i < self->size; i++) { + TSRange *range = &self->contents[i]; + if (range->end_byte > start_byte) { + if (range->start_byte >= end_byte) break; + return true; + } + } + return false; +} + +void ts_range_array_get_changed_ranges( + const TSRange *old_ranges, unsigned old_range_count, + const TSRange *new_ranges, unsigned new_range_count, + TSRangeArray *differences +) { + unsigned new_index = 0; + unsigned old_index = 0; + Length current_position = length_zero(); + bool in_old_range = false; + bool in_new_range = false; + + while (old_index < old_range_count || new_index < new_range_count) { + const TSRange *old_range = &old_ranges[old_index]; + const TSRange *new_range = &new_ranges[new_index]; + + Length next_old_position; + if (in_old_range) { + next_old_position = (Length) {old_range->end_byte, old_range->end_point}; + } else if (old_index < old_range_count) { + next_old_position = (Length) {old_range->start_byte, old_range->start_point}; + } else { + next_old_position = LENGTH_MAX; + } + + Length next_new_position; + if (in_new_range) { + next_new_position = (Length) {new_range->end_byte, new_range->end_point}; + } else if (new_index < new_range_count) { + next_new_position = (Length) {new_range->start_byte, new_range->start_point}; + } else { + next_new_position = LENGTH_MAX; + } + + if (next_old_position.bytes < next_new_position.bytes) { + if (in_old_range != in_new_range) { + ts_range_array_add(differences, current_position, next_old_position); + } + if (in_old_range) old_index++; + current_position = next_old_position; + in_old_range = !in_old_range; + } else if (next_new_position.bytes < next_old_position.bytes) { + if (in_old_range != in_new_range) { + ts_range_array_add(differences, current_position, next_new_position); + } + if (in_new_range) new_index++; + current_position = next_new_position; + in_new_range = !in_new_range; + } else { + if (in_old_range != in_new_range) { + ts_range_array_add(differences, current_position, next_new_position); + } + if (in_old_range) old_index++; + if (in_new_range) new_index++; + in_old_range = !in_old_range; + in_new_range = !in_new_range; + current_position = next_new_position; + } + } +} + +typedef struct { + TreeCursor cursor; + const TSLanguage *language; + unsigned visible_depth; + bool in_padding; +} Iterator; + +static Iterator iterator_new(TreeCursor *cursor, const Subtree *tree, const TSLanguage *language) { + array_clear(&cursor->stack); + array_push(&cursor->stack, ((TreeCursorEntry){ + .subtree = tree, + .position = length_zero(), + .child_index = 0, + .structural_child_index = 0, + })); + return (Iterator) { + .cursor = *cursor, + .language = language, + .visible_depth = 1, + .in_padding = false, + }; +} + +static bool iterator_done(Iterator *self) { + return self->cursor.stack.size == 0; +} + +static Length iterator_start_position(Iterator *self) { + TreeCursorEntry entry = *array_back(&self->cursor.stack); + if (self->in_padding) { + return entry.position; + } else { + return length_add(entry.position, ts_subtree_padding(*entry.subtree)); + } +} + +static Length iterator_end_position(Iterator *self) { + TreeCursorEntry entry = *array_back(&self->cursor.stack); + Length result = length_add(entry.position, ts_subtree_padding(*entry.subtree)); + if (self->in_padding) { + return result; + } else { + return length_add(result, ts_subtree_size(*entry.subtree)); + } +} + +static bool iterator_tree_is_visible(const Iterator *self) { + TreeCursorEntry entry = *array_back(&self->cursor.stack); + if (ts_subtree_visible(*entry.subtree)) return true; + if (self->cursor.stack.size > 1) { + Subtree parent = *self->cursor.stack.contents[self->cursor.stack.size - 2].subtree; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->language, + parent.ptr->production_id + ); + return alias_sequence && alias_sequence[entry.structural_child_index] != 0; + } + return false; +} + +static void iterator_get_visible_state(const Iterator *self, Subtree *tree, + TSSymbol *alias_symbol, uint32_t *start_byte) { + uint32_t i = self->cursor.stack.size - 1; + + if (self->in_padding) { + if (i == 0) return; + i--; + } + + for (; i + 1 > 0; i--) { + TreeCursorEntry entry = self->cursor.stack.contents[i]; + + if (i > 0) { + const Subtree *parent = self->cursor.stack.contents[i - 1].subtree; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->language, + parent->ptr->production_id + ); + if (alias_sequence) { + *alias_symbol = alias_sequence[entry.structural_child_index]; + } + } + + if (ts_subtree_visible(*entry.subtree) || *alias_symbol) { + *tree = *entry.subtree; + *start_byte = entry.position.bytes; + break; + } + } +} + +static void iterator_ascend(Iterator *self) { + if (iterator_done(self)) return; + if (iterator_tree_is_visible(self) && !self->in_padding) self->visible_depth--; + if (array_back(&self->cursor.stack)->child_index > 0) self->in_padding = false; + self->cursor.stack.size--; +} + +static bool iterator_descend(Iterator *self, uint32_t goal_position) { + if (self->in_padding) return false; + + bool did_descend; + do { + did_descend = false; + TreeCursorEntry entry = *array_back(&self->cursor.stack); + Length position = entry.position; + uint32_t structural_child_index = 0; + for (uint32_t i = 0, n = ts_subtree_child_count(*entry.subtree); i < n; i++) { + const Subtree *child = &entry.subtree->ptr->children[i]; + Length child_left = length_add(position, ts_subtree_padding(*child)); + Length child_right = length_add(child_left, ts_subtree_size(*child)); + + if (child_right.bytes > goal_position) { + array_push(&self->cursor.stack, ((TreeCursorEntry){ + .subtree = child, + .position = position, + .child_index = i, + .structural_child_index = structural_child_index, + })); + + if (iterator_tree_is_visible(self)) { + if (child_left.bytes > goal_position) { + self->in_padding = true; + } else { + self->visible_depth++; + } + return true; + } + + did_descend = true; + break; + } + + position = child_right; + if (!ts_subtree_extra(*child)) structural_child_index++; + } + } while (did_descend); + + return false; +} + +static void iterator_advance(Iterator *self) { + if (self->in_padding) { + self->in_padding = false; + if (iterator_tree_is_visible(self)) { + self->visible_depth++; + } else { + iterator_descend(self, 0); + } + return; + } + + for (;;) { + if (iterator_tree_is_visible(self)) self->visible_depth--; + TreeCursorEntry entry = array_pop(&self->cursor.stack); + if (iterator_done(self)) return; + + const Subtree *parent = array_back(&self->cursor.stack)->subtree; + uint32_t child_index = entry.child_index + 1; + if (ts_subtree_child_count(*parent) > child_index) { + Length position = length_add(entry.position, ts_subtree_total_size(*entry.subtree)); + uint32_t structural_child_index = entry.structural_child_index; + if (!ts_subtree_extra(*entry.subtree)) structural_child_index++; + const Subtree *next_child = &parent->ptr->children[child_index]; + + array_push(&self->cursor.stack, ((TreeCursorEntry){ + .subtree = next_child, + .position = position, + .child_index = child_index, + .structural_child_index = structural_child_index, + })); + + if (iterator_tree_is_visible(self)) { + if (ts_subtree_padding(*next_child).bytes > 0) { + self->in_padding = true; + } else { + self->visible_depth++; + } + } else { + iterator_descend(self, 0); + } + break; + } + } +} + +typedef enum { + IteratorDiffers, + IteratorMayDiffer, + IteratorMatches, +} IteratorComparison; + +static IteratorComparison iterator_compare(const Iterator *old_iter, const Iterator *new_iter) { + Subtree old_tree = NULL_SUBTREE; + Subtree new_tree = NULL_SUBTREE; + uint32_t old_start = 0; + uint32_t new_start = 0; + TSSymbol old_alias_symbol = 0; + TSSymbol new_alias_symbol = 0; + iterator_get_visible_state(old_iter, &old_tree, &old_alias_symbol, &old_start); + iterator_get_visible_state(new_iter, &new_tree, &new_alias_symbol, &new_start); + + if (!old_tree.ptr && !new_tree.ptr) return IteratorMatches; + if (!old_tree.ptr || !new_tree.ptr) return IteratorDiffers; + + if ( + old_alias_symbol == new_alias_symbol && + ts_subtree_symbol(old_tree) == ts_subtree_symbol(new_tree) + ) { + if (old_start == new_start && + !ts_subtree_has_changes(old_tree) && + ts_subtree_symbol(old_tree) != ts_builtin_sym_error && + ts_subtree_size(old_tree).bytes == ts_subtree_size(new_tree).bytes && + ts_subtree_parse_state(old_tree) != TS_TREE_STATE_NONE && + ts_subtree_parse_state(new_tree) != TS_TREE_STATE_NONE && + (ts_subtree_parse_state(old_tree) == ERROR_STATE) == + (ts_subtree_parse_state(new_tree) == ERROR_STATE)) { + return IteratorMatches; + } else { + return IteratorMayDiffer; + } + } + + return IteratorDiffers; +} + +#ifdef DEBUG_GET_CHANGED_RANGES +static inline void iterator_print_state(Iterator *self) { + TreeCursorEntry entry = *array_back(&self->cursor.stack); + TSPoint start = iterator_start_position(self).extent; + TSPoint end = iterator_end_position(self).extent; + const char *name = ts_language_symbol_name(self->language, ts_subtree_symbol(*entry.subtree)); + printf( + "(%-25s %s\t depth:%u [%u, %u] - [%u, %u])", + name, self->in_padding ? "(p)" : " ", + self->visible_depth, + start.row + 1, start.column, + end.row + 1, end.column + ); +} +#endif + +unsigned ts_subtree_get_changed_ranges(const Subtree *old_tree, const Subtree *new_tree, + TreeCursor *cursor1, TreeCursor *cursor2, + const TSLanguage *language, + const TSRangeArray *included_range_differences, + TSRange **ranges) { + TSRangeArray results = array_new(); + + Iterator old_iter = iterator_new(cursor1, old_tree, language); + Iterator new_iter = iterator_new(cursor2, new_tree, language); + + unsigned included_range_difference_index = 0; + + Length position = iterator_start_position(&old_iter); + Length next_position = iterator_start_position(&new_iter); + if (position.bytes < next_position.bytes) { + ts_range_array_add(&results, position, next_position); + position = next_position; + } else if (position.bytes > next_position.bytes) { + ts_range_array_add(&results, next_position, position); + next_position = position; + } + + do { + #ifdef DEBUG_GET_CHANGED_RANGES + printf("At [%-2u, %-2u] Compare ", position.extent.row + 1, position.extent.column); + iterator_print_state(&old_iter); + printf("\tvs\t"); + iterator_print_state(&new_iter); + puts(""); + #endif + + // Compare the old and new subtrees. + IteratorComparison comparison = iterator_compare(&old_iter, &new_iter); + + // Even if the two subtrees appear to be identical, they could differ + // internally if they contain a range of text that was previously + // excluded from the parse, and is now included, or vice-versa. + if (comparison == IteratorMatches && ts_range_array_intersects( + included_range_differences, + included_range_difference_index, + position.bytes, + iterator_end_position(&old_iter).bytes + )) { + comparison = IteratorMayDiffer; + } + + bool is_changed = false; + switch (comparison) { + // If the subtrees are definitely identical, move to the end + // of both subtrees. + case IteratorMatches: + next_position = iterator_end_position(&old_iter); + break; + + // If the subtrees might differ internally, descend into both + // subtrees, finding the first child that spans the current position. + case IteratorMayDiffer: + if (iterator_descend(&old_iter, position.bytes)) { + if (!iterator_descend(&new_iter, position.bytes)) { + is_changed = true; + next_position = iterator_end_position(&old_iter); + } + } else if (iterator_descend(&new_iter, position.bytes)) { + is_changed = true; + next_position = iterator_end_position(&new_iter); + } else { + next_position = length_min( + iterator_end_position(&old_iter), + iterator_end_position(&new_iter) + ); + } + break; + + // If the subtrees are different, record a change and then move + // to the end of both subtrees. + case IteratorDiffers: + is_changed = true; + next_position = length_min( + iterator_end_position(&old_iter), + iterator_end_position(&new_iter) + ); + break; + } + + // Ensure that both iterators are caught up to the current position. + while ( + !iterator_done(&old_iter) && + iterator_end_position(&old_iter).bytes <= next_position.bytes + ) iterator_advance(&old_iter); + while ( + !iterator_done(&new_iter) && + iterator_end_position(&new_iter).bytes <= next_position.bytes + ) iterator_advance(&new_iter); + + // Ensure that both iterators are at the same depth in the tree. + while (old_iter.visible_depth > new_iter.visible_depth) { + iterator_ascend(&old_iter); + } + while (new_iter.visible_depth > old_iter.visible_depth) { + iterator_ascend(&new_iter); + } + + if (is_changed) { + #ifdef DEBUG_GET_CHANGED_RANGES + printf( + " change: [[%u, %u] - [%u, %u]]\n", + position.extent.row + 1, position.extent.column, + next_position.extent.row + 1, next_position.extent.column + ); + #endif + + ts_range_array_add(&results, position, next_position); + } + + position = next_position; + + // Keep track of the current position in the included range differences + // array in order to avoid scanning the entire array on each iteration. + while (included_range_difference_index < included_range_differences->size) { + const TSRange *range = &included_range_differences->contents[ + included_range_difference_index + ]; + if (range->end_byte <= position.bytes) { + included_range_difference_index++; + } else { + break; + } + } + } while (!iterator_done(&old_iter) && !iterator_done(&new_iter)); + + Length old_size = ts_subtree_total_size(*old_tree); + Length new_size = ts_subtree_total_size(*new_tree); + if (old_size.bytes < new_size.bytes) { + ts_range_array_add(&results, old_size, new_size); + } else if (new_size.bytes < old_size.bytes) { + ts_range_array_add(&results, new_size, old_size); + } + + *cursor1 = old_iter.cursor; + *cursor2 = new_iter.cursor; + *ranges = results.contents; + return results.size; +} diff --git a/src/tree_sitter/get_changed_ranges.h b/src/tree_sitter/get_changed_ranges.h new file mode 100644 index 0000000000..a1f1dbb430 --- /dev/null +++ b/src/tree_sitter/get_changed_ranges.h @@ -0,0 +1,36 @@ +#ifndef TREE_SITTER_GET_CHANGED_RANGES_H_ +#define TREE_SITTER_GET_CHANGED_RANGES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./tree_cursor.h" +#include "./subtree.h" + +typedef Array(TSRange) TSRangeArray; + +void ts_range_array_get_changed_ranges( + const TSRange *old_ranges, unsigned old_range_count, + const TSRange *new_ranges, unsigned new_range_count, + TSRangeArray *differences +); + +bool ts_range_array_intersects( + const TSRangeArray *self, unsigned start_index, + uint32_t start_byte, uint32_t end_byte +); + +unsigned ts_subtree_get_changed_ranges( + const Subtree *old_tree, const Subtree *new_tree, + TreeCursor *cursor1, TreeCursor *cursor2, + const TSLanguage *language, + const TSRangeArray *included_range_differences, + TSRange **ranges +); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_GET_CHANGED_RANGES_H_ diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c new file mode 100644 index 0000000000..1bfb1a8d03 --- /dev/null +++ b/src/tree_sitter/language.c @@ -0,0 +1,107 @@ +#include "./language.h" +#include "./subtree.h" +#include "./error_costs.h" +#include + +void ts_language_table_entry(const TSLanguage *self, TSStateId state, + TSSymbol symbol, TableEntry *result) { + if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { + result->action_count = 0; + result->is_reusable = false; + result->actions = NULL; + } else { + assert(symbol < self->token_count); + uint32_t action_index = ts_language_lookup(self, state, symbol); + const TSParseActionEntry *entry = &self->parse_actions[action_index]; + result->action_count = entry->count; + result->is_reusable = entry->reusable; + result->actions = (const TSParseAction *)(entry + 1); + } +} + +uint32_t ts_language_symbol_count(const TSLanguage *language) { + return language->symbol_count + language->alias_count; +} + +uint32_t ts_language_version(const TSLanguage *language) { + return language->version; +} + +TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *language, TSSymbol symbol) { + if (symbol == ts_builtin_sym_error) { + return (TSSymbolMetadata){.visible = true, .named = true}; + } else if (symbol == ts_builtin_sym_error_repeat) { + return (TSSymbolMetadata){.visible = false, .named = false}; + } else { + return language->symbol_metadata[symbol]; + } +} + +const char *ts_language_symbol_name(const TSLanguage *language, TSSymbol symbol) { + if (symbol == ts_builtin_sym_error) { + return "ERROR"; + } else if (symbol == ts_builtin_sym_error_repeat) { + return "_ERROR"; + } else { + return language->symbol_names[symbol]; + } +} + +TSSymbol ts_language_symbol_for_name(const TSLanguage *self, const char *name) { + if (!strcmp(name, "ERROR")) return ts_builtin_sym_error; + + uint32_t count = ts_language_symbol_count(self); + for (TSSymbol i = 0; i < count; i++) { + if (!strcmp(self->symbol_names[i], name)) { + return i; + } + } + return 0; +} + +TSSymbolType ts_language_symbol_type(const TSLanguage *language, TSSymbol symbol) { + TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); + if (metadata.named) { + return TSSymbolTypeRegular; + } else if (metadata.visible) { + return TSSymbolTypeAnonymous; + } else { + return TSSymbolTypeAuxiliary; + } +} + +uint32_t ts_language_field_count(const TSLanguage *self) { + if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) { + return self->field_count; + } else { + return 0; + } +} + +const char *ts_language_field_name_for_id(const TSLanguage *self, TSFieldId id) { + uint32_t count = ts_language_field_count(self); + if (count) { + return self->field_names[id]; + } else { + return NULL; + } +} + +TSFieldId ts_language_field_id_for_name( + const TSLanguage *self, + const char *name, + uint32_t name_length +) { + uint32_t count = ts_language_field_count(self); + for (TSSymbol i = 1; i < count + 1; i++) { + switch (strncmp(name, self->field_names[i], name_length)) { + case 0: + return i; + case -1: + return 0; + default: + break; + } + } + return 0; +} diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h new file mode 100644 index 0000000000..0741486a1b --- /dev/null +++ b/src/tree_sitter/language.h @@ -0,0 +1,138 @@ +#ifndef TREE_SITTER_LANGUAGE_H_ +#define TREE_SITTER_LANGUAGE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./subtree.h" +#include "tree_sitter/parser.h" + +#define ts_builtin_sym_error_repeat (ts_builtin_sym_error - 1) +#define TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS 10 +#define TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES 11 + +typedef struct { + const TSParseAction *actions; + uint32_t action_count; + bool is_reusable; +} TableEntry; + +void ts_language_table_entry(const TSLanguage *, TSStateId, TSSymbol, TableEntry *); + +TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol); + +static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) { + return 0 < symbol && symbol < self->external_token_count + 1; +} + +static inline const TSParseAction *ts_language_actions(const TSLanguage *self, + TSStateId state, + TSSymbol symbol, + uint32_t *count) { + TableEntry entry; + ts_language_table_entry(self, state, symbol, &entry); + *count = entry.action_count; + return entry.actions; +} + +static inline bool ts_language_has_actions(const TSLanguage *self, + TSStateId state, + TSSymbol symbol) { + TableEntry entry; + ts_language_table_entry(self, state, symbol, &entry); + return entry.action_count > 0; +} + +static inline bool ts_language_has_reduce_action(const TSLanguage *self, + TSStateId state, + TSSymbol symbol) { + TableEntry entry; + ts_language_table_entry(self, state, symbol, &entry); + return entry.action_count > 0 && entry.actions[0].type == TSParseActionTypeReduce; +} + +static inline uint16_t ts_language_lookup( + const TSLanguage *self, + TSStateId state, + TSSymbol symbol +) { + if ( + self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES && + state >= self->large_state_count + ) { + uint32_t index = self->small_parse_table_map[state - self->large_state_count]; + const uint16_t *data = &self->small_parse_table[index]; + uint16_t section_count = *(data++); + for (unsigned i = 0; i < section_count; i++) { + uint16_t section_value = *(data++); + uint16_t symbol_count = *(data++); + for (unsigned i = 0; i < symbol_count; i++) { + if (*(data++) == symbol) return section_value; + } + } + return 0; + } else { + return self->parse_table[state * self->symbol_count + symbol]; + } +} + +static inline TSStateId ts_language_next_state(const TSLanguage *self, + TSStateId state, + TSSymbol symbol) { + if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { + return 0; + } else if (symbol < self->token_count) { + uint32_t count; + const TSParseAction *actions = ts_language_actions(self, state, symbol, &count); + if (count > 0) { + TSParseAction action = actions[count - 1]; + if (action.type == TSParseActionTypeShift || action.type == TSParseActionTypeRecover) { + return action.params.state; + } + } + return 0; + } else { + return ts_language_lookup(self, state, symbol); + } +} + +static inline const bool * +ts_language_enabled_external_tokens(const TSLanguage *self, + unsigned external_scanner_state) { + if (external_scanner_state == 0) { + return NULL; + } else { + return self->external_scanner.states + self->external_token_count * external_scanner_state; + } +} + +static inline const TSSymbol * +ts_language_alias_sequence(const TSLanguage *self, uint32_t production_id) { + return production_id > 0 ? + self->alias_sequences + production_id * self->max_alias_sequence_length : + NULL; +} + +static inline void ts_language_field_map( + const TSLanguage *self, + uint32_t production_id, + const TSFieldMapEntry **start, + const TSFieldMapEntry **end +) { + if (self->version < TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS || self->field_count == 0) { + *start = NULL; + *end = NULL; + return; + } + + TSFieldMapSlice slice = self->field_map_slices[production_id]; + *start = &self->field_map_entries[slice.index]; + *end = &self->field_map_entries[slice.index] + slice.length; +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_LANGUAGE_H_ diff --git a/src/tree_sitter/length.h b/src/tree_sitter/length.h new file mode 100644 index 0000000000..61de9fc1d5 --- /dev/null +++ b/src/tree_sitter/length.h @@ -0,0 +1,44 @@ +#ifndef TREE_SITTER_LENGTH_H_ +#define TREE_SITTER_LENGTH_H_ + +#include +#include +#include "./point.h" +#include "tree_sitter/api.h" + +typedef struct { + uint32_t bytes; + TSPoint extent; +} Length; + +static const Length LENGTH_UNDEFINED = {0, {0, 1}}; +static const Length LENGTH_MAX = {UINT32_MAX, {UINT32_MAX, UINT32_MAX}}; + +static inline bool length_is_undefined(Length length) { + return length.bytes == 0 && length.extent.column != 0; +} + +static inline Length length_min(Length len1, Length len2) { + return (len1.bytes < len2.bytes) ? len1 : len2; +} + +static inline Length length_add(Length len1, Length len2) { + Length result; + result.bytes = len1.bytes + len2.bytes; + result.extent = point_add(len1.extent, len2.extent); + return result; +} + +static inline Length length_sub(Length len1, Length len2) { + Length result; + result.bytes = len1.bytes - len2.bytes; + result.extent = point_sub(len1.extent, len2.extent); + return result; +} + +static inline Length length_zero(void) { + Length result = {0, {0, 0}}; + return result; +} + +#endif diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c new file mode 100644 index 0000000000..fdc127466f --- /dev/null +++ b/src/tree_sitter/lexer.c @@ -0,0 +1,322 @@ +#include +#include "./lexer.h" +#include "./subtree.h" +#include "./length.h" +#include "./utf16.h" +#include "utf8proc.h" + +#define LOG(...) \ + if (self->logger.log) { \ + snprintf(self->debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \ + self->logger.log(self->logger.payload, TSLogTypeLex, self->debug_buffer); \ + } + +#define LOG_CHARACTER(message, character) \ + LOG( \ + 32 <= character && character < 127 ? \ + message " character:'%c'" : \ + message " character:%d", character \ + ) + +static const char empty_chunk[3] = { 0, 0 }; + +static const int32_t BYTE_ORDER_MARK = 0xFEFF; + +static void ts_lexer__get_chunk(Lexer *self) { + self->chunk_start = self->current_position.bytes; + self->chunk = self->input.read( + self->input.payload, + self->current_position.bytes, + self->current_position.extent, + &self->chunk_size + ); + if (!self->chunk_size) self->chunk = empty_chunk; +} + +typedef utf8proc_ssize_t (*DecodeFunction)( + const utf8proc_uint8_t *, + utf8proc_ssize_t, + utf8proc_int32_t * +); + +static void ts_lexer__get_lookahead(Lexer *self) { + uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start; + const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk; + uint32_t size = self->chunk_size - position_in_chunk; + + if (size == 0) { + self->lookahead_size = 1; + self->data.lookahead = '\0'; + return; + } + + DecodeFunction decode = + self->input.encoding == TSInputEncodingUTF8 ? utf8proc_iterate : utf16_iterate; + + self->lookahead_size = decode(chunk, size, &self->data.lookahead); + + // If this chunk ended in the middle of a multi-byte character, + // try again with a fresh chunk. + if (self->data.lookahead == -1 && size < 4) { + ts_lexer__get_chunk(self); + chunk = (const uint8_t *)self->chunk; + size = self->chunk_size; + self->lookahead_size = decode(chunk, size, &self->data.lookahead); + } + + if (self->data.lookahead == -1) { + self->lookahead_size = 1; + } +} + +static void ts_lexer__advance(TSLexer *payload, bool skip) { + Lexer *self = (Lexer *)payload; + if (self->chunk == empty_chunk) + return; + + if (self->lookahead_size) { + self->current_position.bytes += self->lookahead_size; + if (self->data.lookahead == '\n') { + self->current_position.extent.row++; + self->current_position.extent.column = 0; + } else { + self->current_position.extent.column += self->lookahead_size; + } + } + + TSRange *current_range = &self->included_ranges[self->current_included_range_index]; + if (self->current_position.bytes == current_range->end_byte) { + self->current_included_range_index++; + if (self->current_included_range_index == self->included_range_count) { + self->data.lookahead = '\0'; + self->lookahead_size = 1; + return; + } else { + current_range++; + self->current_position = (Length) { + current_range->start_byte, + current_range->start_point, + }; + } + } + + if (skip) { + LOG_CHARACTER("skip", self->data.lookahead); + self->token_start_position = self->current_position; + } else { + LOG_CHARACTER("consume", self->data.lookahead); + } + + if (self->current_position.bytes >= self->chunk_start + self->chunk_size) { + ts_lexer__get_chunk(self); + } + + ts_lexer__get_lookahead(self); +} + +static void ts_lexer__mark_end(TSLexer *payload) { + Lexer *self = (Lexer *)payload; + TSRange *current_included_range = &self->included_ranges[self->current_included_range_index]; + if (self->current_included_range_index > 0 && + self->current_position.bytes == current_included_range->start_byte) { + TSRange *previous_included_range = current_included_range - 1; + self->token_end_position = (Length) { + previous_included_range->end_byte, + previous_included_range->end_point, + }; + } else { + self->token_end_position = self->current_position; + } +} + +static uint32_t ts_lexer__get_column(TSLexer *payload) { + Lexer *self = (Lexer *)payload; + uint32_t goal_byte = self->current_position.bytes; + + self->current_position.bytes -= self->current_position.extent.column; + self->current_position.extent.column = 0; + + if (self->current_position.bytes < self->chunk_start) { + ts_lexer__get_chunk(self); + } + + uint32_t result = 0; + while (self->current_position.bytes < goal_byte) { + ts_lexer__advance(payload, false); + result++; + } + + return result; +} + +static bool ts_lexer__is_at_included_range_start(TSLexer *payload) { + const Lexer *self = (const Lexer *)payload; + TSRange *current_range = &self->included_ranges[self->current_included_range_index]; + return self->current_position.bytes == current_range->start_byte; +} + +// The lexer's methods are stored as a struct field so that generated +// parsers can call them without needing to be linked against this library. + +void ts_lexer_init(Lexer *self) { + *self = (Lexer) { + .data = { + .advance = ts_lexer__advance, + .mark_end = ts_lexer__mark_end, + .get_column = ts_lexer__get_column, + .is_at_included_range_start = ts_lexer__is_at_included_range_start, + .lookahead = 0, + .result_symbol = 0, + }, + .chunk = NULL, + .chunk_start = 0, + .current_position = {UINT32_MAX, {0, 0}}, + .logger = { + .payload = NULL, + .log = NULL + }, + .current_included_range_index = 0, + }; + + self->included_ranges = NULL; + ts_lexer_set_included_ranges(self, NULL, 0); + ts_lexer_reset(self, length_zero()); +} + +void ts_lexer_delete(Lexer *self) { + ts_free(self->included_ranges); +} + +void ts_lexer_set_input(Lexer *self, TSInput input) { + self->input = input; + self->data.lookahead = 0; + self->lookahead_size = 0; + self->chunk = 0; + self->chunk_start = 0; + self->chunk_size = 0; +} + +static void ts_lexer_goto(Lexer *self, Length position) { + bool found_included_range = false; + for (unsigned i = 0; i < self->included_range_count; i++) { + TSRange *included_range = &self->included_ranges[i]; + if (included_range->end_byte > position.bytes) { + if (included_range->start_byte > position.bytes) { + position = (Length) { + .bytes = included_range->start_byte, + .extent = included_range->start_point, + }; + } + + self->current_included_range_index = i; + found_included_range = true; + break; + } + } + + if (!found_included_range) { + TSRange *last_included_range = &self->included_ranges[self->included_range_count - 1]; + position = (Length) { + .bytes = last_included_range->end_byte, + .extent = last_included_range->end_point, + }; + self->chunk = empty_chunk; + self->chunk_start = position.bytes; + self->chunk_size = 2; + } + + self->token_start_position = position; + self->token_end_position = LENGTH_UNDEFINED; + self->current_position = position; + + if (self->chunk && (position.bytes < self->chunk_start || + position.bytes >= self->chunk_start + self->chunk_size)) { + self->chunk = 0; + self->chunk_start = 0; + self->chunk_size = 0; + } + + self->lookahead_size = 0; + self->data.lookahead = 0; +} + +void ts_lexer_reset(Lexer *self, Length position) { + if (position.bytes != self->current_position.bytes) ts_lexer_goto(self, position); +} + +void ts_lexer_start(Lexer *self) { + self->token_start_position = self->current_position; + self->token_end_position = LENGTH_UNDEFINED; + self->data.result_symbol = 0; + if (!self->chunk) ts_lexer__get_chunk(self); + if (!self->lookahead_size) ts_lexer__get_lookahead(self); + if ( + self->current_position.bytes == 0 && + self->data.lookahead == BYTE_ORDER_MARK + ) ts_lexer__advance((TSLexer *)self, true); +} + +void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) { + if (length_is_undefined(self->token_end_position)) { + ts_lexer__mark_end(&self->data); + } + + uint32_t current_lookahead_end_byte = self->current_position.bytes + 1; + + // In order to determine that a byte sequence is invalid UTF8 or UTF16, + // the character decoding algorithm may have looked at the following byte. + // Therefore, the next byte *after* the current (invalid) character + // affects the interpretation of the current character. + if (self->data.lookahead == -1) { + current_lookahead_end_byte++; + } + + if (current_lookahead_end_byte > *lookahead_end_byte) { + *lookahead_end_byte = current_lookahead_end_byte; + } +} + +void ts_lexer_advance_to_end(Lexer *self) { + while (self->data.lookahead != 0) { + ts_lexer__advance((TSLexer *)self, false); + } +} + +void ts_lexer_mark_end(Lexer *self) { + ts_lexer__mark_end(&self->data); +} + +static const TSRange DEFAULT_RANGES[] = { + { + .start_point = { + .row = 0, + .column = 0, + }, + .end_point = { + .row = UINT32_MAX, + .column = UINT32_MAX, + }, + .start_byte = 0, + .end_byte = UINT32_MAX + } +}; + +void ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count) { + if (!ranges) { + ranges = DEFAULT_RANGES; + count = 1; + } + + size_t sz = count * sizeof(TSRange); + self->included_ranges = ts_realloc(self->included_ranges, sz); + memcpy(self->included_ranges, ranges, sz); + self->included_range_count = count; + ts_lexer_goto(self, self->current_position); +} + +TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) { + *count = self->included_range_count; + return self->included_ranges; +} + +#undef LOG diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h new file mode 100644 index 0000000000..f523d88f65 --- /dev/null +++ b/src/tree_sitter/lexer.h @@ -0,0 +1,48 @@ +#ifndef TREE_SITTER_LEXER_H_ +#define TREE_SITTER_LEXER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./length.h" +#include "./subtree.h" +#include "tree_sitter/api.h" +#include "tree_sitter/parser.h" + +typedef struct { + TSLexer data; + Length current_position; + Length token_start_position; + Length token_end_position; + + TSRange * included_ranges; + size_t included_range_count; + size_t current_included_range_index; + + const char *chunk; + uint32_t chunk_start; + uint32_t chunk_size; + uint32_t lookahead_size; + + TSInput input; + TSLogger logger; + char debug_buffer[TREE_SITTER_SERIALIZATION_BUFFER_SIZE]; +} Lexer; + +void ts_lexer_init(Lexer *); +void ts_lexer_delete(Lexer *); +void ts_lexer_set_input(Lexer *, TSInput); +void ts_lexer_reset(Lexer *, Length); +void ts_lexer_start(Lexer *); +void ts_lexer_finish(Lexer *, uint32_t *); +void ts_lexer_advance_to_end(Lexer *); +void ts_lexer_mark_end(Lexer *); +void ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count); +TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_LEXER_H_ diff --git a/src/tree_sitter/lib.c b/src/tree_sitter/lib.c new file mode 100644 index 0000000000..fc5fbc9210 --- /dev/null +++ b/src/tree_sitter/lib.c @@ -0,0 +1,20 @@ +// The Tree-sitter library can be built by compiling this one source file. +// +// The following directories must be added to the include path: +// - include +// - utf8proc + +#define _POSIX_C_SOURCE 200112L +#define UTF8PROC_STATIC + +#include "./get_changed_ranges.c" +#include "./language.c" +#include "./lexer.c" +#include "./node.c" +#include "./parser.c" +#include "./stack.c" +#include "./subtree.c" +#include "./tree_cursor.c" +#include "./tree.c" +#include "./utf16.c" +#include "utf8proc.c" diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c new file mode 100644 index 0000000000..6b2be36ee5 --- /dev/null +++ b/src/tree_sitter/node.c @@ -0,0 +1,673 @@ +#include +#include "./subtree.h" +#include "./tree.h" +#include "./language.h" + +typedef struct { + Subtree parent; + const TSTree *tree; + Length position; + uint32_t child_index; + uint32_t structural_child_index; + const TSSymbol *alias_sequence; +} NodeChildIterator; + +// TSNode - constructors + +TSNode ts_node_new( + const TSTree *tree, + const Subtree *subtree, + Length position, + TSSymbol alias +) { + return (TSNode) { + {position.bytes, position.extent.row, position.extent.column, alias}, + subtree, + tree, + }; +} + +static inline TSNode ts_node__null(void) { + return ts_node_new(NULL, NULL, length_zero(), 0); +} + +// TSNode - accessors + +uint32_t ts_node_start_byte(TSNode self) { + return self.context[0]; +} + +TSPoint ts_node_start_point(TSNode self) { + return (TSPoint) {self.context[1], self.context[2]}; +} + +static inline uint32_t ts_node__alias(const TSNode *self) { + return self->context[3]; +} + +static inline Subtree ts_node__subtree(TSNode self) { + return *(const Subtree *)self.id; +} + +// NodeChildIterator + +static inline NodeChildIterator ts_node_iterate_children(const TSNode *node) { + Subtree subtree = ts_node__subtree(*node); + if (ts_subtree_child_count(subtree) == 0) { + return (NodeChildIterator) {NULL_SUBTREE, node->tree, length_zero(), 0, 0, NULL}; + } + const TSSymbol *alias_sequence = ts_language_alias_sequence( + node->tree->language, + subtree.ptr->production_id + ); + return (NodeChildIterator) { + .tree = node->tree, + .parent = subtree, + .position = {ts_node_start_byte(*node), ts_node_start_point(*node)}, + .child_index = 0, + .structural_child_index = 0, + .alias_sequence = alias_sequence, + }; +} + +static inline bool ts_node_child_iterator_done(NodeChildIterator *self) { + return self->child_index == self->parent.ptr->child_count; +} + +static inline bool ts_node_child_iterator_next( + NodeChildIterator *self, + TSNode *result +) { + if (!self->parent.ptr || ts_node_child_iterator_done(self)) return false; + const Subtree *child = &self->parent.ptr->children[self->child_index]; + TSSymbol alias_symbol = 0; + if (!ts_subtree_extra(*child)) { + if (self->alias_sequence) { + alias_symbol = self->alias_sequence[self->structural_child_index]; + } + self->structural_child_index++; + } + if (self->child_index > 0) { + self->position = length_add(self->position, ts_subtree_padding(*child)); + } + *result = ts_node_new( + self->tree, + child, + self->position, + alias_symbol + ); + self->position = length_add(self->position, ts_subtree_size(*child)); + self->child_index++; + return true; +} + +// TSNode - private + +static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) { + Subtree tree = ts_node__subtree(self); + if (include_anonymous) { + return ts_subtree_visible(tree) || ts_node__alias(&self); + } else { + TSSymbol alias = ts_node__alias(&self); + if (alias) { + return ts_language_symbol_metadata(self.tree->language, alias).named; + } else { + return ts_subtree_visible(tree) && ts_subtree_named(tree); + } + } +} + +static inline uint32_t ts_node__relevant_child_count( + TSNode self, + bool include_anonymous +) { + Subtree tree = ts_node__subtree(self); + if (ts_subtree_child_count(tree) > 0) { + if (include_anonymous) { + return tree.ptr->visible_child_count; + } else { + return tree.ptr->named_child_count; + } + } else { + return 0; + } +} + +static inline TSNode ts_node__child( + TSNode self, + uint32_t child_index, + bool include_anonymous +) { + TSNode result = self; + bool did_descend = true; + + while (did_descend) { + did_descend = false; + + TSNode child; + uint32_t index = 0; + NodeChildIterator iterator = ts_node_iterate_children(&result); + while (ts_node_child_iterator_next(&iterator, &child)) { + if (ts_node__is_relevant(child, include_anonymous)) { + if (index == child_index) { + ts_tree_set_cached_parent(self.tree, &child, &self); + return child; + } + index++; + } else { + uint32_t grandchild_index = child_index - index; + uint32_t grandchild_count = ts_node__relevant_child_count(child, include_anonymous); + if (grandchild_index < grandchild_count) { + did_descend = true; + result = child; + child_index = grandchild_index; + break; + } + index += grandchild_count; + } + } + } + + return ts_node__null(); +} + +static bool ts_subtree_has_trailing_empty_descendant( + Subtree self, + Subtree other +) { + for (unsigned i = ts_subtree_child_count(self) - 1; i + 1 > 0; i--) { + Subtree child = self.ptr->children[i]; + if (ts_subtree_total_bytes(child) > 0) break; + if (child.ptr == other.ptr || ts_subtree_has_trailing_empty_descendant(child, other)) { + return true; + } + } + return false; +} + +static inline TSNode ts_node__prev_sibling(TSNode self, bool include_anonymous) { + Subtree self_subtree = ts_node__subtree(self); + bool self_is_empty = ts_subtree_total_bytes(self_subtree) == 0; + uint32_t target_end_byte = ts_node_end_byte(self); + + TSNode node = ts_node_parent(self); + TSNode earlier_node = ts_node__null(); + bool earlier_node_is_relevant = false; + + while (!ts_node_is_null(node)) { + TSNode earlier_child = ts_node__null(); + bool earlier_child_is_relevant = false; + bool found_child_containing_target = false; + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&node); + while (ts_node_child_iterator_next(&iterator, &child)) { + if (child.id == self.id) break; + if (iterator.position.bytes > target_end_byte) { + found_child_containing_target = true; + break; + } + + if (iterator.position.bytes == target_end_byte && + (!self_is_empty || + ts_subtree_has_trailing_empty_descendant(ts_node__subtree(child), self_subtree))) { + found_child_containing_target = true; + break; + } + + if (ts_node__is_relevant(child, include_anonymous)) { + earlier_child = child; + earlier_child_is_relevant = true; + } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) { + earlier_child = child; + earlier_child_is_relevant = false; + } + } + + if (found_child_containing_target) { + if (!ts_node_is_null(earlier_child)) { + earlier_node = earlier_child; + earlier_node_is_relevant = earlier_child_is_relevant; + } + node = child; + } else if (earlier_child_is_relevant) { + return earlier_child; + } else if (!ts_node_is_null(earlier_child)) { + node = earlier_child; + } else if (earlier_node_is_relevant) { + return earlier_node; + } else { + node = earlier_node; + } + } + + return ts_node__null(); +} + +static inline TSNode ts_node__next_sibling(TSNode self, bool include_anonymous) { + uint32_t target_end_byte = ts_node_end_byte(self); + + TSNode node = ts_node_parent(self); + TSNode later_node = ts_node__null(); + bool later_node_is_relevant = false; + + while (!ts_node_is_null(node)) { + TSNode later_child = ts_node__null(); + bool later_child_is_relevant = false; + TSNode child_containing_target = ts_node__null(); + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&node); + while (ts_node_child_iterator_next(&iterator, &child)) { + if (iterator.position.bytes < target_end_byte) continue; + if (ts_node_start_byte(child) <= ts_node_start_byte(self)) { + if (ts_node__subtree(child).ptr != ts_node__subtree(self).ptr) { + child_containing_target = child; + } + } else if (ts_node__is_relevant(child, include_anonymous)) { + later_child = child; + later_child_is_relevant = true; + break; + } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) { + later_child = child; + later_child_is_relevant = false; + break; + } + } + + if (!ts_node_is_null(child_containing_target)) { + if (!ts_node_is_null(later_child)) { + later_node = later_child; + later_node_is_relevant = later_child_is_relevant; + } + node = child_containing_target; + } else if (later_child_is_relevant) { + return later_child; + } else if (!ts_node_is_null(later_child)) { + node = later_child; + } else if (later_node_is_relevant) { + return later_node; + } else { + node = later_node; + } + } + + return ts_node__null(); +} + +static inline TSNode ts_node__first_child_for_byte( + TSNode self, + uint32_t goal, + bool include_anonymous +) { + TSNode node = self; + bool did_descend = true; + + while (did_descend) { + did_descend = false; + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&node); + while (ts_node_child_iterator_next(&iterator, &child)) { + if (ts_node_end_byte(child) > goal) { + if (ts_node__is_relevant(child, include_anonymous)) { + return child; + } else if (ts_node_child_count(child) > 0) { + did_descend = true; + node = child; + break; + } + } + } + } + + return ts_node__null(); +} + +static inline TSNode ts_node__descendant_for_byte_range( + TSNode self, + uint32_t range_start, + uint32_t range_end, + bool include_anonymous +) { + TSNode node = self; + TSNode last_visible_node = self; + + bool did_descend = true; + while (did_descend) { + did_descend = false; + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&node); + while (ts_node_child_iterator_next(&iterator, &child)) { + uint32_t node_end = iterator.position.bytes; + + // The end of this node must extend far enough forward to touch + // the end of the range and exceed the start of the range. + if (node_end < range_end) continue; + if (node_end <= range_start) continue; + + // The start of this node must extend far enough backward to + // touch the start of the range. + if (range_start < ts_node_start_byte(child)) break; + + node = child; + if (ts_node__is_relevant(node, include_anonymous)) { + ts_tree_set_cached_parent(self.tree, &child, &last_visible_node); + last_visible_node = node; + } + did_descend = true; + break; + } + } + + return last_visible_node; +} + +static inline TSNode ts_node__descendant_for_point_range( + TSNode self, + TSPoint range_start, + TSPoint range_end, + bool include_anonymous +) { + TSNode node = self; + TSNode last_visible_node = self; + + bool did_descend = true; + while (did_descend) { + did_descend = false; + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&node); + while (ts_node_child_iterator_next(&iterator, &child)) { + TSPoint node_end = iterator.position.extent; + + // The end of this node must extend far enough forward to touch + // the end of the range and exceed the start of the range. + if (point_lt(node_end, range_end)) continue; + if (point_lte(node_end, range_start)) continue; + + // The start of this node must extend far enough backward to + // touch the start of the range. + if (point_lt(range_start, ts_node_start_point(child))) break; + + node = child; + if (ts_node__is_relevant(node, include_anonymous)) { + ts_tree_set_cached_parent(self.tree, &child, &last_visible_node); + last_visible_node = node; + } + did_descend = true; + break; + } + } + + return last_visible_node; +} + +// TSNode - public + +uint32_t ts_node_end_byte(TSNode self) { + return ts_node_start_byte(self) + ts_subtree_size(ts_node__subtree(self)).bytes; +} + +TSPoint ts_node_end_point(TSNode self) { + return point_add(ts_node_start_point(self), ts_subtree_size(ts_node__subtree(self)).extent); +} + +TSSymbol ts_node_symbol(TSNode self) { + return ts_node__alias(&self) + ? ts_node__alias(&self) + : ts_subtree_symbol(ts_node__subtree(self)); +} + +const char *ts_node_type(TSNode self) { + return ts_language_symbol_name(self.tree->language, ts_node_symbol(self)); +} + +char *ts_node_string(TSNode self) { + return ts_subtree_string(ts_node__subtree(self), self.tree->language, false); +} + +bool ts_node_eq(TSNode self, TSNode other) { + return self.tree == other.tree && self.id == other.id; +} + +bool ts_node_is_null(TSNode self) { + return self.id == 0; +} + +bool ts_node_is_extra(TSNode self) { + return ts_subtree_extra(ts_node__subtree(self)); +} + +bool ts_node_is_named(TSNode self) { + TSSymbol alias = ts_node__alias(&self); + return alias + ? ts_language_symbol_metadata(self.tree->language, alias).named + : ts_subtree_named(ts_node__subtree(self)); +} + +bool ts_node_is_missing(TSNode self) { + return ts_subtree_missing(ts_node__subtree(self)); +} + +bool ts_node_has_changes(TSNode self) { + return ts_subtree_has_changes(ts_node__subtree(self)); +} + +bool ts_node_has_error(TSNode self) { + return ts_subtree_error_cost(ts_node__subtree(self)) > 0; +} + +TSNode ts_node_parent(TSNode self) { + TSNode node = ts_tree_get_cached_parent(self.tree, &self); + if (node.id) return node; + + node = ts_tree_root_node(self.tree); + uint32_t end_byte = ts_node_end_byte(self); + if (node.id == self.id) return ts_node__null(); + + TSNode last_visible_node = node; + bool did_descend = true; + while (did_descend) { + did_descend = false; + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&node); + while (ts_node_child_iterator_next(&iterator, &child)) { + if ( + ts_node_start_byte(child) > ts_node_start_byte(self) || + child.id == self.id + ) break; + if (iterator.position.bytes >= end_byte) { + node = child; + if (ts_node__is_relevant(child, true)) { + ts_tree_set_cached_parent(self.tree, &node, &last_visible_node); + last_visible_node = node; + } + did_descend = true; + break; + } + } + } + + return last_visible_node; +} + +TSNode ts_node_child(TSNode self, uint32_t child_index) { + return ts_node__child(self, child_index, true); +} + +TSNode ts_node_named_child(TSNode self, uint32_t child_index) { + return ts_node__child(self, child_index, false); +} + +TSNode ts_node_child_by_field_id(TSNode self, TSFieldId field_id) { +recur: + if (!field_id || ts_node_child_count(self) == 0) return ts_node__null(); + + const TSFieldMapEntry *field_map, *field_map_end; + ts_language_field_map( + self.tree->language, + ts_node__subtree(self).ptr->production_id, + &field_map, + &field_map_end + ); + if (field_map == field_map_end) return ts_node__null(); + + // The field mappings are sorted by their field id. Scan all + // the mappings to find the ones for the given field id. + while (field_map->field_id < field_id) { + field_map++; + if (field_map == field_map_end) return ts_node__null(); + } + while (field_map_end[-1].field_id > field_id) { + field_map_end--; + if (field_map == field_map_end) return ts_node__null(); + } + + TSNode child; + NodeChildIterator iterator = ts_node_iterate_children(&self); + while (ts_node_child_iterator_next(&iterator, &child)) { + if (!ts_subtree_extra(ts_node__subtree(child))) { + uint32_t index = iterator.structural_child_index - 1; + if (index < field_map->child_index) continue; + + // Hidden nodes' fields are "inherited" by their visible parent. + if (field_map->inherited) { + + // If this is the *last* possible child node for this field, + // then perform a tail call to avoid recursion. + if (field_map + 1 == field_map_end) { + self = child; + goto recur; + } + + // Otherwise, descend into this child, but if it doesn't contain + // the field, continue searching subsequent children. + else { + TSNode result = ts_node_child_by_field_id(child, field_id); + if (result.id) return result; + field_map++; + if (field_map == field_map_end) return ts_node__null(); + } + } + + else if (ts_node__is_relevant(child, true)) { + return child; + } + + // If the field refers to a hidden node, return its first visible + // child. + else { + return ts_node_child(child, 0); + } + } + } + + return ts_node__null(); +} + +TSNode ts_node_child_by_field_name( + TSNode self, + const char *name, + uint32_t name_length +) { + TSFieldId field_id = ts_language_field_id_for_name( + self.tree->language, + name, + name_length + ); + return ts_node_child_by_field_id(self, field_id); +} + +uint32_t ts_node_child_count(TSNode self) { + Subtree tree = ts_node__subtree(self); + if (ts_subtree_child_count(tree) > 0) { + return tree.ptr->visible_child_count; + } else { + return 0; + } +} + +uint32_t ts_node_named_child_count(TSNode self) { + Subtree tree = ts_node__subtree(self); + if (ts_subtree_child_count(tree) > 0) { + return tree.ptr->named_child_count; + } else { + return 0; + } +} + +TSNode ts_node_next_sibling(TSNode self) { + return ts_node__next_sibling(self, true); +} + +TSNode ts_node_next_named_sibling(TSNode self) { + return ts_node__next_sibling(self, false); +} + +TSNode ts_node_prev_sibling(TSNode self) { + return ts_node__prev_sibling(self, true); +} + +TSNode ts_node_prev_named_sibling(TSNode self) { + return ts_node__prev_sibling(self, false); +} + +TSNode ts_node_first_child_for_byte(TSNode self, uint32_t byte) { + return ts_node__first_child_for_byte(self, byte, true); +} + +TSNode ts_node_first_named_child_for_byte(TSNode self, uint32_t byte) { + return ts_node__first_child_for_byte(self, byte, false); +} + +TSNode ts_node_descendant_for_byte_range( + TSNode self, + uint32_t start, + uint32_t end +) { + return ts_node__descendant_for_byte_range(self, start, end, true); +} + +TSNode ts_node_named_descendant_for_byte_range( + TSNode self, + uint32_t start, + uint32_t end +) { + return ts_node__descendant_for_byte_range(self, start, end, false); +} + +TSNode ts_node_descendant_for_point_range( + TSNode self, + TSPoint start, + TSPoint end +) { + return ts_node__descendant_for_point_range(self, start, end, true); +} + +TSNode ts_node_named_descendant_for_point_range( + TSNode self, + TSPoint start, + TSPoint end +) { + return ts_node__descendant_for_point_range(self, start, end, false); +} + +void ts_node_edit(TSNode *self, const TSInputEdit *edit) { + uint32_t start_byte = ts_node_start_byte(*self); + TSPoint start_point = ts_node_start_point(*self); + + if (start_byte >= edit->old_end_byte) { + start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte); + start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point)); + } else if (start_byte > edit->start_byte) { + start_byte = edit->new_end_byte; + start_point = edit->new_end_point; + } + + self->context[0] = start_byte; + self->context[1] = start_point.row; + self->context[2] = start_point.column; +} diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c new file mode 100644 index 0000000000..88b20845fd --- /dev/null +++ b/src/tree_sitter/parser.c @@ -0,0 +1,1887 @@ +#include +#include +#include +#include +#include +#include "tree_sitter/api.h" +#include "./alloc.h" +#include "./array.h" +#include "./atomic.h" +#include "./clock.h" +#include "./error_costs.h" +#include "./get_changed_ranges.h" +#include "./language.h" +#include "./length.h" +#include "./lexer.h" +#include "./reduce_action.h" +#include "./reusable_node.h" +#include "./stack.h" +#include "./subtree.h" +#include "./tree.h" + +#define LOG(...) \ + if (self->lexer.logger.log || self->dot_graph_file) { \ + snprintf(self->lexer.debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \ + ts_parser__log(self); \ + } + +#define LOG_STACK() \ + if (self->dot_graph_file) { \ + ts_stack_print_dot_graph(self->stack, self->language, self->dot_graph_file); \ + fputs("\n\n", self->dot_graph_file); \ + } + +#define LOG_TREE(tree) \ + if (self->dot_graph_file) { \ + ts_subtree_print_dot_graph(tree, self->language, self->dot_graph_file); \ + fputs("\n", self->dot_graph_file); \ + } + +#define SYM_NAME(symbol) ts_language_symbol_name(self->language, symbol) + +#define TREE_NAME(tree) SYM_NAME(ts_subtree_symbol(tree)) + +static const unsigned MAX_VERSION_COUNT = 6; +static const unsigned MAX_VERSION_COUNT_OVERFLOW = 4; +static const unsigned MAX_SUMMARY_DEPTH = 16; +static const unsigned MAX_COST_DIFFERENCE = 16 * ERROR_COST_PER_SKIPPED_TREE; +static const unsigned OP_COUNT_PER_TIMEOUT_CHECK = 100; + +typedef struct { + Subtree token; + Subtree last_external_token; + uint32_t byte_index; +} TokenCache; + +struct TSParser { + Lexer lexer; + Stack *stack; + SubtreePool tree_pool; + const TSLanguage *language; + ReduceActionSet reduce_actions; + Subtree finished_tree; + SubtreeHeapData scratch_tree_data; + MutableSubtree scratch_tree; + TokenCache token_cache; + ReusableNode reusable_node; + void *external_scanner_payload; + FILE *dot_graph_file; + TSClock end_clock; + TSDuration timeout_duration; + unsigned accept_count; + unsigned operation_count; + const volatile size_t *cancellation_flag; + bool halt_on_error; + Subtree old_tree; + TSRangeArray included_range_differences; + unsigned included_range_difference_index; +}; + +typedef struct { + unsigned cost; + unsigned node_count; + int dynamic_precedence; + bool is_in_error; +} ErrorStatus; + +typedef enum { + ErrorComparisonTakeLeft, + ErrorComparisonPreferLeft, + ErrorComparisonNone, + ErrorComparisonPreferRight, + ErrorComparisonTakeRight, +} ErrorComparison; + +typedef struct { + const char *string; + uint32_t length; +} TSStringInput; + +// StringInput + +static const char *ts_string_input_read( + void *_self, + uint32_t byte, + TSPoint _, + uint32_t *length +) { + TSStringInput *self = (TSStringInput *)_self; + if (byte >= self->length) { + *length = 0; + return ""; + } else { + *length = self->length - byte; + return self->string + byte; + } +} + +// Parser - Private + +static void ts_parser__log(TSParser *self) { + if (self->lexer.logger.log) { + self->lexer.logger.log( + self->lexer.logger.payload, + TSLogTypeParse, + self->lexer.debug_buffer + ); + } + + if (self->dot_graph_file) { + fprintf(self->dot_graph_file, "graph {\nlabel=\""); + for (char *c = &self->lexer.debug_buffer[0]; *c != 0; c++) { + if (*c == '"') fputc('\\', self->dot_graph_file); + fputc(*c, self->dot_graph_file); + } + fprintf(self->dot_graph_file, "\"\n}\n\n"); + } +} + +static bool ts_parser__breakdown_top_of_stack( + TSParser *self, + StackVersion version +) { + bool did_break_down = false; + bool pending = false; + + do { + StackSliceArray pop = ts_stack_pop_pending(self->stack, version); + if (!pop.size) break; + + did_break_down = true; + pending = false; + for (uint32_t i = 0; i < pop.size; i++) { + StackSlice slice = pop.contents[i]; + TSStateId state = ts_stack_state(self->stack, slice.version); + Subtree parent = *array_front(&slice.subtrees); + + for (uint32_t j = 0, n = ts_subtree_child_count(parent); j < n; j++) { + Subtree child = parent.ptr->children[j]; + pending = ts_subtree_child_count(child) > 0; + + if (ts_subtree_is_error(child)) { + state = ERROR_STATE; + } else if (!ts_subtree_extra(child)) { + state = ts_language_next_state(self->language, state, ts_subtree_symbol(child)); + } + + ts_subtree_retain(child); + ts_stack_push(self->stack, slice.version, child, pending, state); + } + + for (uint32_t j = 1; j < slice.subtrees.size; j++) { + Subtree tree = slice.subtrees.contents[j]; + ts_stack_push(self->stack, slice.version, tree, false, state); + } + + ts_subtree_release(&self->tree_pool, parent); + array_delete(&slice.subtrees); + + LOG("breakdown_top_of_stack tree:%s", TREE_NAME(parent)); + LOG_STACK(); + } + } while (pending); + + return did_break_down; +} + +static void ts_parser__breakdown_lookahead( + TSParser *self, + Subtree *lookahead, + TSStateId state, + ReusableNode *reusable_node +) { + bool did_descend = false; + Subtree tree = reusable_node_tree(reusable_node); + while (ts_subtree_child_count(tree) > 0 && ts_subtree_parse_state(tree) != state) { + LOG("state_mismatch sym:%s", TREE_NAME(tree)); + reusable_node_descend(reusable_node); + tree = reusable_node_tree(reusable_node); + did_descend = true; + } + + if (did_descend) { + ts_subtree_release(&self->tree_pool, *lookahead); + *lookahead = tree; + ts_subtree_retain(*lookahead); + } +} + +static ErrorComparison ts_parser__compare_versions( + TSParser *self, + ErrorStatus a, + ErrorStatus b +) { + if (!a.is_in_error && b.is_in_error) { + if (a.cost < b.cost) { + return ErrorComparisonTakeLeft; + } else { + return ErrorComparisonPreferLeft; + } + } + + if (a.is_in_error && !b.is_in_error) { + if (b.cost < a.cost) { + return ErrorComparisonTakeRight; + } else { + return ErrorComparisonPreferRight; + } + } + + if (a.cost < b.cost) { + if ((b.cost - a.cost) * (1 + a.node_count) > MAX_COST_DIFFERENCE) { + return ErrorComparisonTakeLeft; + } else { + return ErrorComparisonPreferLeft; + } + } + + if (b.cost < a.cost) { + if ((a.cost - b.cost) * (1 + b.node_count) > MAX_COST_DIFFERENCE) { + return ErrorComparisonTakeRight; + } else { + return ErrorComparisonPreferRight; + } + } + + if (a.dynamic_precedence > b.dynamic_precedence) return ErrorComparisonPreferLeft; + if (b.dynamic_precedence > a.dynamic_precedence) return ErrorComparisonPreferRight; + return ErrorComparisonNone; +} + +static ErrorStatus ts_parser__version_status( + TSParser *self, + StackVersion version +) { + unsigned cost = ts_stack_error_cost(self->stack, version); + bool is_paused = ts_stack_is_paused(self->stack, version); + if (is_paused) cost += ERROR_COST_PER_SKIPPED_TREE; + return (ErrorStatus) { + .cost = cost, + .node_count = ts_stack_node_count_since_error(self->stack, version), + .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version), + .is_in_error = is_paused || ts_stack_state(self->stack, version) == ERROR_STATE + }; +} + +static bool ts_parser__better_version_exists( + TSParser *self, + StackVersion version, + bool is_in_error, + unsigned cost +) { + if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) <= cost) { + return true; + } + + Length position = ts_stack_position(self->stack, version); + ErrorStatus status = { + .cost = cost, + .is_in_error = is_in_error, + .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version), + .node_count = ts_stack_node_count_since_error(self->stack, version), + }; + + for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) { + if (i == version || + !ts_stack_is_active(self->stack, i) || + ts_stack_position(self->stack, i).bytes < position.bytes) continue; + ErrorStatus status_i = ts_parser__version_status(self, i); + switch (ts_parser__compare_versions(self, status, status_i)) { + case ErrorComparisonTakeRight: + return true; + case ErrorComparisonPreferRight: + if (ts_stack_can_merge(self->stack, i, version)) return true; + default: + break; + } + } + + return false; +} + +static void ts_parser__restore_external_scanner( + TSParser *self, + Subtree external_token +) { + if (external_token.ptr) { + self->language->external_scanner.deserialize( + self->external_scanner_payload, + ts_external_scanner_state_data(&external_token.ptr->external_scanner_state), + external_token.ptr->external_scanner_state.length + ); + } else { + self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); + } +} + +static bool ts_parser__can_reuse_first_leaf( + TSParser *self, + TSStateId state, + Subtree tree, + TableEntry *table_entry +) { + TSLexMode current_lex_mode = self->language->lex_modes[state]; + TSSymbol leaf_symbol = ts_subtree_leaf_symbol(tree); + TSStateId leaf_state = ts_subtree_leaf_parse_state(tree); + TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state]; + + // If the token was created in a state with the same set of lookaheads, it is reusable. + if ( + table_entry->action_count > 0 && + memcmp(&leaf_lex_mode, ¤t_lex_mode, sizeof(TSLexMode)) == 0 && + ( + leaf_symbol != self->language->keyword_capture_token || + (!ts_subtree_is_keyword(tree) && ts_subtree_parse_state(tree) == state) + ) + ) return true; + + // Empty tokens are not reusable in states with different lookaheads. + if (ts_subtree_size(tree).bytes == 0 && leaf_symbol != ts_builtin_sym_end) return false; + + // If the current state allows external tokens or other tokens that conflict with this + // token, this token is not reusable. + return current_lex_mode.external_lex_state == 0 && table_entry->is_reusable; +} + +static Subtree ts_parser__lex( + TSParser *self, + StackVersion version, + TSStateId parse_state +) { + Length start_position = ts_stack_position(self->stack, version); + Subtree external_token = ts_stack_last_external_token(self->stack, version); + TSLexMode lex_mode = self->language->lex_modes[parse_state]; + const bool *valid_external_tokens = ts_language_enabled_external_tokens( + self->language, + lex_mode.external_lex_state + ); + + bool found_external_token = false; + bool error_mode = parse_state == ERROR_STATE; + bool skipped_error = false; + int32_t first_error_character = 0; + Length error_start_position = length_zero(); + Length error_end_position = length_zero(); + uint32_t lookahead_end_byte = 0; + ts_lexer_reset(&self->lexer, start_position); + + for (;;) { + Length current_position = self->lexer.current_position; + + if (valid_external_tokens) { + LOG( + "lex_external state:%d, row:%u, column:%u", + lex_mode.external_lex_state, + current_position.extent.row + 1, + current_position.extent.column + ); + ts_lexer_start(&self->lexer); + ts_parser__restore_external_scanner(self, external_token); + bool found_token = self->language->external_scanner.scan( + self->external_scanner_payload, + &self->lexer.data, + valid_external_tokens + ); + ts_lexer_finish(&self->lexer, &lookahead_end_byte); + + // Zero-length external tokens are generally allowed, but they're not + // allowed right after a syntax error. This is for two reasons: + // 1. After a syntax error, the lexer is looking for any possible token, + // as opposed to the specific set of tokens that are valid in some + // parse state. In this situation, it's very easy for an external + // scanner to produce unwanted zero-length tokens. + // 2. The parser sometimes inserts *missing* tokens to recover from + // errors. These tokens are also zero-length. If we allow more + // zero-length tokens to be created after missing tokens, it + // can lead to infinite loops. Forbidding zero-length tokens + // right at the point of error recovery is a conservative strategy + // for preventing this kind of infinite loop. + if (found_token && ( + self->lexer.token_end_position.bytes > current_position.bytes || + (!error_mode && ts_stack_has_advanced_since_error(self->stack, version)) + )) { + found_external_token = true; + break; + } + + ts_lexer_reset(&self->lexer, current_position); + } + + LOG( + "lex_internal state:%d, row:%u, column:%u", + lex_mode.lex_state, + current_position.extent.row + 1, + current_position.extent.column + ); + ts_lexer_start(&self->lexer); + bool found_token = self->language->lex_fn(&self->lexer.data, lex_mode.lex_state); + ts_lexer_finish(&self->lexer, &lookahead_end_byte); + if (found_token) break; + + if (!error_mode) { + error_mode = true; + lex_mode = self->language->lex_modes[ERROR_STATE]; + valid_external_tokens = ts_language_enabled_external_tokens( + self->language, + lex_mode.external_lex_state + ); + ts_lexer_reset(&self->lexer, start_position); + continue; + } + + if (!skipped_error) { + LOG("skip_unrecognized_character"); + skipped_error = true; + error_start_position = self->lexer.token_start_position; + error_end_position = self->lexer.token_start_position; + first_error_character = self->lexer.data.lookahead; + } + + if (self->lexer.current_position.bytes == error_end_position.bytes) { + if (self->lexer.data.lookahead == 0) { + self->lexer.data.result_symbol = ts_builtin_sym_error; + break; + } + self->lexer.data.advance(&self->lexer.data, false); + } + + error_end_position = self->lexer.current_position; + } + + Subtree result; + if (skipped_error) { + Length padding = length_sub(error_start_position, start_position); + Length size = length_sub(error_end_position, error_start_position); + uint32_t lookahead_bytes = lookahead_end_byte - error_end_position.bytes; + result = ts_subtree_new_error( + &self->tree_pool, + first_error_character, + padding, + size, + lookahead_bytes, + parse_state, + self->language + ); + + LOG( + "lexed_lookahead sym:%s, size:%u, character:'%c'", + SYM_NAME(ts_subtree_symbol(result)), + ts_subtree_total_size(result).bytes, + first_error_character + ); + } else { + if (self->lexer.token_end_position.bytes < self->lexer.token_start_position.bytes) { + self->lexer.token_start_position = self->lexer.token_end_position; + } + + bool is_keyword = false; + TSSymbol symbol = self->lexer.data.result_symbol; + Length padding = length_sub(self->lexer.token_start_position, start_position); + Length size = length_sub(self->lexer.token_end_position, self->lexer.token_start_position); + uint32_t lookahead_bytes = lookahead_end_byte - self->lexer.token_end_position.bytes; + + if (found_external_token) { + symbol = self->language->external_scanner.symbol_map[symbol]; + } else if (symbol == self->language->keyword_capture_token && symbol != 0) { + uint32_t end_byte = self->lexer.token_end_position.bytes; + ts_lexer_reset(&self->lexer, self->lexer.token_start_position); + ts_lexer_start(&self->lexer); + if ( + self->language->keyword_lex_fn(&self->lexer.data, 0) && + self->lexer.token_end_position.bytes == end_byte && + ts_language_has_actions(self->language, parse_state, self->lexer.data.result_symbol) + ) { + is_keyword = true; + symbol = self->lexer.data.result_symbol; + } + } + + result = ts_subtree_new_leaf( + &self->tree_pool, + symbol, + padding, + size, + lookahead_bytes, + parse_state, + found_external_token, + is_keyword, + self->language + ); + + if (found_external_token) { + unsigned length = self->language->external_scanner.serialize( + self->external_scanner_payload, + self->lexer.debug_buffer + ); + ts_external_scanner_state_init( + &((SubtreeHeapData *)result.ptr)->external_scanner_state, + self->lexer.debug_buffer, + length + ); + } + + LOG( + "lexed_lookahead sym:%s, size:%u", + SYM_NAME(ts_subtree_symbol(result)), + ts_subtree_total_size(result).bytes + ); + } + + return result; +} + +static Subtree ts_parser__get_cached_token( + TSParser *self, + TSStateId state, + size_t position, + Subtree last_external_token, + TableEntry *table_entry +) { + TokenCache *cache = &self->token_cache; + if ( + cache->token.ptr && cache->byte_index == position && + ts_subtree_external_scanner_state_eq(cache->last_external_token, last_external_token) + ) { + ts_language_table_entry(self->language, state, ts_subtree_symbol(cache->token), table_entry); + if (ts_parser__can_reuse_first_leaf(self, state, cache->token, table_entry)) { + ts_subtree_retain(cache->token); + return cache->token; + } + } + return NULL_SUBTREE; +} + +static void ts_parser__set_cached_token( + TSParser *self, + size_t byte_index, + Subtree last_external_token, + Subtree token +) { + TokenCache *cache = &self->token_cache; + if (token.ptr) ts_subtree_retain(token); + if (last_external_token.ptr) ts_subtree_retain(last_external_token); + if (cache->token.ptr) ts_subtree_release(&self->tree_pool, cache->token); + if (cache->last_external_token.ptr) ts_subtree_release(&self->tree_pool, cache->last_external_token); + cache->token = token; + cache->byte_index = byte_index; + cache->last_external_token = last_external_token; +} + +static bool ts_parser__has_included_range_difference( + const TSParser *self, + uint32_t start_position, + uint32_t end_position +) { + return ts_range_array_intersects( + &self->included_range_differences, + self->included_range_difference_index, + start_position, + end_position + ); +} + +static Subtree ts_parser__reuse_node( + TSParser *self, + StackVersion version, + TSStateId *state, + uint32_t position, + Subtree last_external_token, + TableEntry *table_entry +) { + Subtree result; + while ((result = reusable_node_tree(&self->reusable_node)).ptr) { + uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node); + uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result); + + if (byte_offset > position) { + LOG("before_reusable_node symbol:%s", TREE_NAME(result)); + break; + } + + if (byte_offset < position) { + LOG("past_reusable_node symbol:%s", TREE_NAME(result)); + if (end_byte_offset <= position || !reusable_node_descend(&self->reusable_node)) { + reusable_node_advance(&self->reusable_node); + } + continue; + } + + if (!ts_subtree_external_scanner_state_eq(self->reusable_node.last_external_token, last_external_token)) { + LOG("reusable_node_has_different_external_scanner_state symbol:%s", TREE_NAME(result)); + reusable_node_advance(&self->reusable_node); + continue; + } + + const char *reason = NULL; + if (ts_subtree_has_changes(result)) { + reason = "has_changes"; + } else if (ts_subtree_is_error(result)) { + reason = "is_error"; + } else if (ts_subtree_missing(result)) { + reason = "is_missing"; + } else if (ts_subtree_is_fragile(result)) { + reason = "is_fragile"; + } else if (ts_parser__has_included_range_difference(self, byte_offset, end_byte_offset)) { + reason = "contains_different_included_range"; + } + + if (reason) { + LOG("cant_reuse_node_%s tree:%s", reason, TREE_NAME(result)); + if (!reusable_node_descend(&self->reusable_node)) { + reusable_node_advance(&self->reusable_node); + ts_parser__breakdown_top_of_stack(self, version); + *state = ts_stack_state(self->stack, version); + } + continue; + } + + TSSymbol leaf_symbol = ts_subtree_leaf_symbol(result); + ts_language_table_entry(self->language, *state, leaf_symbol, table_entry); + if (!ts_parser__can_reuse_first_leaf(self, *state, result, table_entry)) { + LOG( + "cant_reuse_node symbol:%s, first_leaf_symbol:%s", + TREE_NAME(result), + SYM_NAME(leaf_symbol) + ); + reusable_node_advance_past_leaf(&self->reusable_node); + break; + } + + LOG("reuse_node symbol:%s", TREE_NAME(result)); + ts_subtree_retain(result); + return result; + } + + return NULL_SUBTREE; +} + +static bool ts_parser__select_tree(TSParser *self, Subtree left, Subtree right) { + if (!left.ptr) return true; + if (!right.ptr) return false; + + if (ts_subtree_error_cost(right) < ts_subtree_error_cost(left)) { + LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left)); + return true; + } + + if (ts_subtree_error_cost(left) < ts_subtree_error_cost(right)) { + LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); + return false; + } + + if (ts_subtree_dynamic_precedence(right) > ts_subtree_dynamic_precedence(left)) { + LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", + TREE_NAME(right), ts_subtree_dynamic_precedence(right), TREE_NAME(left), + ts_subtree_dynamic_precedence(left)); + return true; + } + + if (ts_subtree_dynamic_precedence(left) > ts_subtree_dynamic_precedence(right)) { + LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u", + TREE_NAME(left), ts_subtree_dynamic_precedence(left), TREE_NAME(right), + ts_subtree_dynamic_precedence(right)); + return false; + } + + if (ts_subtree_error_cost(left) > 0) return true; + + int comparison = ts_subtree_compare(left, right); + switch (comparison) { + case -1: + LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); + return false; + break; + case 1: + LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left)); + return true; + default: + LOG("select_existing symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right)); + return false; + } +} + +static void ts_parser__shift( + TSParser *self, + StackVersion version, + TSStateId state, + Subtree lookahead, + bool extra +) { + Subtree subtree_to_push; + if (extra != ts_subtree_extra(lookahead)) { + MutableSubtree result = ts_subtree_make_mut(&self->tree_pool, lookahead); + ts_subtree_set_extra(&result); + subtree_to_push = ts_subtree_from_mut(result); + } else { + subtree_to_push = lookahead; + } + + bool is_pending = ts_subtree_child_count(subtree_to_push) > 0; + ts_stack_push(self->stack, version, subtree_to_push, is_pending, state); + if (ts_subtree_has_external_tokens(subtree_to_push)) { + ts_stack_set_last_external_token( + self->stack, version, ts_subtree_last_external_token(subtree_to_push) + ); + } +} + +static bool ts_parser__replace_children( + TSParser *self, + MutableSubtree *tree, + SubtreeArray *children +) { + *self->scratch_tree.ptr = *tree->ptr; + self->scratch_tree.ptr->child_count = 0; + ts_subtree_set_children(self->scratch_tree, children->contents, children->size, self->language); + if (ts_parser__select_tree(self, ts_subtree_from_mut(*tree), ts_subtree_from_mut(self->scratch_tree))) { + *tree->ptr = *self->scratch_tree.ptr; + return true; + } else { + return false; + } +} + +static StackVersion ts_parser__reduce( + TSParser *self, + StackVersion version, + TSSymbol symbol, + uint32_t count, + int dynamic_precedence, + uint16_t production_id, + bool fragile +) { + uint32_t initial_version_count = ts_stack_version_count(self->stack); + uint32_t removed_version_count = 0; + StackSliceArray pop = ts_stack_pop_count(self->stack, version, count); + + for (uint32_t i = 0; i < pop.size; i++) { + StackSlice slice = pop.contents[i]; + StackVersion slice_version = slice.version - removed_version_count; + + // Error recovery can sometimes cause lots of stack versions to merge, + // such that a single pop operation can produce a lots of slices. + // Avoid creating too many stack versions in that situation. + if (i > 0 && slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) { + ts_stack_remove_version(self->stack, slice_version); + ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); + removed_version_count++; + while (i + 1 < pop.size) { + StackSlice next_slice = pop.contents[i + 1]; + if (next_slice.version != slice.version) break; + ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees); + i++; + } + continue; + } + + // Extra tokens on top of the stack should not be included in this new parent + // node. They will be re-pushed onto the stack after the parent node is + // created and pushed. + SubtreeArray children = slice.subtrees; + while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) { + children.size--; + } + + MutableSubtree parent = ts_subtree_new_node(&self->tree_pool, + symbol, &children, production_id, self->language + ); + + // This pop operation may have caused multiple stack versions to collapse + // into one, because they all diverged from a common state. In that case, + // choose one of the arrays of trees to be the parent node's children, and + // delete the rest of the tree arrays. + while (i + 1 < pop.size) { + StackSlice next_slice = pop.contents[i + 1]; + if (next_slice.version != slice.version) break; + i++; + + SubtreeArray children = next_slice.subtrees; + while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) { + children.size--; + } + + if (ts_parser__replace_children(self, &parent, &children)) { + ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); + slice = next_slice; + } else { + ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees); + } + } + + parent.ptr->dynamic_precedence += dynamic_precedence; + parent.ptr->production_id = production_id; + + TSStateId state = ts_stack_state(self->stack, slice_version); + TSStateId next_state = ts_language_next_state(self->language, state, symbol); + if (fragile || pop.size > 1 || initial_version_count > 1) { + parent.ptr->fragile_left = true; + parent.ptr->fragile_right = true; + parent.ptr->parse_state = TS_TREE_STATE_NONE; + } else { + parent.ptr->parse_state = state; + } + + // Push the parent node onto the stack, along with any extra tokens that + // were previously on top of the stack. + ts_stack_push(self->stack, slice_version, ts_subtree_from_mut(parent), false, next_state); + for (uint32_t j = parent.ptr->child_count; j < slice.subtrees.size; j++) { + ts_stack_push(self->stack, slice_version, slice.subtrees.contents[j], false, next_state); + } + + for (StackVersion j = 0; j < slice_version; j++) { + if (j == version) continue; + if (ts_stack_merge(self->stack, j, slice_version)) { + removed_version_count++; + break; + } + } + } + + // Return the first new stack version that was created. + return ts_stack_version_count(self->stack) > initial_version_count + ? initial_version_count + : STACK_VERSION_NONE; +} + +static void ts_parser__accept( + TSParser *self, + StackVersion version, + Subtree lookahead +) { + assert(ts_subtree_is_eof(lookahead)); + ts_stack_push(self->stack, version, lookahead, false, 1); + + StackSliceArray pop = ts_stack_pop_all(self->stack, version); + for (uint32_t i = 0; i < pop.size; i++) { + SubtreeArray trees = pop.contents[i].subtrees; + + Subtree root = NULL_SUBTREE; + for (uint32_t j = trees.size - 1; j + 1 > 0; j--) { + Subtree child = trees.contents[j]; + if (!ts_subtree_extra(child)) { + assert(!child.data.is_inline); + uint32_t child_count = ts_subtree_child_count(child); + for (uint32_t k = 0; k < child_count; k++) { + ts_subtree_retain(child.ptr->children[k]); + } + array_splice(&trees, j, 1, child_count, child.ptr->children); + root = ts_subtree_from_mut(ts_subtree_new_node( + &self->tree_pool, + ts_subtree_symbol(child), + &trees, + child.ptr->production_id, + self->language + )); + ts_subtree_release(&self->tree_pool, child); + break; + } + } + + assert(root.ptr); + self->accept_count++; + + if (self->finished_tree.ptr) { + if (ts_parser__select_tree(self, self->finished_tree, root)) { + ts_subtree_release(&self->tree_pool, self->finished_tree); + self->finished_tree = root; + } else { + ts_subtree_release(&self->tree_pool, root); + } + } else { + self->finished_tree = root; + } + } + + ts_stack_remove_version(self->stack, pop.contents[0].version); + ts_stack_halt(self->stack, version); +} + +static bool ts_parser__do_all_potential_reductions( + TSParser *self, + StackVersion starting_version, + TSSymbol lookahead_symbol +) { + uint32_t initial_version_count = ts_stack_version_count(self->stack); + + bool can_shift_lookahead_symbol = false; + StackVersion version = starting_version; + for (unsigned i = 0; true; i++) { + uint32_t version_count = ts_stack_version_count(self->stack); + if (version >= version_count) break; + + bool merged = false; + for (StackVersion i = initial_version_count; i < version; i++) { + if (ts_stack_merge(self->stack, i, version)) { + merged = true; + break; + } + } + if (merged) continue; + + TSStateId state = ts_stack_state(self->stack, version); + bool has_shift_action = false; + array_clear(&self->reduce_actions); + + TSSymbol first_symbol, end_symbol; + if (lookahead_symbol != 0) { + first_symbol = lookahead_symbol; + end_symbol = lookahead_symbol + 1; + } else { + first_symbol = 1; + end_symbol = self->language->token_count; + } + + for (TSSymbol symbol = first_symbol; symbol < end_symbol; symbol++) { + TableEntry entry; + ts_language_table_entry(self->language, state, symbol, &entry); + for (uint32_t i = 0; i < entry.action_count; i++) { + TSParseAction action = entry.actions[i]; + switch (action.type) { + case TSParseActionTypeShift: + case TSParseActionTypeRecover: + if (!action.params.extra && !action.params.repetition) has_shift_action = true; + break; + case TSParseActionTypeReduce: + if (action.params.child_count > 0) + ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){ + .symbol = action.params.symbol, + .count = action.params.child_count, + .dynamic_precedence = action.params.dynamic_precedence, + .production_id = action.params.production_id, + }); + default: + break; + } + } + } + + StackVersion reduction_version = STACK_VERSION_NONE; + for (uint32_t i = 0; i < self->reduce_actions.size; i++) { + ReduceAction action = self->reduce_actions.contents[i]; + + reduction_version = ts_parser__reduce( + self, version, action.symbol, action.count, + action.dynamic_precedence, action.production_id, + true + ); + } + + if (has_shift_action) { + can_shift_lookahead_symbol = true; + } else if (reduction_version != STACK_VERSION_NONE && i < MAX_VERSION_COUNT) { + ts_stack_renumber_version(self->stack, reduction_version, version); + continue; + } else if (lookahead_symbol != 0) { + ts_stack_remove_version(self->stack, version); + } + + if (version == starting_version) { + version = version_count; + } else { + version++; + } + } + + return can_shift_lookahead_symbol; +} + +static void ts_parser__handle_error( + TSParser *self, + StackVersion version, + TSSymbol lookahead_symbol +) { + uint32_t previous_version_count = ts_stack_version_count(self->stack); + + // Perform any reductions that can happen in this state, regardless of the lookahead. After + // skipping one or more invalid tokens, the parser might find a token that would have allowed + // a reduction to take place. + ts_parser__do_all_potential_reductions(self, version, 0); + uint32_t version_count = ts_stack_version_count(self->stack); + Length position = ts_stack_position(self->stack, version); + + // Push a discontinuity onto the stack. Merge all of the stack versions that + // were created in the previous step. + bool did_insert_missing_token = false; + for (StackVersion v = version; v < version_count;) { + if (!did_insert_missing_token) { + TSStateId state = ts_stack_state(self->stack, v); + for (TSSymbol missing_symbol = 1; + missing_symbol < self->language->token_count; + missing_symbol++) { + TSStateId state_after_missing_symbol = ts_language_next_state( + self->language, state, missing_symbol + ); + if (state_after_missing_symbol == 0) continue; + + if (ts_language_has_reduce_action( + self->language, + state_after_missing_symbol, + lookahead_symbol + )) { + // In case the parser is currently outside of any included range, the lexer will + // snap to the beginning of the next included range. The missing token's padding + // must be assigned to position it within the next included range. + ts_lexer_reset(&self->lexer, position); + ts_lexer_mark_end(&self->lexer); + Length padding = length_sub(self->lexer.token_end_position, position); + + StackVersion version_with_missing_tree = ts_stack_copy_version(self->stack, v); + Subtree missing_tree = ts_subtree_new_missing_leaf( + &self->tree_pool, missing_symbol, padding, self->language + ); + ts_stack_push( + self->stack, version_with_missing_tree, + missing_tree, false, + state_after_missing_symbol + ); + + if (ts_parser__do_all_potential_reductions( + self, version_with_missing_tree, + lookahead_symbol + )) { + LOG( + "recover_with_missing symbol:%s, state:%u", + SYM_NAME(missing_symbol), + ts_stack_state(self->stack, version_with_missing_tree) + ); + did_insert_missing_token = true; + break; + } + } + } + } + + ts_stack_push(self->stack, v, NULL_SUBTREE, false, ERROR_STATE); + v = (v == version) ? previous_version_count : v + 1; + } + + for (unsigned i = previous_version_count; i < version_count; i++) { + bool did_merge = ts_stack_merge(self->stack, version, previous_version_count); + assert(did_merge); + } + + ts_stack_record_summary(self->stack, version, MAX_SUMMARY_DEPTH); + LOG_STACK(); +} + +static void ts_parser__halt_parse(TSParser *self) { + LOG("halting_parse"); + LOG_STACK(); + + ts_lexer_advance_to_end(&self->lexer); + Length remaining_length = length_sub( + self->lexer.current_position, + ts_stack_position(self->stack, 0) + ); + + Subtree filler_node = ts_subtree_new_error( + &self->tree_pool, + 0, + length_zero(), + remaining_length, + remaining_length.bytes, + 0, + self->language + ); + ts_subtree_to_mut_unsafe(filler_node).ptr->visible = false; + ts_stack_push(self->stack, 0, filler_node, false, 0); + + SubtreeArray children = array_new(); + Subtree root_error = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language); + ts_stack_push(self->stack, 0, root_error, false, 0); + + Subtree eof = ts_subtree_new_leaf( + &self->tree_pool, + ts_builtin_sym_end, + length_zero(), + length_zero(), + 0, + 0, + false, + false, + self->language + ); + ts_parser__accept(self, 0, eof); +} + +static bool ts_parser__recover_to_state( + TSParser *self, + StackVersion version, + unsigned depth, + TSStateId goal_state +) { + StackSliceArray pop = ts_stack_pop_count(self->stack, version, depth); + StackVersion previous_version = STACK_VERSION_NONE; + + for (unsigned i = 0; i < pop.size; i++) { + StackSlice slice = pop.contents[i]; + + if (slice.version == previous_version) { + ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); + array_erase(&pop, i--); + continue; + } + + if (ts_stack_state(self->stack, slice.version) != goal_state) { + ts_stack_halt(self->stack, slice.version); + ts_subtree_array_delete(&self->tree_pool, &slice.subtrees); + array_erase(&pop, i--); + continue; + } + + SubtreeArray error_trees = ts_stack_pop_error(self->stack, slice.version); + if (error_trees.size > 0) { + assert(error_trees.size == 1); + Subtree error_tree = error_trees.contents[0]; + uint32_t error_child_count = ts_subtree_child_count(error_tree); + if (error_child_count > 0) { + array_splice(&slice.subtrees, 0, 0, error_child_count, error_tree.ptr->children); + for (unsigned j = 0; j < error_child_count; j++) { + ts_subtree_retain(slice.subtrees.contents[j]); + } + } + ts_subtree_array_delete(&self->tree_pool, &error_trees); + } + + SubtreeArray trailing_extras = ts_subtree_array_remove_trailing_extras(&slice.subtrees); + + if (slice.subtrees.size > 0) { + Subtree error = ts_subtree_new_error_node(&self->tree_pool, &slice.subtrees, true, self->language); + ts_stack_push(self->stack, slice.version, error, false, goal_state); + } else { + array_delete(&slice.subtrees); + } + + for (unsigned j = 0; j < trailing_extras.size; j++) { + Subtree tree = trailing_extras.contents[j]; + ts_stack_push(self->stack, slice.version, tree, false, goal_state); + } + + previous_version = slice.version; + array_delete(&trailing_extras); + } + + return previous_version != STACK_VERSION_NONE; +} + +static void ts_parser__recover( + TSParser *self, + StackVersion version, + Subtree lookahead +) { + bool did_recover = false; + unsigned previous_version_count = ts_stack_version_count(self->stack); + Length position = ts_stack_position(self->stack, version); + StackSummary *summary = ts_stack_get_summary(self->stack, version); + unsigned node_count_since_error = ts_stack_node_count_since_error(self->stack, version); + unsigned current_error_cost = ts_stack_error_cost(self->stack, version); + + // When the parser is in the error state, there are two strategies for recovering with a + // given lookahead token: + // 1. Find a previous state on the stack in which that lookahead token would be valid. Then, + // create a new stack version that is in that state again. This entails popping all of the + // subtrees that have been pushed onto the stack since that previous state, and wrapping + // them in an ERROR node. + // 2. Wrap the lookahead token in an ERROR node, push that ERROR node onto the stack, and + // move on to the next lookahead token, remaining in the error state. + // + // First, try the strategy 1. Upon entering the error state, the parser recorded a summary + // of the previous parse states and their depths. Look at each state in the summary, to see + // if the current lookahead token would be valid in that state. + if (summary && !ts_subtree_is_error(lookahead)) { + for (unsigned i = 0; i < summary->size; i++) { + StackSummaryEntry entry = summary->contents[i]; + + if (entry.state == ERROR_STATE) continue; + if (entry.position.bytes == position.bytes) continue; + unsigned depth = entry.depth; + if (node_count_since_error > 0) depth++; + + // Do not recover in ways that create redundant stack versions. + bool would_merge = false; + for (unsigned j = 0; j < previous_version_count; j++) { + if ( + ts_stack_state(self->stack, j) == entry.state && + ts_stack_position(self->stack, j).bytes == position.bytes + ) { + would_merge = true; + break; + } + } + if (would_merge) continue; + + // Do not recover if the result would clearly be worse than some existing stack version. + unsigned new_cost = + current_error_cost + + entry.depth * ERROR_COST_PER_SKIPPED_TREE + + (position.bytes - entry.position.bytes) * ERROR_COST_PER_SKIPPED_CHAR + + (position.extent.row - entry.position.extent.row) * ERROR_COST_PER_SKIPPED_LINE; + if (ts_parser__better_version_exists(self, version, false, new_cost)) break; + + // If the current lookahead token is valid in some previous state, recover to that state. + // Then stop looking for further recoveries. + if (ts_language_has_actions(self->language, entry.state, ts_subtree_symbol(lookahead))) { + if (ts_parser__recover_to_state(self, version, depth, entry.state)) { + did_recover = true; + LOG("recover_to_previous state:%u, depth:%u", entry.state, depth); + LOG_STACK(); + break; + } + } + } + } + + // In the process of attemping to recover, some stack versions may have been created + // and subsequently halted. Remove those versions. + for (unsigned i = previous_version_count; i < ts_stack_version_count(self->stack); i++) { + if (!ts_stack_is_active(self->stack, i)) { + ts_stack_remove_version(self->stack, i--); + } + } + + // If strategy 1 succeeded, a new stack version will have been created which is able to handle + // the current lookahead token. Now, in addition, try strategy 2 described above: skip the + // current lookahead token by wrapping it in an ERROR node. + + // Don't pursue this additional strategy if there are already too many stack versions. + if (did_recover && ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) { + ts_stack_halt(self->stack, version); + ts_subtree_release(&self->tree_pool, lookahead); + return; + } + + // If the parser is still in the error state at the end of the file, just wrap everything + // in an ERROR node and terminate. + if (ts_subtree_is_eof(lookahead)) { + LOG("recover_eof"); + SubtreeArray children = array_new(); + Subtree parent = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language); + ts_stack_push(self->stack, version, parent, false, 1); + ts_parser__accept(self, version, lookahead); + return; + } + + // Do not recover if the result would clearly be worse than some existing stack version. + unsigned new_cost = + current_error_cost + ERROR_COST_PER_SKIPPED_TREE + + ts_subtree_total_bytes(lookahead) * ERROR_COST_PER_SKIPPED_CHAR + + ts_subtree_total_size(lookahead).extent.row * ERROR_COST_PER_SKIPPED_LINE; + if (ts_parser__better_version_exists(self, version, false, new_cost)) { + ts_stack_halt(self->stack, version); + ts_subtree_release(&self->tree_pool, lookahead); + return; + } + + // If the current lookahead token is an extra token, mark it as extra. This means it won't + // be counted in error cost calculations. + unsigned n; + const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n); + if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.extra) { + MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); + ts_subtree_set_extra(&mutable_lookahead); + lookahead = ts_subtree_from_mut(mutable_lookahead); + } + + // Wrap the lookahead token in an ERROR. + LOG("skip_token symbol:%s", TREE_NAME(lookahead)); + SubtreeArray children = array_new(); + array_reserve(&children, 1); + array_push(&children, lookahead); + MutableSubtree error_repeat = ts_subtree_new_node( + &self->tree_pool, + ts_builtin_sym_error_repeat, + &children, + 0, + self->language + ); + + // If other tokens have already been skipped, so there is already an ERROR at the top of the + // stack, then pop that ERROR off the stack and wrap the two ERRORs together into one larger + // ERROR. + if (node_count_since_error > 0) { + StackSliceArray pop = ts_stack_pop_count(self->stack, version, 1); + + // TODO: Figure out how to make this condition occur. + // See https://github.com/atom/atom/issues/18450#issuecomment-439579778 + // If multiple stack versions have merged at this point, just pick one of the errors + // arbitrarily and discard the rest. + if (pop.size > 1) { + for (unsigned i = 1; i < pop.size; i++) { + ts_subtree_array_delete(&self->tree_pool, &pop.contents[i].subtrees); + } + while (ts_stack_version_count(self->stack) > pop.contents[0].version + 1) { + ts_stack_remove_version(self->stack, pop.contents[0].version + 1); + } + } + + ts_stack_renumber_version(self->stack, pop.contents[0].version, version); + array_push(&pop.contents[0].subtrees, ts_subtree_from_mut(error_repeat)); + error_repeat = ts_subtree_new_node( + &self->tree_pool, + ts_builtin_sym_error_repeat, + &pop.contents[0].subtrees, + 0, + self->language + ); + } + + // Push the new ERROR onto the stack. + ts_stack_push(self->stack, version, ts_subtree_from_mut(error_repeat), false, ERROR_STATE); + if (ts_subtree_has_external_tokens(lookahead)) { + ts_stack_set_last_external_token( + self->stack, version, ts_subtree_last_external_token(lookahead) + ); + } +} + +static bool ts_parser__advance( + TSParser *self, + StackVersion version, + bool allow_node_reuse +) { + TSStateId state = ts_stack_state(self->stack, version); + uint32_t position = ts_stack_position(self->stack, version).bytes; + Subtree last_external_token = ts_stack_last_external_token(self->stack, version); + + bool did_reuse = true; + Subtree lookahead = NULL_SUBTREE; + TableEntry table_entry = {.action_count = 0}; + + // If possible, reuse a node from the previous syntax tree. + if (allow_node_reuse) { + lookahead = ts_parser__reuse_node( + self, version, &state, position, last_external_token, &table_entry + ); + } + + // If no node from the previous syntax tree could be reused, then try to + // reuse the token previously returned by the lexer. + if (!lookahead.ptr) { + did_reuse = false; + lookahead = ts_parser__get_cached_token( + self, state, position, last_external_token, &table_entry + ); + } + + // Otherwise, re-run the lexer. + if (!lookahead.ptr) { + lookahead = ts_parser__lex(self, version, state); + ts_parser__set_cached_token(self, position, last_external_token, lookahead); + ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry); + } + + for (;;) { + // If a cancellation flag or a timeout was provided, then check every + // time a fixed number of parse actions has been processed. + if (++self->operation_count == OP_COUNT_PER_TIMEOUT_CHECK) { + self->operation_count = 0; + } + if ( + self->operation_count == 0 && + ((self->cancellation_flag && atomic_load(self->cancellation_flag)) || + (!clock_is_null(self->end_clock) && clock_is_gt(clock_now(), self->end_clock))) + ) { + ts_subtree_release(&self->tree_pool, lookahead); + return false; + } + + // Process each parse action for the current lookahead token in + // the current state. If there are multiple actions, then this is + // an ambiguous state. REDUCE actions always create a new stack + // version, whereas SHIFT actions update the existing stack version + // and terminate this loop. + StackVersion last_reduction_version = STACK_VERSION_NONE; + for (uint32_t i = 0; i < table_entry.action_count; i++) { + TSParseAction action = table_entry.actions[i]; + + switch (action.type) { + case TSParseActionTypeShift: { + if (action.params.repetition) break; + TSStateId next_state; + if (action.params.extra) { + + // TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out. + if (state == ERROR_STATE) continue; + + next_state = state; + LOG("shift_extra"); + } else { + next_state = action.params.state; + LOG("shift state:%u", next_state); + } + + if (ts_subtree_child_count(lookahead) > 0) { + ts_parser__breakdown_lookahead(self, &lookahead, state, &self->reusable_node); + next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead)); + } + + ts_parser__shift(self, version, next_state, lookahead, action.params.extra); + if (did_reuse) reusable_node_advance(&self->reusable_node); + return true; + } + + case TSParseActionTypeReduce: { + bool is_fragile = table_entry.action_count > 1; + LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.symbol), action.params.child_count); + StackVersion reduction_version = ts_parser__reduce( + self, version, action.params.symbol, action.params.child_count, + action.params.dynamic_precedence, action.params.production_id, + is_fragile + ); + if (reduction_version != STACK_VERSION_NONE) { + last_reduction_version = reduction_version; + } + break; + } + + case TSParseActionTypeAccept: { + LOG("accept"); + ts_parser__accept(self, version, lookahead); + return true; + } + + case TSParseActionTypeRecover: { + if (ts_subtree_child_count(lookahead) > 0) { + ts_parser__breakdown_lookahead(self, &lookahead, ERROR_STATE, &self->reusable_node); + } + + ts_parser__recover(self, version, lookahead); + if (did_reuse) reusable_node_advance(&self->reusable_node); + return true; + } + } + } + + // If a reduction was performed, then replace the current stack version + // with one of the stack versions created by a reduction, and continue + // processing this version of the stack with the same lookahead symbol. + if (last_reduction_version != STACK_VERSION_NONE) { + ts_stack_renumber_version(self->stack, last_reduction_version, version); + LOG_STACK(); + state = ts_stack_state(self->stack, version); + ts_language_table_entry( + self->language, + state, + ts_subtree_leaf_symbol(lookahead), + &table_entry + ); + continue; + } + + // If there were no parse actions for the current lookahead token, then + // it is not valid in this state. If the current lookahead token is a + // keyword, then switch to treating it as the normal word token if that + // token is valid in this state. + if ( + ts_subtree_is_keyword(lookahead) && + ts_subtree_symbol(lookahead) != self->language->keyword_capture_token + ) { + ts_language_table_entry(self->language, state, self->language->keyword_capture_token, &table_entry); + if (table_entry.action_count > 0) { + LOG( + "switch from_keyword:%s, to_word_token:%s", + TREE_NAME(lookahead), + SYM_NAME(self->language->keyword_capture_token) + ); + + MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead); + ts_subtree_set_symbol(&mutable_lookahead, self->language->keyword_capture_token, self->language); + lookahead = ts_subtree_from_mut(mutable_lookahead); + continue; + } + } + + // If the current lookahead token is not valid and the parser is + // already in the error state, restart the error recovery process. + // TODO - can this be unified with the other `RECOVER` case above? + if (state == ERROR_STATE) { + ts_parser__recover(self, version, lookahead); + return true; + } + + // If the current lookahead token is not valid and the previous + // subtree on the stack was reused from an old tree, it isn't actually + // valid to reuse it. Remove it from the stack, and in its place, + // push each of its children. Then try again to process the current + // lookahead. + if (ts_parser__breakdown_top_of_stack(self, version)) { + continue; + } + + // At this point, the current lookahead token is definitely not valid + // for this parse stack version. Mark this version as paused and continue + // processing any other stack versions that might exist. If some other + // version advances successfully, then this version can simply be removed. + // But if all versions end up paused, then error recovery is needed. + LOG("detect_error"); + ts_stack_pause(self->stack, version, ts_subtree_leaf_symbol(lookahead)); + ts_subtree_release(&self->tree_pool, lookahead); + return true; + } +} + +static unsigned ts_parser__condense_stack(TSParser *self) { + bool made_changes = false; + unsigned min_error_cost = UINT_MAX; + for (StackVersion i = 0; i < ts_stack_version_count(self->stack); i++) { + // Prune any versions that have been marked for removal. + if (ts_stack_is_halted(self->stack, i)) { + ts_stack_remove_version(self->stack, i); + i--; + continue; + } + + // Keep track of the minimum error cost of any stack version so + // that it can be returned. + ErrorStatus status_i = ts_parser__version_status(self, i); + if (!status_i.is_in_error && status_i.cost < min_error_cost) { + min_error_cost = status_i.cost; + } + + // Examine each pair of stack versions, removing any versions that + // are clearly worse than another version. Ensure that the versions + // are ordered from most promising to least promising. + for (StackVersion j = 0; j < i; j++) { + ErrorStatus status_j = ts_parser__version_status(self, j); + + switch (ts_parser__compare_versions(self, status_j, status_i)) { + case ErrorComparisonTakeLeft: + made_changes = true; + ts_stack_remove_version(self->stack, i); + i--; + j = i; + break; + + case ErrorComparisonPreferLeft: + case ErrorComparisonNone: + if (ts_stack_merge(self->stack, j, i)) { + made_changes = true; + i--; + j = i; + } + break; + + case ErrorComparisonPreferRight: + made_changes = true; + if (ts_stack_merge(self->stack, j, i)) { + i--; + j = i; + } else { + ts_stack_swap_versions(self->stack, i, j); + } + break; + + case ErrorComparisonTakeRight: + made_changes = true; + ts_stack_remove_version(self->stack, j); + i--; + j--; + break; + } + } + } + + // Enfore a hard upper bound on the number of stack versions by + // discarding the least promising versions. + while (ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) { + ts_stack_remove_version(self->stack, MAX_VERSION_COUNT); + made_changes = true; + } + + // If the best-performing stack version is currently paused, or all + // versions are paused, then resume the best paused version and begin + // the error recovery process. Otherwise, remove the paused versions. + if (ts_stack_version_count(self->stack) > 0) { + bool has_unpaused_version = false; + for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) { + if (ts_stack_is_paused(self->stack, i)) { + if (!has_unpaused_version && self->accept_count < MAX_VERSION_COUNT) { + LOG("resume version:%u", i); + min_error_cost = ts_stack_error_cost(self->stack, i); + TSSymbol lookahead_symbol = ts_stack_resume(self->stack, i); + ts_parser__handle_error(self, i, lookahead_symbol); + has_unpaused_version = true; + } else { + ts_stack_remove_version(self->stack, i); + i--; + n--; + } + } else { + has_unpaused_version = true; + } + } + } + + if (made_changes) { + LOG("condense"); + LOG_STACK(); + } + + return min_error_cost; +} + +static bool ts_parser_has_outstanding_parse(TSParser *self) { + return ( + self->lexer.current_position.bytes > 0 || + ts_stack_state(self->stack, 0) != 1 + ); +} + +// Parser - Public + +TSParser *ts_parser_new(void) { + TSParser *self = ts_calloc(1, sizeof(TSParser)); + ts_lexer_init(&self->lexer); + array_init(&self->reduce_actions); + array_reserve(&self->reduce_actions, 4); + self->tree_pool = ts_subtree_pool_new(32); + self->stack = ts_stack_new(&self->tree_pool); + self->finished_tree = NULL_SUBTREE; + self->reusable_node = reusable_node_new(); + self->dot_graph_file = NULL; + self->halt_on_error = false; + self->cancellation_flag = NULL; + self->timeout_duration = 0; + self->end_clock = clock_null(); + self->operation_count = 0; + self->old_tree = NULL_SUBTREE; + self->scratch_tree.ptr = &self->scratch_tree_data; + self->included_range_differences = (TSRangeArray) array_new(); + self->included_range_difference_index = 0; + ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); + return self; +} + +void ts_parser_delete(TSParser *self) { + if (!self) return; + + ts_stack_delete(self->stack); + if (self->reduce_actions.contents) { + array_delete(&self->reduce_actions); + } + if (self->included_range_differences.contents) { + array_delete(&self->included_range_differences); + } + if (self->old_tree.ptr) { + ts_subtree_release(&self->tree_pool, self->old_tree); + self->old_tree = NULL_SUBTREE; + } + ts_lexer_delete(&self->lexer); + ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); + ts_subtree_pool_delete(&self->tree_pool); + reusable_node_delete(&self->reusable_node); + ts_parser_set_language(self, NULL); + ts_free(self); +} + +const TSLanguage *ts_parser_language(const TSParser *self) { + return self->language; +} + +bool ts_parser_set_language(TSParser *self, const TSLanguage *language) { + if (language) { + if (language->version > TREE_SITTER_LANGUAGE_VERSION) return false; + if (language->version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) return false; + } + + if (self->external_scanner_payload && self->language->external_scanner.destroy) { + self->language->external_scanner.destroy(self->external_scanner_payload); + } + + if (language && language->external_scanner.create) { + self->external_scanner_payload = language->external_scanner.create(); + } else { + self->external_scanner_payload = NULL; + } + + self->language = language; + return true; +} + +TSLogger ts_parser_logger(const TSParser *self) { + return self->lexer.logger; +} + +void ts_parser_set_logger(TSParser *self, TSLogger logger) { + self->lexer.logger = logger; +} + +void ts_parser_print_dot_graphs(TSParser *self, int fd) { + if (self->dot_graph_file) { + fclose(self->dot_graph_file); + } + + if (fd >= 0) { + self->dot_graph_file = fdopen(fd, "a"); + } else { + self->dot_graph_file = NULL; + } +} + +void ts_parser_halt_on_error(TSParser *self, bool should_halt_on_error) { + self->halt_on_error = should_halt_on_error; +} + +const size_t *ts_parser_cancellation_flag(const TSParser *self) { + return (const size_t *)self->cancellation_flag; +} + +void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag) { + self->cancellation_flag = (const volatile size_t *)flag; +} + +uint64_t ts_parser_timeout_micros(const TSParser *self) { + return duration_to_micros(self->timeout_duration); +} + +void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) { + self->timeout_duration = duration_from_micros(timeout_micros); +} + +void ts_parser_set_included_ranges(TSParser *self, const TSRange *ranges, uint32_t count) { + ts_lexer_set_included_ranges(&self->lexer, ranges, count); +} + +const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) { + return ts_lexer_included_ranges(&self->lexer, count); +} + +void ts_parser_reset(TSParser *self) { + if (self->language->external_scanner.deserialize) { + self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); + } + + if (self->old_tree.ptr) { + ts_subtree_release(&self->tree_pool, self->old_tree); + self->old_tree = NULL_SUBTREE; + } + + reusable_node_clear(&self->reusable_node); + ts_lexer_reset(&self->lexer, length_zero()); + ts_stack_clear(self->stack); + ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); + if (self->finished_tree.ptr) { + ts_subtree_release(&self->tree_pool, self->finished_tree); + self->finished_tree = NULL_SUBTREE; + } + self->accept_count = 0; +} + +TSTree *ts_parser_parse( + TSParser *self, + const TSTree *old_tree, + TSInput input +) { + if (!self->language || !input.read) return NULL; + + ts_lexer_set_input(&self->lexer, input); + + array_clear(&self->included_range_differences); + self->included_range_difference_index = 0; + + if (ts_parser_has_outstanding_parse(self)) { + LOG("resume_parsing"); + } else if (old_tree) { + ts_subtree_retain(old_tree->root); + self->old_tree = old_tree->root; + ts_range_array_get_changed_ranges( + old_tree->included_ranges, old_tree->included_range_count, + self->lexer.included_ranges, self->lexer.included_range_count, + &self->included_range_differences + ); + reusable_node_reset(&self->reusable_node, old_tree->root); + LOG("parse_after_edit"); + LOG_TREE(self->old_tree); + for (unsigned i = 0; i < self->included_range_differences.size; i++) { + TSRange *range = &self->included_range_differences.contents[i]; + LOG("different_included_range %u - %u", range->start_byte, range->end_byte); + } + } else { + reusable_node_clear(&self->reusable_node); + LOG("new_parse"); + } + + uint32_t position = 0, last_position = 0, version_count = 0; + self->operation_count = 0; + if (self->timeout_duration) { + self->end_clock = clock_after(clock_now(), self->timeout_duration); + } else { + self->end_clock = clock_null(); + } + + do { + for (StackVersion version = 0; + version_count = ts_stack_version_count(self->stack), version < version_count; + version++) { + bool allow_node_reuse = version_count == 1; + while (ts_stack_is_active(self->stack, version)) { + LOG("process version:%d, version_count:%u, state:%d, row:%u, col:%u", + version, ts_stack_version_count(self->stack), + ts_stack_state(self->stack, version), + ts_stack_position(self->stack, version).extent.row + 1, + ts_stack_position(self->stack, version).extent.column); + + if (!ts_parser__advance(self, version, allow_node_reuse)) return NULL; + LOG_STACK(); + + position = ts_stack_position(self->stack, version).bytes; + if (position > last_position || (version > 0 && position == last_position)) { + last_position = position; + break; + } + } + } + + unsigned min_error_cost = ts_parser__condense_stack(self); + if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) { + break; + } else if (self->halt_on_error && min_error_cost > 0) { + ts_parser__halt_parse(self); + break; + } + + while (self->included_range_difference_index < self->included_range_differences.size) { + TSRange *range = &self->included_range_differences.contents[self->included_range_difference_index]; + if (range->end_byte <= position) { + self->included_range_difference_index++; + } else { + break; + } + } + } while (version_count != 0); + + ts_subtree_balance(self->finished_tree, &self->tree_pool, self->language); + LOG("done"); + LOG_TREE(self->finished_tree); + + TSTree *result = ts_tree_new( + self->finished_tree, + self->language, + self->lexer.included_ranges, + self->lexer.included_range_count + ); + self->finished_tree = NULL_SUBTREE; + ts_parser_reset(self); + return result; +} + +TSTree *ts_parser_parse_string( + TSParser *self, + const TSTree *old_tree, + const char *string, + uint32_t length +) { + return ts_parser_parse_string_encoding(self, old_tree, string, length, TSInputEncodingUTF8); +} + +TSTree *ts_parser_parse_string_encoding(TSParser *self, const TSTree *old_tree, + const char *string, uint32_t length, TSInputEncoding encoding) { + TSStringInput input = {string, length}; + return ts_parser_parse(self, old_tree, (TSInput) { + &input, + ts_string_input_read, + encoding, + }); +} + +#undef LOG diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h new file mode 100644 index 0000000000..974a7ca52f --- /dev/null +++ b/src/tree_sitter/parser.h @@ -0,0 +1,220 @@ +#ifndef TREE_SITTER_PARSER_H_ +#define TREE_SITTER_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define ts_builtin_sym_error ((TSSymbol)-1) +#define ts_builtin_sym_end 0 +#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 + +#ifndef TREE_SITTER_API_H_ +typedef uint16_t TSSymbol; +typedef uint16_t TSFieldId; +typedef struct TSLanguage TSLanguage; +#endif + +typedef struct { + TSFieldId field_id; + uint8_t child_index; + bool inherited; +} TSFieldMapEntry; + +typedef struct { + uint16_t index; + uint16_t length; +} TSFieldMapSlice; + +typedef uint16_t TSStateId; + +typedef struct { + bool visible : 1; + bool named : 1; +} TSSymbolMetadata; + +typedef struct TSLexer TSLexer; + +struct TSLexer { + int32_t lookahead; + TSSymbol result_symbol; + void (*advance)(TSLexer *, bool); + void (*mark_end)(TSLexer *); + uint32_t (*get_column)(TSLexer *); + bool (*is_at_included_range_start)(TSLexer *); +}; + +typedef enum { + TSParseActionTypeShift, + TSParseActionTypeReduce, + TSParseActionTypeAccept, + TSParseActionTypeRecover, +} TSParseActionType; + +typedef struct { + union { + struct { + TSStateId state; + bool extra : 1; + bool repetition : 1; + }; + struct { + TSSymbol symbol; + int16_t dynamic_precedence; + uint8_t child_count; + uint8_t production_id; + }; + } params; + TSParseActionType type : 4; +} TSParseAction; + +typedef struct { + uint16_t lex_state; + uint16_t external_lex_state; +} TSLexMode; + +typedef union { + TSParseAction action; + struct { + uint8_t count; + bool reusable : 1; + }; +} TSParseActionEntry; + +struct TSLanguage { + uint32_t version; + uint32_t symbol_count; + uint32_t alias_count; + uint32_t token_count; + uint32_t external_token_count; + const char **symbol_names; + const TSSymbolMetadata *symbol_metadata; + const uint16_t *parse_table; + const TSParseActionEntry *parse_actions; + const TSLexMode *lex_modes; + const TSSymbol *alias_sequences; + uint16_t max_alias_sequence_length; + bool (*lex_fn)(TSLexer *, TSStateId); + bool (*keyword_lex_fn)(TSLexer *, TSStateId); + TSSymbol keyword_capture_token; + struct { + const bool *states; + const TSSymbol *symbol_map; + void *(*create)(void); + void (*destroy)(void *); + bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); + unsigned (*serialize)(void *, char *); + void (*deserialize)(void *, const char *, unsigned); + } external_scanner; + uint32_t field_count; + const TSFieldMapSlice *field_map_slices; + const TSFieldMapEntry *field_map_entries; + const char **field_names; + uint32_t large_state_count; + const uint16_t *small_parse_table; + const uint32_t *small_parse_table_map; +}; + +/* + * Lexer Macros + */ + +#define START_LEXER() \ + bool result = false; \ + bool skip = false; \ + int32_t lookahead; \ + goto start; \ + next_state: \ + lexer->advance(lexer, skip); \ + start: \ + skip = false; \ + lookahead = lexer->lookahead; + +#define ADVANCE(state_value) \ + { \ + state = state_value; \ + goto next_state; \ + } + +#define SKIP(state_value) \ + { \ + skip = true; \ + state = state_value; \ + goto next_state; \ + } + +#define ACCEPT_TOKEN(symbol_value) \ + result = true; \ + lexer->result_symbol = symbol_value; \ + lexer->mark_end(lexer); + +#define END_STATE() return result; + +/* + * Parse Table Macros + */ + +#define SMALL_STATE(id) id - LARGE_STATE_COUNT + +#define STATE(id) id + +#define ACTIONS(id) id + +#define SHIFT(state_value) \ + { \ + { \ + .type = TSParseActionTypeShift, \ + .params = {.state = state_value}, \ + } \ + } + +#define SHIFT_REPEAT(state_value) \ + { \ + { \ + .type = TSParseActionTypeShift, \ + .params = { \ + .state = state_value, \ + .repetition = true \ + }, \ + } \ + } + +#define RECOVER() \ + { \ + { .type = TSParseActionTypeRecover } \ + } + +#define SHIFT_EXTRA() \ + { \ + { \ + .type = TSParseActionTypeShift, \ + .params = {.extra = true} \ + } \ + } + +#define REDUCE(symbol_val, child_count_val, ...) \ + { \ + { \ + .type = TSParseActionTypeReduce, \ + .params = { \ + .symbol = symbol_val, \ + .child_count = child_count_val, \ + __VA_ARGS__ \ + } \ + } \ + } + +#define ACCEPT_INPUT() \ + { \ + { .type = TSParseActionTypeAccept } \ + } + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_PARSER_H_ diff --git a/src/tree_sitter/point.h b/src/tree_sitter/point.h new file mode 100644 index 0000000000..4d0aed18ef --- /dev/null +++ b/src/tree_sitter/point.h @@ -0,0 +1,53 @@ +#ifndef TREE_SITTER_POINT_H_ +#define TREE_SITTER_POINT_H_ + +#include "tree_sitter/api.h" + +#define POINT_MAX ((TSPoint) {UINT32_MAX, UINT32_MAX}) + +static inline TSPoint point__new(unsigned row, unsigned column) { + TSPoint result = {row, column}; + return result; +} + +static inline TSPoint point_add(TSPoint a, TSPoint b) { + if (b.row > 0) + return point__new(a.row + b.row, b.column); + else + return point__new(a.row, a.column + b.column); +} + +static inline TSPoint point_sub(TSPoint a, TSPoint b) { + if (a.row > b.row) + return point__new(a.row - b.row, a.column); + else + return point__new(0, a.column - b.column); +} + +static inline bool point_lte(TSPoint a, TSPoint b) { + return (a.row < b.row) || (a.row == b.row && a.column <= b.column); +} + +static inline bool point_lt(TSPoint a, TSPoint b) { + return (a.row < b.row) || (a.row == b.row && a.column < b.column); +} + +static inline bool point_eq(TSPoint a, TSPoint b) { + return a.row == b.row && a.column == b.column; +} + +static inline TSPoint point_min(TSPoint a, TSPoint b) { + if (a.row < b.row || (a.row == b.row && a.column < b.column)) + return a; + else + return b; +} + +static inline TSPoint point_max(TSPoint a, TSPoint b) { + if (a.row > b.row || (a.row == b.row && a.column > b.column)) + return a; + else + return b; +} + +#endif diff --git a/src/tree_sitter/reduce_action.h b/src/tree_sitter/reduce_action.h new file mode 100644 index 0000000000..72aff08d73 --- /dev/null +++ b/src/tree_sitter/reduce_action.h @@ -0,0 +1,34 @@ +#ifndef TREE_SITTER_REDUCE_ACTION_H_ +#define TREE_SITTER_REDUCE_ACTION_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./array.h" +#include "tree_sitter/api.h" + +typedef struct { + uint32_t count; + TSSymbol symbol; + int dynamic_precedence; + unsigned short production_id; +} ReduceAction; + +typedef Array(ReduceAction) ReduceActionSet; + +static inline void ts_reduce_action_set_add(ReduceActionSet *self, + ReduceAction new_action) { + for (uint32_t i = 0; i < self->size; i++) { + ReduceAction action = self->contents[i]; + if (action.symbol == new_action.symbol && action.count == new_action.count) + return; + } + array_push(self, new_action); +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_REDUCE_ACTION_H_ diff --git a/src/tree_sitter/reusable_node.h b/src/tree_sitter/reusable_node.h new file mode 100644 index 0000000000..9cba951909 --- /dev/null +++ b/src/tree_sitter/reusable_node.h @@ -0,0 +1,88 @@ +#include "./subtree.h" + +typedef struct { + Subtree tree; + uint32_t child_index; + uint32_t byte_offset; +} StackEntry; + +typedef struct { + Array(StackEntry) stack; + Subtree last_external_token; +} ReusableNode; + +static inline ReusableNode reusable_node_new(void) { + return (ReusableNode) {array_new(), NULL_SUBTREE}; +} + +static inline void reusable_node_clear(ReusableNode *self) { + array_clear(&self->stack); + self->last_external_token = NULL_SUBTREE; +} + +static inline void reusable_node_reset(ReusableNode *self, Subtree tree) { + reusable_node_clear(self); + array_push(&self->stack, ((StackEntry) { + .tree = tree, + .child_index = 0, + .byte_offset = 0, + })); +} + +static inline Subtree reusable_node_tree(ReusableNode *self) { + return self->stack.size > 0 + ? self->stack.contents[self->stack.size - 1].tree + : NULL_SUBTREE; +} + +static inline uint32_t reusable_node_byte_offset(ReusableNode *self) { + return self->stack.size > 0 + ? self->stack.contents[self->stack.size - 1].byte_offset + : UINT32_MAX; +} + +static inline void reusable_node_delete(ReusableNode *self) { + array_delete(&self->stack); +} + +static inline void reusable_node_advance(ReusableNode *self) { + StackEntry last_entry = *array_back(&self->stack); + uint32_t byte_offset = last_entry.byte_offset + ts_subtree_total_bytes(last_entry.tree); + if (ts_subtree_has_external_tokens(last_entry.tree)) { + self->last_external_token = ts_subtree_last_external_token(last_entry.tree); + } + + Subtree tree; + uint32_t next_index; + do { + StackEntry popped_entry = array_pop(&self->stack); + next_index = popped_entry.child_index + 1; + if (self->stack.size == 0) return; + tree = array_back(&self->stack)->tree; + } while (ts_subtree_child_count(tree) <= next_index); + + array_push(&self->stack, ((StackEntry) { + .tree = tree.ptr->children[next_index], + .child_index = next_index, + .byte_offset = byte_offset, + })); +} + +static inline bool reusable_node_descend(ReusableNode *self) { + StackEntry last_entry = *array_back(&self->stack); + if (ts_subtree_child_count(last_entry.tree) > 0) { + array_push(&self->stack, ((StackEntry) { + .tree = last_entry.tree.ptr->children[0], + .child_index = 0, + .byte_offset = last_entry.byte_offset, + })); + return true; + } else { + return false; + } +} + +static inline void reusable_node_advance_past_leaf(ReusableNode *self) { + while (reusable_node_descend(self)) {} + reusable_node_advance(self); +} diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c new file mode 100644 index 0000000000..3e842c99c3 --- /dev/null +++ b/src/tree_sitter/stack.c @@ -0,0 +1,846 @@ +#include "./alloc.h" +#include "./language.h" +#include "./subtree.h" +#include "./array.h" +#include "./stack.h" +#include "./length.h" +#include +#include + +#define MAX_LINK_COUNT 8 +#define MAX_NODE_POOL_SIZE 50 +#define MAX_ITERATOR_COUNT 64 + +#ifdef _WIN32 +#define inline __forceinline +#else +#define inline static inline __attribute__((always_inline)) +#endif + +typedef struct StackNode StackNode; + +typedef struct { + StackNode *node; + Subtree subtree; + bool is_pending; +} StackLink; + +struct StackNode { + TSStateId state; + Length position; + StackLink links[MAX_LINK_COUNT]; + short unsigned int link_count; + uint32_t ref_count; + unsigned error_cost; + unsigned node_count; + int dynamic_precedence; +}; + +typedef struct { + StackNode *node; + SubtreeArray subtrees; + uint32_t subtree_count; + bool is_pending; +} StackIterator; + +typedef struct { + void *payload; + StackIterateCallback callback; +} StackIterateSession; + +typedef Array(StackNode *) StackNodeArray; + +typedef enum { + StackStatusActive, + StackStatusPaused, + StackStatusHalted, +} StackStatus; + +typedef struct { + StackNode *node; + Subtree last_external_token; + StackSummary *summary; + unsigned node_count_at_last_error; + TSSymbol lookahead_when_paused; + StackStatus status; +} StackHead; + +struct Stack { + Array(StackHead) heads; + StackSliceArray slices; + Array(StackIterator) iterators; + StackNodeArray node_pool; + StackNode *base_node; + SubtreePool *subtree_pool; +}; + +typedef unsigned StackAction; +enum { + StackActionNone, + StackActionStop = 1, + StackActionPop = 2, +}; + +typedef StackAction (*StackCallback)(void *, const StackIterator *); + +static void stack_node_retain(StackNode *self) { + if (!self) + return; + assert(self->ref_count > 0); + self->ref_count++; + assert(self->ref_count != 0); +} + +static void stack_node_release(StackNode *self, StackNodeArray *pool, SubtreePool *subtree_pool) { +recur: + assert(self->ref_count != 0); + self->ref_count--; + if (self->ref_count > 0) return; + + StackNode *first_predecessor = NULL; + if (self->link_count > 0) { + for (unsigned i = self->link_count - 1; i > 0; i--) { + StackLink link = self->links[i]; + if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree); + stack_node_release(link.node, pool, subtree_pool); + } + StackLink link = self->links[0]; + if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree); + first_predecessor = self->links[0].node; + } + + if (pool->size < MAX_NODE_POOL_SIZE) { + array_push(pool, self); + } else { + ts_free(self); + } + + if (first_predecessor) { + self = first_predecessor; + goto recur; + } +} + +static StackNode *stack_node_new(StackNode *previous_node, Subtree subtree, + bool is_pending, TSStateId state, StackNodeArray *pool) { + StackNode *node = pool->size > 0 ? + array_pop(pool) : + ts_malloc(sizeof(StackNode)); + *node = (StackNode){.ref_count = 1, .link_count = 0, .state = state}; + + if (previous_node) { + node->link_count = 1; + node->links[0] = (StackLink){ + .node = previous_node, + .subtree = subtree, + .is_pending = is_pending, + }; + + node->position = previous_node->position; + node->error_cost = previous_node->error_cost; + node->dynamic_precedence = previous_node->dynamic_precedence; + node->node_count = previous_node->node_count; + + if (subtree.ptr) { + node->error_cost += ts_subtree_error_cost(subtree); + node->position = length_add(node->position, ts_subtree_total_size(subtree)); + node->node_count += ts_subtree_node_count(subtree); + node->dynamic_precedence += ts_subtree_dynamic_precedence(subtree); + } + } else { + node->position = length_zero(); + node->error_cost = 0; + } + + return node; +} + +static bool stack__subtree_is_equivalent(Subtree left, Subtree right) { + return + left.ptr == right.ptr || + (left.ptr && right.ptr && + ts_subtree_symbol(left) == ts_subtree_symbol(right) && + ((ts_subtree_error_cost(left) > 0 && ts_subtree_error_cost(right) > 0) || + (ts_subtree_padding(left).bytes == ts_subtree_padding(right).bytes && + ts_subtree_size(left).bytes == ts_subtree_size(right).bytes && + ts_subtree_child_count(left) == ts_subtree_child_count(right) && + ts_subtree_extra(left) == ts_subtree_extra(right) && + ts_subtree_external_scanner_state_eq(left, right)))); +} + +static void stack_node_add_link(StackNode *self, StackLink link, SubtreePool *subtree_pool) { + if (link.node == self) return; + + for (int i = 0; i < self->link_count; i++) { + StackLink *existing_link = &self->links[i]; + if (stack__subtree_is_equivalent(existing_link->subtree, link.subtree)) { + // In general, we preserve ambiguities until they are removed from the stack + // during a pop operation where multiple paths lead to the same node. But in + // the special case where two links directly connect the same pair of nodes, + // we can safely remove the ambiguity ahead of time without changing behavior. + if (existing_link->node == link.node) { + if ( + ts_subtree_dynamic_precedence(link.subtree) > + ts_subtree_dynamic_precedence(existing_link->subtree) + ) { + ts_subtree_retain(link.subtree); + ts_subtree_release(subtree_pool, existing_link->subtree); + existing_link->subtree = link.subtree; + self->dynamic_precedence = + link.node->dynamic_precedence + ts_subtree_dynamic_precedence(link.subtree); + } + return; + } + + // If the previous nodes are mergeable, merge them recursively. + if (existing_link->node->state == link.node->state && + existing_link->node->position.bytes == link.node->position.bytes) { + for (int j = 0; j < link.node->link_count; j++) { + stack_node_add_link(existing_link->node, link.node->links[j], subtree_pool); + } + int32_t dynamic_precedence = link.node->dynamic_precedence; + if (link.subtree.ptr) { + dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree); + } + if (dynamic_precedence > self->dynamic_precedence) { + self->dynamic_precedence = dynamic_precedence; + } + return; + } + } + } + + if (self->link_count == MAX_LINK_COUNT) return; + + stack_node_retain(link.node); + unsigned node_count = link.node->node_count; + int dynamic_precedence = link.node->dynamic_precedence; + self->links[self->link_count++] = link; + + if (link.subtree.ptr) { + ts_subtree_retain(link.subtree); + node_count += ts_subtree_node_count(link.subtree); + dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree); + } + + if (node_count > self->node_count) self->node_count = node_count; + if (dynamic_precedence > self->dynamic_precedence) self->dynamic_precedence = dynamic_precedence; +} + +static void stack_head_delete(StackHead *self, StackNodeArray *pool, SubtreePool *subtree_pool) { + if (self->node) { + if (self->last_external_token.ptr) { + ts_subtree_release(subtree_pool, self->last_external_token); + } + if (self->summary) { + array_delete(self->summary); + ts_free(self->summary); + } + stack_node_release(self->node, pool, subtree_pool); + } +} + +static StackVersion ts_stack__add_version(Stack *self, StackVersion original_version, + StackNode *node) { + StackHead head = { + .node = node, + .node_count_at_last_error = self->heads.contents[original_version].node_count_at_last_error, + .last_external_token = self->heads.contents[original_version].last_external_token, + .status = StackStatusActive, + .lookahead_when_paused = 0, + }; + array_push(&self->heads, head); + stack_node_retain(node); + if (head.last_external_token.ptr) ts_subtree_retain(head.last_external_token); + return (StackVersion)(self->heads.size - 1); +} + +static void ts_stack__add_slice(Stack *self, StackVersion original_version, + StackNode *node, SubtreeArray *subtrees) { + for (uint32_t i = self->slices.size - 1; i + 1 > 0; i--) { + StackVersion version = self->slices.contents[i].version; + if (self->heads.contents[version].node == node) { + StackSlice slice = {*subtrees, version}; + array_insert(&self->slices, i + 1, slice); + return; + } + } + + StackVersion version = ts_stack__add_version(self, original_version, node); + StackSlice slice = { *subtrees, version }; + array_push(&self->slices, slice); +} + +inline StackSliceArray stack__iter(Stack *self, StackVersion version, + StackCallback callback, void *payload, + int goal_subtree_count) { + array_clear(&self->slices); + array_clear(&self->iterators); + + StackHead *head = array_get(&self->heads, version); + StackIterator iterator = { + .node = head->node, + .subtrees = array_new(), + .subtree_count = 0, + .is_pending = true, + }; + + bool include_subtrees = false; + if (goal_subtree_count >= 0) { + include_subtrees = true; + array_reserve(&iterator.subtrees, goal_subtree_count); + } + + array_push(&self->iterators, iterator); + + while (self->iterators.size > 0) { + for (uint32_t i = 0, size = self->iterators.size; i < size; i++) { + StackIterator *iterator = &self->iterators.contents[i]; + StackNode *node = iterator->node; + + StackAction action = callback(payload, iterator); + bool should_pop = action & StackActionPop; + bool should_stop = action & StackActionStop || node->link_count == 0; + + if (should_pop) { + SubtreeArray subtrees = iterator->subtrees; + if (!should_stop) + ts_subtree_array_copy(subtrees, &subtrees); + ts_subtree_array_reverse(&subtrees); + ts_stack__add_slice( + self, + version, + node, + &subtrees + ); + } + + if (should_stop) { + if (!should_pop) + ts_subtree_array_delete(self->subtree_pool, &iterator->subtrees); + array_erase(&self->iterators, i); + i--, size--; + continue; + } + + for (uint32_t j = 1; j <= node->link_count; j++) { + StackIterator *next_iterator; + StackLink link; + if (j == node->link_count) { + link = node->links[0]; + next_iterator = &self->iterators.contents[i]; + } else { + if (self->iterators.size >= MAX_ITERATOR_COUNT) continue; + link = node->links[j]; + StackIterator current_iterator = self->iterators.contents[i]; + array_push(&self->iterators, current_iterator); + next_iterator = array_back(&self->iterators); + ts_subtree_array_copy(next_iterator->subtrees, &next_iterator->subtrees); + } + + next_iterator->node = link.node; + if (link.subtree.ptr) { + if (include_subtrees) { + array_push(&next_iterator->subtrees, link.subtree); + ts_subtree_retain(link.subtree); + } + + if (!ts_subtree_extra(link.subtree)) { + next_iterator->subtree_count++; + if (!link.is_pending) { + next_iterator->is_pending = false; + } + } + } else { + next_iterator->subtree_count++; + next_iterator->is_pending = false; + } + } + } + } + + return self->slices; +} + +Stack *ts_stack_new(SubtreePool *subtree_pool) { + Stack *self = ts_calloc(1, sizeof(Stack)); + + array_init(&self->heads); + array_init(&self->slices); + array_init(&self->iterators); + array_init(&self->node_pool); + array_reserve(&self->heads, 4); + array_reserve(&self->slices, 4); + array_reserve(&self->iterators, 4); + array_reserve(&self->node_pool, MAX_NODE_POOL_SIZE); + + self->subtree_pool = subtree_pool; + self->base_node = stack_node_new(NULL, NULL_SUBTREE, false, 1, &self->node_pool); + ts_stack_clear(self); + + return self; +} + +void ts_stack_delete(Stack *self) { + if (self->slices.contents) + array_delete(&self->slices); + if (self->iterators.contents) + array_delete(&self->iterators); + stack_node_release(self->base_node, &self->node_pool, self->subtree_pool); + for (uint32_t i = 0; i < self->heads.size; i++) { + stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool); + } + array_clear(&self->heads); + if (self->node_pool.contents) { + for (uint32_t i = 0; i < self->node_pool.size; i++) + ts_free(self->node_pool.contents[i]); + array_delete(&self->node_pool); + } + array_delete(&self->heads); + ts_free(self); +} + +uint32_t ts_stack_version_count(const Stack *self) { + return self->heads.size; +} + +TSStateId ts_stack_state(const Stack *self, StackVersion version) { + return array_get(&self->heads, version)->node->state; +} + +Length ts_stack_position(const Stack *self, StackVersion version) { + return array_get(&self->heads, version)->node->position; +} + +Subtree ts_stack_last_external_token(const Stack *self, StackVersion version) { + return array_get(&self->heads, version)->last_external_token; +} + +void ts_stack_set_last_external_token(Stack *self, StackVersion version, Subtree token) { + StackHead *head = array_get(&self->heads, version); + if (token.ptr) ts_subtree_retain(token); + if (head->last_external_token.ptr) ts_subtree_release(self->subtree_pool, head->last_external_token); + head->last_external_token = token; +} + +unsigned ts_stack_error_cost(const Stack *self, StackVersion version) { + StackHead *head = array_get(&self->heads, version); + unsigned result = head->node->error_cost; + if ( + head->status == StackStatusPaused || + (head->node->state == ERROR_STATE && !head->node->links[0].subtree.ptr)) { + result += ERROR_COST_PER_RECOVERY; + } + return result; +} + +unsigned ts_stack_node_count_since_error(const Stack *self, StackVersion version) { + StackHead *head = array_get(&self->heads, version); + if (head->node->node_count < head->node_count_at_last_error) { + head->node_count_at_last_error = head->node->node_count; + } + return head->node->node_count - head->node_count_at_last_error; +} + +void ts_stack_push(Stack *self, StackVersion version, Subtree subtree, + bool pending, TSStateId state) { + StackHead *head = array_get(&self->heads, version); + StackNode *new_node = stack_node_new(head->node, subtree, pending, state, &self->node_pool); + if (!subtree.ptr) head->node_count_at_last_error = new_node->node_count; + head->node = new_node; +} + +inline StackAction iterate_callback(void *payload, const StackIterator *iterator) { + StackIterateSession *session = payload; + session->callback( + session->payload, + iterator->node->state, + iterator->subtree_count + ); + return StackActionNone; +} + +void ts_stack_iterate(Stack *self, StackVersion version, + StackIterateCallback callback, void *payload) { + StackIterateSession session = {payload, callback}; + stack__iter(self, version, iterate_callback, &session, -1); +} + +inline StackAction pop_count_callback(void *payload, const StackIterator *iterator) { + unsigned *goal_subtree_count = payload; + if (iterator->subtree_count == *goal_subtree_count) { + return StackActionPop | StackActionStop; + } else { + return StackActionNone; + } +} + +StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t count) { + return stack__iter(self, version, pop_count_callback, &count, count); +} + +inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) { + if (iterator->subtree_count >= 1) { + if (iterator->is_pending) { + return StackActionPop | StackActionStop; + } else { + return StackActionStop; + } + } else { + return StackActionNone; + } +} + +StackSliceArray ts_stack_pop_pending(Stack *self, StackVersion version) { + StackSliceArray pop = stack__iter(self, version, pop_pending_callback, NULL, 0); + if (pop.size > 0) { + ts_stack_renumber_version(self, pop.contents[0].version, version); + pop.contents[0].version = version; + } + return pop; +} + +inline StackAction pop_error_callback(void *payload, const StackIterator *iterator) { + if (iterator->subtrees.size > 0) { + bool *found_error = payload; + if (!*found_error && ts_subtree_is_error(iterator->subtrees.contents[0])) { + *found_error = true; + return StackActionPop | StackActionStop; + } else { + return StackActionStop; + } + } else { + return StackActionNone; + } +} + +SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) { + StackNode *node = array_get(&self->heads, version)->node; + for (unsigned i = 0; i < node->link_count; i++) { + if (node->links[i].subtree.ptr && ts_subtree_is_error(node->links[i].subtree)) { + bool found_error = false; + StackSliceArray pop = stack__iter(self, version, pop_error_callback, &found_error, 1); + if (pop.size > 0) { + assert(pop.size == 1); + ts_stack_renumber_version(self, pop.contents[0].version, version); + return pop.contents[0].subtrees; + } + break; + } + } + return (SubtreeArray){.size = 0}; +} + +inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) { + return iterator->node->link_count == 0 ? StackActionPop : StackActionNone; +} + +StackSliceArray ts_stack_pop_all(Stack *self, StackVersion version) { + return stack__iter(self, version, pop_all_callback, NULL, 0); +} + +typedef struct { + StackSummary *summary; + unsigned max_depth; +} SummarizeStackSession; + +inline StackAction summarize_stack_callback(void *payload, const StackIterator *iterator) { + SummarizeStackSession *session = payload; + TSStateId state = iterator->node->state; + unsigned depth = iterator->subtree_count; + if (depth > session->max_depth) return StackActionStop; + for (unsigned i = session->summary->size - 1; i + 1 > 0; i--) { + StackSummaryEntry entry = session->summary->contents[i]; + if (entry.depth < depth) break; + if (entry.depth == depth && entry.state == state) return StackActionNone; + } + array_push(session->summary, ((StackSummaryEntry){ + .position = iterator->node->position, + .depth = depth, + .state = state, + })); + return StackActionNone; +} + +void ts_stack_record_summary(Stack *self, StackVersion version, unsigned max_depth) { + SummarizeStackSession session = { + .summary = ts_malloc(sizeof(StackSummary)), + .max_depth = max_depth + }; + array_init(session.summary); + stack__iter(self, version, summarize_stack_callback, &session, -1); + self->heads.contents[version].summary = session.summary; +} + +StackSummary *ts_stack_get_summary(Stack *self, StackVersion version) { + return array_get(&self->heads, version)->summary; +} + +int ts_stack_dynamic_precedence(Stack *self, StackVersion version) { + return array_get(&self->heads, version)->node->dynamic_precedence; +} + +bool ts_stack_has_advanced_since_error(const Stack *self, StackVersion version) { + const StackHead *head = array_get(&self->heads, version); + const StackNode *node = head->node; + if (node->error_cost == 0) return true; + while (node) { + if (node->link_count > 0) { + Subtree subtree = node->links[0].subtree; + if (subtree.ptr) { + if (ts_subtree_total_bytes(subtree) > 0) { + return true; + } else if ( + node->node_count > head->node_count_at_last_error && + ts_subtree_error_cost(subtree) == 0 + ) { + node = node->links[0].node; + continue; + } + } + } + break; + } + return false; +} + +void ts_stack_remove_version(Stack *self, StackVersion version) { + stack_head_delete(array_get(&self->heads, version), &self->node_pool, self->subtree_pool); + array_erase(&self->heads, version); +} + +void ts_stack_renumber_version(Stack *self, StackVersion v1, StackVersion v2) { + if (v1 == v2) return; + assert(v2 < v1); + assert((uint32_t)v1 < self->heads.size); + StackHead *source_head = &self->heads.contents[v1]; + StackHead *target_head = &self->heads.contents[v2]; + if (target_head->summary && !source_head->summary) { + source_head->summary = target_head->summary; + target_head->summary = NULL; + } + stack_head_delete(target_head, &self->node_pool, self->subtree_pool); + *target_head = *source_head; + array_erase(&self->heads, v1); +} + +void ts_stack_swap_versions(Stack *self, StackVersion v1, StackVersion v2) { + StackHead temporary_head = self->heads.contents[v1]; + self->heads.contents[v1] = self->heads.contents[v2]; + self->heads.contents[v2] = temporary_head; +} + +StackVersion ts_stack_copy_version(Stack *self, StackVersion version) { + assert(version < self->heads.size); + array_push(&self->heads, self->heads.contents[version]); + StackHead *head = array_back(&self->heads); + stack_node_retain(head->node); + if (head->last_external_token.ptr) ts_subtree_retain(head->last_external_token); + head->summary = NULL; + return self->heads.size - 1; +} + +bool ts_stack_merge(Stack *self, StackVersion version1, StackVersion version2) { + if (!ts_stack_can_merge(self, version1, version2)) return false; + StackHead *head1 = &self->heads.contents[version1]; + StackHead *head2 = &self->heads.contents[version2]; + for (uint32_t i = 0; i < head2->node->link_count; i++) { + stack_node_add_link(head1->node, head2->node->links[i], self->subtree_pool); + } + if (head1->node->state == ERROR_STATE) { + head1->node_count_at_last_error = head1->node->node_count; + } + ts_stack_remove_version(self, version2); + return true; +} + +bool ts_stack_can_merge(Stack *self, StackVersion version1, StackVersion version2) { + StackHead *head1 = &self->heads.contents[version1]; + StackHead *head2 = &self->heads.contents[version2]; + return + head1->status == StackStatusActive && + head2->status == StackStatusActive && + head1->node->state == head2->node->state && + head1->node->position.bytes == head2->node->position.bytes && + head1->node->error_cost == head2->node->error_cost && + ts_subtree_external_scanner_state_eq(head1->last_external_token, head2->last_external_token); +} + +void ts_stack_halt(Stack *self, StackVersion version) { + array_get(&self->heads, version)->status = StackStatusHalted; +} + +void ts_stack_pause(Stack *self, StackVersion version, TSSymbol lookahead) { + StackHead *head = array_get(&self->heads, version); + head->status = StackStatusPaused; + head->lookahead_when_paused = lookahead; + head->node_count_at_last_error = head->node->node_count; +} + +bool ts_stack_is_active(const Stack *self, StackVersion version) { + return array_get(&self->heads, version)->status == StackStatusActive; +} + +bool ts_stack_is_halted(const Stack *self, StackVersion version) { + return array_get(&self->heads, version)->status == StackStatusHalted; +} + +bool ts_stack_is_paused(const Stack *self, StackVersion version) { + return array_get(&self->heads, version)->status == StackStatusPaused; +} + +TSSymbol ts_stack_resume(Stack *self, StackVersion version) { + StackHead *head = array_get(&self->heads, version); + assert(head->status == StackStatusPaused); + TSSymbol result = head->lookahead_when_paused; + head->status = StackStatusActive; + head->lookahead_when_paused = 0; + return result; +} + +void ts_stack_clear(Stack *self) { + stack_node_retain(self->base_node); + for (uint32_t i = 0; i < self->heads.size; i++) { + stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool); + } + array_clear(&self->heads); + array_push(&self->heads, ((StackHead){ + .node = self->base_node, + .last_external_token = NULL_SUBTREE, + .status = StackStatusActive, + .lookahead_when_paused = 0, + })); +} + +bool ts_stack_print_dot_graph(Stack *self, const TSLanguage *language, FILE *f) { + array_reserve(&self->iterators, 32); + bool was_recording_allocations = ts_toggle_allocation_recording(false); + if (!f) f = stderr; + + fprintf(f, "digraph stack {\n"); + fprintf(f, "rankdir=\"RL\";\n"); + fprintf(f, "edge [arrowhead=none]\n"); + + Array(StackNode *) visited_nodes = array_new(); + + array_clear(&self->iterators); + for (uint32_t i = 0; i < self->heads.size; i++) { + StackHead *head = &self->heads.contents[i]; + if (head->status == StackStatusHalted) continue; + + fprintf(f, "node_head_%u [shape=none, label=\"\"]\n", i); + fprintf(f, "node_head_%u -> node_%p [", i, head->node); + + if (head->status == StackStatusPaused) { + fprintf(f, "color=red "); + } + fprintf(f, + "label=%u, fontcolor=blue, weight=10000, labeltooltip=\"node_count: %u\nerror_cost: %u", + i, + ts_stack_node_count_since_error(self, i), + ts_stack_error_cost(self, i) + ); + + if (head->last_external_token.ptr) { + const ExternalScannerState *state = &head->last_external_token.ptr->external_scanner_state; + const char *data = ts_external_scanner_state_data(state); + fprintf(f, "\nexternal_scanner_state:"); + for (uint32_t j = 0; j < state->length; j++) fprintf(f, " %2X", data[j]); + } + + fprintf(f, "\"]\n"); + array_push(&self->iterators, ((StackIterator){.node = head->node })); + } + + bool all_iterators_done = false; + while (!all_iterators_done) { + all_iterators_done = true; + + for (uint32_t i = 0; i < self->iterators.size; i++) { + StackIterator iterator = self->iterators.contents[i]; + StackNode *node = iterator.node; + + for (uint32_t j = 0; j < visited_nodes.size; j++) { + if (visited_nodes.contents[j] == node) { + node = NULL; + break; + } + } + + if (!node) continue; + all_iterators_done = false; + + fprintf(f, "node_%p [", node); + if (node->state == ERROR_STATE) { + fprintf(f, "label=\"?\""); + } else if ( + node->link_count == 1 && + node->links[0].subtree.ptr && + ts_subtree_extra(node->links[0].subtree) + ) { + fprintf(f, "shape=point margin=0 label=\"\""); + } else { + fprintf(f, "label=\"%d\"", node->state); + } + + fprintf( + f, + " tooltip=\"position: %u,%u\nnode_count:%u\nerror_cost: %u\ndynamic_precedence: %d\"];\n", + node->position.extent.row + 1, + node->position.extent.column, + node->node_count, + node->error_cost, + node->dynamic_precedence + ); + + for (int j = 0; j < node->link_count; j++) { + StackLink link = node->links[j]; + fprintf(f, "node_%p -> node_%p [", node, link.node); + if (link.is_pending) fprintf(f, "style=dashed "); + if (link.subtree.ptr && ts_subtree_extra(link.subtree)) fprintf(f, "fontcolor=gray "); + + if (!link.subtree.ptr) { + fprintf(f, "color=red"); + } else { + fprintf(f, "label=\""); + bool quoted = ts_subtree_visible(link.subtree) && !ts_subtree_named(link.subtree); + if (quoted) fprintf(f, "'"); + const char *name = ts_language_symbol_name(language, ts_subtree_symbol(link.subtree)); + for (const char *c = name; *c; c++) { + if (*c == '\"' || *c == '\\') fprintf(f, "\\"); + fprintf(f, "%c", *c); + } + if (quoted) fprintf(f, "'"); + fprintf(f, "\""); + fprintf( + f, + "labeltooltip=\"error_cost: %u\ndynamic_precedence: %u\"", + ts_subtree_error_cost(link.subtree), + ts_subtree_dynamic_precedence(link.subtree) + ); + } + + fprintf(f, "];\n"); + + StackIterator *next_iterator; + if (j == 0) { + next_iterator = &self->iterators.contents[i]; + } else { + array_push(&self->iterators, iterator); + next_iterator = array_back(&self->iterators); + } + next_iterator->node = link.node; + } + + array_push(&visited_nodes, node); + } + } + + fprintf(f, "}\n"); + + array_delete(&visited_nodes); + ts_toggle_allocation_recording(was_recording_allocations); + return true; +} + +#undef inline diff --git a/src/tree_sitter/stack.h b/src/tree_sitter/stack.h new file mode 100644 index 0000000000..ec7a69d2b4 --- /dev/null +++ b/src/tree_sitter/stack.h @@ -0,0 +1,135 @@ +#ifndef TREE_SITTER_PARSE_STACK_H_ +#define TREE_SITTER_PARSE_STACK_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./array.h" +#include "./subtree.h" +#include "./error_costs.h" +#include + +typedef struct Stack Stack; + +typedef unsigned StackVersion; +#define STACK_VERSION_NONE ((StackVersion)-1) + +typedef struct { + SubtreeArray subtrees; + StackVersion version; +} StackSlice; +typedef Array(StackSlice) StackSliceArray; + +typedef struct { + Length position; + unsigned depth; + TSStateId state; +} StackSummaryEntry; +typedef Array(StackSummaryEntry) StackSummary; + +// Create a stack. +Stack *ts_stack_new(SubtreePool *); + +// Release the memory reserved for a given stack. +void ts_stack_delete(Stack *); + +// Get the stack's current number of versions. +uint32_t ts_stack_version_count(const Stack *); + +// Get the state at the top of the given version of the stack. If the stack is +// empty, this returns the initial state, 0. +TSStateId ts_stack_state(const Stack *, StackVersion); + +// Get the last external token associated with a given version of the stack. +Subtree ts_stack_last_external_token(const Stack *, StackVersion); + +// Set the last external token associated with a given version of the stack. +void ts_stack_set_last_external_token(Stack *, StackVersion, Subtree ); + +// Get the position of the given version of the stack within the document. +Length ts_stack_position(const Stack *, StackVersion); + +// Push a tree and state onto the given version of the stack. +// +// This transfers ownership of the tree to the Stack. Callers that +// need to retain ownership of the tree for their own purposes should +// first retain the tree. +void ts_stack_push(Stack *, StackVersion, Subtree , bool, TSStateId); + +// Pop the given number of entries from the given version of the stack. This +// operation can increase the number of stack versions by revealing multiple +// versions which had previously been merged. It returns an array that +// specifies the index of each revealed version and the trees that were +// removed from that version. +StackSliceArray ts_stack_pop_count(Stack *, StackVersion, uint32_t count); + +// Remove an error at the top of the given version of the stack. +SubtreeArray ts_stack_pop_error(Stack *, StackVersion); + +// Remove any pending trees from the top of the given version of the stack. +StackSliceArray ts_stack_pop_pending(Stack *, StackVersion); + +// Remove any all trees from the given version of the stack. +StackSliceArray ts_stack_pop_all(Stack *, StackVersion); + +// Get the maximum number of tree nodes reachable from this version of the stack +// since the last error was detected. +unsigned ts_stack_node_count_since_error(const Stack *, StackVersion); + +int ts_stack_dynamic_precedence(Stack *, StackVersion); + +bool ts_stack_has_advanced_since_error(const Stack *, StackVersion); + +// Compute a summary of all the parse states near the top of the given +// version of the stack and store the summary for later retrieval. +void ts_stack_record_summary(Stack *, StackVersion, unsigned max_depth); + +// Retrieve a summary of all the parse states near the top of the +// given version of the stack. +StackSummary *ts_stack_get_summary(Stack *, StackVersion); + +// Get the total cost of all errors on the given version of the stack. +unsigned ts_stack_error_cost(const Stack *, StackVersion version); + +// Merge the given two stack versions if possible, returning true +// if they were successfully merged and false otherwise. +bool ts_stack_merge(Stack *, StackVersion, StackVersion); + +// Determine whether the given two stack versions can be merged. +bool ts_stack_can_merge(Stack *, StackVersion, StackVersion); + +TSSymbol ts_stack_resume(Stack *, StackVersion); + +void ts_stack_pause(Stack *, StackVersion, TSSymbol); + +void ts_stack_halt(Stack *, StackVersion); + +bool ts_stack_is_active(const Stack *, StackVersion); + +bool ts_stack_is_paused(const Stack *, StackVersion); + +bool ts_stack_is_halted(const Stack *, StackVersion); + +void ts_stack_renumber_version(Stack *, StackVersion, StackVersion); + +void ts_stack_swap_versions(Stack *, StackVersion, StackVersion); + +StackVersion ts_stack_copy_version(Stack *, StackVersion); + +// Remove the given version from the stack. +void ts_stack_remove_version(Stack *, StackVersion); + +void ts_stack_clear(Stack *); + +bool ts_stack_print_dot_graph(Stack *, const TSLanguage *, FILE *); + +typedef void (*StackIterateCallback)(void *, TSStateId, uint32_t); + +void ts_stack_iterate(Stack *, StackVersion, StackIterateCallback, void *); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_PARSE_STACK_H_ diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c new file mode 100644 index 0000000000..e95733eb46 --- /dev/null +++ b/src/tree_sitter/subtree.c @@ -0,0 +1,996 @@ +#include +#include +#include +#include +#include +#include +#include "./alloc.h" +#include "./atomic.h" +#include "./subtree.h" +#include "./length.h" +#include "./language.h" +#include "./error_costs.h" +#include + +typedef struct { + Length start; + Length old_end; + Length new_end; +} Edit; + +#ifdef TREE_SITTER_TEST + +#define TS_MAX_INLINE_TREE_LENGTH 2 +#define TS_MAX_TREE_POOL_SIZE 0 + +#else + +#define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX +#define TS_MAX_TREE_POOL_SIZE 32 + +#endif + +static const ExternalScannerState empty_state = {.length = 0, .short_data = {0}}; + +// ExternalScannerState + +void ts_external_scanner_state_init(ExternalScannerState *self, const char *data, unsigned length) { + self->length = length; + if (length > sizeof(self->short_data)) { + self->long_data = ts_malloc(length); + memcpy(self->long_data, data, length); + } else { + memcpy(self->short_data, data, length); + } +} + +ExternalScannerState ts_external_scanner_state_copy(const ExternalScannerState *self) { + ExternalScannerState result = *self; + if (self->length > sizeof(self->short_data)) { + result.long_data = ts_malloc(self->length); + memcpy(result.long_data, self->long_data, self->length); + } + return result; +} + +void ts_external_scanner_state_delete(ExternalScannerState *self) { + if (self->length > sizeof(self->short_data)) { + ts_free(self->long_data); + } +} + +const char *ts_external_scanner_state_data(const ExternalScannerState *self) { + if (self->length > sizeof(self->short_data)) { + return self->long_data; + } else { + return self->short_data; + } +} + +bool ts_external_scanner_state_eq(const ExternalScannerState *a, const ExternalScannerState *b) { + return a == b || ( + a->length == b->length && + !memcmp(ts_external_scanner_state_data(a), ts_external_scanner_state_data(b), a->length) + ); +} + +// SubtreeArray + +void ts_subtree_array_copy(SubtreeArray self, SubtreeArray *dest) { + dest->size = self.size; + dest->capacity = self.capacity; + dest->contents = self.contents; + if (self.capacity > 0) { + dest->contents = ts_calloc(self.capacity, sizeof(Subtree)); + memcpy(dest->contents, self.contents, self.size * sizeof(Subtree)); + for (uint32_t i = 0; i < self.size; i++) { + ts_subtree_retain(dest->contents[i]); + } + } +} + +void ts_subtree_array_delete(SubtreePool *pool, SubtreeArray *self) { + for (uint32_t i = 0; i < self->size; i++) { + ts_subtree_release(pool, self->contents[i]); + } + array_delete(self); +} + +SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *self) { + SubtreeArray result = array_new(); + + uint32_t i = self->size - 1; + for (; i + 1 > 0; i--) { + Subtree child = self->contents[i]; + if (!ts_subtree_extra(child)) break; + array_push(&result, child); + } + + self->size = i + 1; + ts_subtree_array_reverse(&result); + return result; +} + +void ts_subtree_array_reverse(SubtreeArray *self) { + for (uint32_t i = 0, limit = self->size / 2; i < limit; i++) { + size_t reverse_index = self->size - 1 - i; + Subtree swap = self->contents[i]; + self->contents[i] = self->contents[reverse_index]; + self->contents[reverse_index] = swap; + } +} + +// SubtreePool + +SubtreePool ts_subtree_pool_new(uint32_t capacity) { + SubtreePool self = {array_new(), array_new()}; + array_reserve(&self.free_trees, capacity); + return self; +} + +void ts_subtree_pool_delete(SubtreePool *self) { + if (self->free_trees.contents) { + for (unsigned i = 0; i < self->free_trees.size; i++) { + ts_free(self->free_trees.contents[i].ptr); + } + array_delete(&self->free_trees); + } + if (self->tree_stack.contents) array_delete(&self->tree_stack); +} + +static SubtreeHeapData *ts_subtree_pool_allocate(SubtreePool *self) { + if (self->free_trees.size > 0) { + return array_pop(&self->free_trees).ptr; + } else { + return ts_malloc(sizeof(SubtreeHeapData)); + } +} + +static void ts_subtree_pool_free(SubtreePool *self, SubtreeHeapData *tree) { + if (self->free_trees.capacity > 0 && self->free_trees.size + 1 <= TS_MAX_TREE_POOL_SIZE) { + array_push(&self->free_trees, (MutableSubtree) {.ptr = tree}); + } else { + ts_free(tree); + } +} + +// Subtree + +static inline bool ts_subtree_can_inline(Length padding, Length size, uint32_t lookahead_bytes) { + return + padding.bytes < TS_MAX_INLINE_TREE_LENGTH && + padding.extent.row < 16 && + padding.extent.column < TS_MAX_INLINE_TREE_LENGTH && + size.extent.row == 0 && + size.extent.column < TS_MAX_INLINE_TREE_LENGTH && + lookahead_bytes < 16; +} + +Subtree ts_subtree_new_leaf( + SubtreePool *pool, TSSymbol symbol, Length padding, Length size, + uint32_t lookahead_bytes, TSStateId parse_state, bool has_external_tokens, + bool is_keyword, const TSLanguage *language +) { + TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); + bool extra = symbol == ts_builtin_sym_end; + + bool is_inline = ( + symbol <= UINT8_MAX && + !has_external_tokens && + ts_subtree_can_inline(padding, size, lookahead_bytes) + ); + + if (is_inline) { + return (Subtree) {{ + .parse_state = parse_state, + .symbol = symbol, + .padding_bytes = padding.bytes, + .padding_rows = padding.extent.row, + .padding_columns = padding.extent.column, + .size_bytes = size.bytes, + .lookahead_bytes = lookahead_bytes, + .visible = metadata.visible, + .named = metadata.named, + .extra = extra, + .has_changes = false, + .is_missing = false, + .is_keyword = is_keyword, + .is_inline = true, + }}; + } else { + SubtreeHeapData *data = ts_subtree_pool_allocate(pool); + *data = (SubtreeHeapData) { + .ref_count = 1, + .padding = padding, + .size = size, + .lookahead_bytes = lookahead_bytes, + .error_cost = 0, + .child_count = 0, + .symbol = symbol, + .parse_state = parse_state, + .visible = metadata.visible, + .named = metadata.named, + .extra = extra, + .fragile_left = false, + .fragile_right = false, + .has_changes = false, + .has_external_tokens = has_external_tokens, + .is_missing = false, + .is_keyword = is_keyword, + .first_leaf = {.symbol = 0, .parse_state = 0}, + }; + return (Subtree) {.ptr = data}; + } +} + +void ts_subtree_set_symbol( + MutableSubtree *self, + TSSymbol symbol, + const TSLanguage *language +) { + TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); + if (self->data.is_inline) { + assert(symbol < UINT8_MAX); + self->data.symbol = symbol; + self->data.named = metadata.named; + self->data.visible = metadata.visible; + } else { + self->ptr->symbol = symbol; + self->ptr->named = metadata.named; + self->ptr->visible = metadata.visible; + } +} + +Subtree ts_subtree_new_error( + SubtreePool *pool, int32_t lookahead_char, Length padding, Length size, + uint32_t bytes_scanned, TSStateId parse_state, const TSLanguage *language +) { + Subtree result = ts_subtree_new_leaf( + pool, ts_builtin_sym_error, padding, size, bytes_scanned, + parse_state, false, false, language + ); + SubtreeHeapData *data = (SubtreeHeapData *)result.ptr; + data->fragile_left = true; + data->fragile_right = true; + data->lookahead_char = lookahead_char; + return result; +} + +MutableSubtree ts_subtree_make_mut(SubtreePool *pool, Subtree self) { + if (self.data.is_inline) return (MutableSubtree) {self.data}; + if (self.ptr->ref_count == 1) return ts_subtree_to_mut_unsafe(self); + + SubtreeHeapData *result = ts_subtree_pool_allocate(pool); + memcpy(result, self.ptr, sizeof(SubtreeHeapData)); + if (result->child_count > 0) { + result->children = ts_calloc(self.ptr->child_count, sizeof(Subtree)); + memcpy(result->children, self.ptr->children, result->child_count * sizeof(Subtree)); + for (uint32_t i = 0; i < result->child_count; i++) { + ts_subtree_retain(result->children[i]); + } + } else if (result->has_external_tokens) { + result->external_scanner_state = ts_external_scanner_state_copy(&self.ptr->external_scanner_state); + } + result->ref_count = 1; + ts_subtree_release(pool, self); + return (MutableSubtree) {.ptr = result}; +} + +static void ts_subtree__compress(MutableSubtree self, unsigned count, const TSLanguage *language, + MutableSubtreeArray *stack) { + unsigned initial_stack_size = stack->size; + + MutableSubtree tree = self; + TSSymbol symbol = tree.ptr->symbol; + for (unsigned i = 0; i < count; i++) { + if (tree.ptr->ref_count > 1 || tree.ptr->child_count < 2) break; + + MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]); + if ( + child.data.is_inline || + child.ptr->child_count < 2 || + child.ptr->ref_count > 1 || + child.ptr->symbol != symbol + ) break; + + MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[0]); + if ( + grandchild.data.is_inline || + grandchild.ptr->child_count < 2 || + grandchild.ptr->ref_count > 1 || + grandchild.ptr->symbol != symbol + ) break; + + tree.ptr->children[0] = ts_subtree_from_mut(grandchild); + child.ptr->children[0] = grandchild.ptr->children[grandchild.ptr->child_count - 1]; + grandchild.ptr->children[grandchild.ptr->child_count - 1] = ts_subtree_from_mut(child); + array_push(stack, tree); + tree = grandchild; + } + + while (stack->size > initial_stack_size) { + tree = array_pop(stack); + MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]); + MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[child.ptr->child_count - 1]); + ts_subtree_set_children(grandchild, grandchild.ptr->children, grandchild.ptr->child_count, language); + ts_subtree_set_children(child, child.ptr->children, child.ptr->child_count, language); + ts_subtree_set_children(tree, tree.ptr->children, tree.ptr->child_count, language); + } +} + +void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *language) { + array_clear(&pool->tree_stack); + + if (ts_subtree_child_count(self) > 0 && self.ptr->ref_count == 1) { + array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self)); + } + + while (pool->tree_stack.size > 0) { + MutableSubtree tree = array_pop(&pool->tree_stack); + + if (tree.ptr->repeat_depth > 0) { + Subtree child1 = tree.ptr->children[0]; + Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1]; + if ( + ts_subtree_child_count(child1) > 0 && + ts_subtree_child_count(child2) > 0 && + child1.ptr->repeat_depth > child2.ptr->repeat_depth + ) { + unsigned n = child1.ptr->repeat_depth - child2.ptr->repeat_depth; + for (unsigned i = n / 2; i > 0; i /= 2) { + ts_subtree__compress(tree, i, language, &pool->tree_stack); + n -= i; + } + } + } + + for (uint32_t i = 0; i < tree.ptr->child_count; i++) { + Subtree child = tree.ptr->children[i]; + if (ts_subtree_child_count(child) > 0 && child.ptr->ref_count == 1) { + array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child)); + } + } + } +} + +static inline uint32_t ts_subtree_repeat_depth(Subtree self) { + return ts_subtree_child_count(self) ? self.ptr->repeat_depth : 0; +} + +void ts_subtree_set_children( + MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language +) { + assert(!self.data.is_inline); + + if (self.ptr->child_count > 0 && children != self.ptr->children) { + ts_free(self.ptr->children); + } + + self.ptr->child_count = child_count; + self.ptr->children = children; + self.ptr->named_child_count = 0; + self.ptr->visible_child_count = 0; + self.ptr->error_cost = 0; + self.ptr->repeat_depth = 0; + self.ptr->node_count = 1; + self.ptr->has_external_tokens = false; + self.ptr->dynamic_precedence = 0; + + uint32_t non_extra_index = 0; + const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id); + uint32_t lookahead_end_byte = 0; + + for (uint32_t i = 0; i < self.ptr->child_count; i++) { + Subtree child = self.ptr->children[i]; + + if (i == 0) { + self.ptr->padding = ts_subtree_padding(child); + self.ptr->size = ts_subtree_size(child); + } else { + self.ptr->size = length_add(self.ptr->size, ts_subtree_total_size(child)); + } + + uint32_t child_lookahead_end_byte = + self.ptr->padding.bytes + + self.ptr->size.bytes + + ts_subtree_lookahead_bytes(child); + if (child_lookahead_end_byte > lookahead_end_byte) lookahead_end_byte = child_lookahead_end_byte; + + if (ts_subtree_symbol(child) != ts_builtin_sym_error_repeat) { + self.ptr->error_cost += ts_subtree_error_cost(child); + } + + self.ptr->dynamic_precedence += ts_subtree_dynamic_precedence(child); + self.ptr->node_count += ts_subtree_node_count(child); + + if (alias_sequence && alias_sequence[non_extra_index] != 0 && !ts_subtree_extra(child)) { + self.ptr->visible_child_count++; + if (ts_language_symbol_metadata(language, alias_sequence[non_extra_index]).named) { + self.ptr->named_child_count++; + } + } else if (ts_subtree_visible(child)) { + self.ptr->visible_child_count++; + if (ts_subtree_named(child)) self.ptr->named_child_count++; + } else if (ts_subtree_child_count(child) > 0) { + self.ptr->visible_child_count += child.ptr->visible_child_count; + self.ptr->named_child_count += child.ptr->named_child_count; + } + + if (ts_subtree_has_external_tokens(child)) self.ptr->has_external_tokens = true; + + if (ts_subtree_is_error(child)) { + self.ptr->fragile_left = self.ptr->fragile_right = true; + self.ptr->parse_state = TS_TREE_STATE_NONE; + } + + if (!ts_subtree_extra(child)) non_extra_index++; + } + + self.ptr->lookahead_bytes = lookahead_end_byte - self.ptr->size.bytes - self.ptr->padding.bytes; + + if (self.ptr->symbol == ts_builtin_sym_error || self.ptr->symbol == ts_builtin_sym_error_repeat) { + self.ptr->error_cost += + ERROR_COST_PER_RECOVERY + + ERROR_COST_PER_SKIPPED_CHAR * self.ptr->size.bytes + + ERROR_COST_PER_SKIPPED_LINE * self.ptr->size.extent.row; + for (uint32_t i = 0; i < self.ptr->child_count; i++) { + Subtree child = self.ptr->children[i]; + uint32_t grandchild_count = ts_subtree_child_count(child); + if (ts_subtree_extra(child)) continue; + if (ts_subtree_is_error(child) && grandchild_count == 0) continue; + if (ts_subtree_visible(child)) { + self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE; + } else if (grandchild_count > 0) { + self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE * child.ptr->visible_child_count; + } + } + } + + if (self.ptr->child_count > 0) { + Subtree first_child = self.ptr->children[0]; + Subtree last_child = self.ptr->children[self.ptr->child_count - 1]; + + self.ptr->first_leaf.symbol = ts_subtree_leaf_symbol(first_child); + self.ptr->first_leaf.parse_state = ts_subtree_leaf_parse_state(first_child); + + if (ts_subtree_fragile_left(first_child)) self.ptr->fragile_left = true; + if (ts_subtree_fragile_right(last_child)) self.ptr->fragile_right = true; + + if ( + self.ptr->child_count >= 2 && + !self.ptr->visible && + !self.ptr->named && + ts_subtree_symbol(first_child) == self.ptr->symbol + ) { + if (ts_subtree_repeat_depth(first_child) > ts_subtree_repeat_depth(last_child)) { + self.ptr->repeat_depth = ts_subtree_repeat_depth(first_child) + 1; + } else { + self.ptr->repeat_depth = ts_subtree_repeat_depth(last_child) + 1; + } + } + } +} + +MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol, + SubtreeArray *children, unsigned production_id, + const TSLanguage *language) { + TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); + bool fragile = symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat; + SubtreeHeapData *data = ts_subtree_pool_allocate(pool); + *data = (SubtreeHeapData) { + .ref_count = 1, + .symbol = symbol, + .production_id = production_id, + .visible = metadata.visible, + .named = metadata.named, + .has_changes = false, + .fragile_left = fragile, + .fragile_right = fragile, + .is_keyword = false, + .node_count = 0, + .first_leaf = {.symbol = 0, .parse_state = 0}, + }; + MutableSubtree result = {.ptr = data}; + ts_subtree_set_children(result, children->contents, children->size, language); + return result; +} + +Subtree ts_subtree_new_error_node(SubtreePool *pool, SubtreeArray *children, + bool extra, const TSLanguage *language) { + MutableSubtree result = ts_subtree_new_node( + pool, ts_builtin_sym_error, children, 0, language + ); + result.ptr->extra = extra; + return ts_subtree_from_mut(result); +} + +Subtree ts_subtree_new_missing_leaf(SubtreePool *pool, TSSymbol symbol, Length padding, + const TSLanguage *language) { + Subtree result = ts_subtree_new_leaf( + pool, symbol, padding, length_zero(), 0, + 0, false, false, language + ); + + if (result.data.is_inline) { + result.data.is_missing = true; + } else { + ((SubtreeHeapData *)result.ptr)->is_missing = true; + } + + return result; +} + +void ts_subtree_retain(Subtree self) { + if (self.data.is_inline) return; + assert(self.ptr->ref_count > 0); + atomic_inc((volatile uint32_t *)&self.ptr->ref_count); + assert(self.ptr->ref_count != 0); +} + +void ts_subtree_release(SubtreePool *pool, Subtree self) { + if (self.data.is_inline) return; + array_clear(&pool->tree_stack); + + assert(self.ptr->ref_count > 0); + if (atomic_dec((volatile uint32_t *)&self.ptr->ref_count) == 0) { + array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self)); + } + + while (pool->tree_stack.size > 0) { + MutableSubtree tree = array_pop(&pool->tree_stack); + if (tree.ptr->child_count > 0) { + for (uint32_t i = 0; i < tree.ptr->child_count; i++) { + Subtree child = tree.ptr->children[i]; + if (child.data.is_inline) continue; + assert(child.ptr->ref_count > 0); + if (atomic_dec((volatile uint32_t *)&child.ptr->ref_count) == 0) { + array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child)); + } + } + ts_free(tree.ptr->children); + } else if (tree.ptr->has_external_tokens) { + ts_external_scanner_state_delete(&tree.ptr->external_scanner_state); + } + ts_subtree_pool_free(pool, tree.ptr); + } +} + +bool ts_subtree_eq(Subtree self, Subtree other) { + if (self.data.is_inline || other.data.is_inline) { + return memcmp(&self, &other, sizeof(SubtreeInlineData)) == 0; + } + + if (self.ptr) { + if (!other.ptr) return false; + } else { + return !other.ptr; + } + + if (self.ptr->symbol != other.ptr->symbol) return false; + if (self.ptr->visible != other.ptr->visible) return false; + if (self.ptr->named != other.ptr->named) return false; + if (self.ptr->padding.bytes != other.ptr->padding.bytes) return false; + if (self.ptr->size.bytes != other.ptr->size.bytes) return false; + if (self.ptr->symbol == ts_builtin_sym_error) return self.ptr->lookahead_char == other.ptr->lookahead_char; + if (self.ptr->child_count != other.ptr->child_count) return false; + if (self.ptr->child_count > 0) { + if (self.ptr->visible_child_count != other.ptr->visible_child_count) return false; + if (self.ptr->named_child_count != other.ptr->named_child_count) return false; + + for (uint32_t i = 0; i < self.ptr->child_count; i++) { + if (!ts_subtree_eq(self.ptr->children[i], other.ptr->children[i])) { + return false; + } + } + } + return true; +} + +int ts_subtree_compare(Subtree left, Subtree right) { + if (ts_subtree_symbol(left) < ts_subtree_symbol(right)) return -1; + if (ts_subtree_symbol(right) < ts_subtree_symbol(left)) return 1; + if (ts_subtree_child_count(left) < ts_subtree_child_count(right)) return -1; + if (ts_subtree_child_count(right) < ts_subtree_child_count(left)) return 1; + for (uint32_t i = 0, n = ts_subtree_child_count(left); i < n; i++) { + Subtree left_child = left.ptr->children[i]; + Subtree right_child = right.ptr->children[i]; + switch (ts_subtree_compare(left_child, right_child)) { + case -1: return -1; + case 1: return 1; + default: break; + } + } + return 0; +} + +static inline void ts_subtree_set_has_changes(MutableSubtree *self) { + if (self->data.is_inline) { + self->data.has_changes = true; + } else { + self->ptr->has_changes = true; + } +} + +Subtree ts_subtree_edit(Subtree self, const TSInputEdit *edit, SubtreePool *pool) { + typedef struct { + Subtree *tree; + Edit edit; + } StackEntry; + + Array(StackEntry) stack = array_new(); + array_push(&stack, ((StackEntry) { + .tree = &self, + .edit = (Edit) { + .start = {edit->start_byte, edit->start_point}, + .old_end = {edit->old_end_byte, edit->old_end_point}, + .new_end = {edit->new_end_byte, edit->new_end_point}, + }, + })); + + while (stack.size) { + StackEntry entry = array_pop(&stack); + Edit edit = entry.edit; + bool is_noop = edit.old_end.bytes == edit.start.bytes && edit.new_end.bytes == edit.start.bytes; + bool is_pure_insertion = edit.old_end.bytes == edit.start.bytes; + + Length size = ts_subtree_size(*entry.tree); + Length padding = ts_subtree_padding(*entry.tree); + uint32_t lookahead_bytes = ts_subtree_lookahead_bytes(*entry.tree); + uint32_t end_byte = padding.bytes + size.bytes + lookahead_bytes; + if (edit.start.bytes > end_byte || (is_noop && edit.start.bytes == end_byte)) continue; + + // If the edit is entirely within the space before this subtree, then shift this + // subtree over according to the edit without changing its size. + if (edit.old_end.bytes <= padding.bytes) { + padding = length_add(edit.new_end, length_sub(padding, edit.old_end)); + } + + // If the edit starts in the space before this subtree and extends into this subtree, + // shrink the subtree's content to compensate for the change in the space before it. + else if (edit.start.bytes < padding.bytes) { + size = length_sub(size, length_sub(edit.old_end, padding)); + padding = edit.new_end; + } + + // If the edit is a pure insertion right at the start of the subtree, + // shift the subtree over according to the insertion. + else if (edit.start.bytes == padding.bytes && is_pure_insertion) { + padding = edit.new_end; + } + + // If the edit is within this subtree, resize the subtree to reflect the edit. + else { + uint32_t total_bytes = padding.bytes + size.bytes; + if (edit.start.bytes < total_bytes || + (edit.start.bytes == total_bytes && is_pure_insertion)) { + size = length_add( + length_sub(edit.new_end, padding), + length_sub(size, length_sub(edit.old_end, padding)) + ); + } + } + + MutableSubtree result = ts_subtree_make_mut(pool, *entry.tree); + + if (result.data.is_inline) { + if (ts_subtree_can_inline(padding, size, lookahead_bytes)) { + result.data.padding_bytes = padding.bytes; + result.data.padding_rows = padding.extent.row; + result.data.padding_columns = padding.extent.column; + result.data.size_bytes = size.bytes; + } else { + SubtreeHeapData *data = ts_subtree_pool_allocate(pool); + data->ref_count = 1; + data->padding = padding; + data->size = size; + data->lookahead_bytes = lookahead_bytes; + data->error_cost = 0; + data->child_count = 0; + data->symbol = result.data.symbol; + data->parse_state = result.data.parse_state; + data->visible = result.data.visible; + data->named = result.data.named; + data->extra = result.data.extra; + data->fragile_left = false; + data->fragile_right = false; + data->has_changes = false; + data->has_external_tokens = false; + data->is_missing = result.data.is_missing; + data->is_keyword = result.data.is_keyword; + result.ptr = data; + } + } else { + result.ptr->padding = padding; + result.ptr->size = size; + } + + ts_subtree_set_has_changes(&result); + *entry.tree = ts_subtree_from_mut(result); + + Length child_left, child_right = length_zero(); + for (uint32_t i = 0, n = ts_subtree_child_count(*entry.tree); i < n; i++) { + Subtree *child = &result.ptr->children[i]; + Length child_size = ts_subtree_total_size(*child); + child_left = child_right; + child_right = length_add(child_left, child_size); + + // If this child ends before the edit, it is not affected. + if (child_right.bytes + ts_subtree_lookahead_bytes(*child) < edit.start.bytes) continue; + + // If this child starts after the edit, then we're done processing children. + if (child_left.bytes > edit.old_end.bytes || + (child_left.bytes == edit.old_end.bytes && child_size.bytes > 0 && i > 0)) break; + + // Transform edit into the child's coordinate space. + Edit child_edit = { + .start = length_sub(edit.start, child_left), + .old_end = length_sub(edit.old_end, child_left), + .new_end = length_sub(edit.new_end, child_left), + }; + + // Clamp child_edit to the child's bounds. + if (edit.start.bytes < child_left.bytes) child_edit.start = length_zero(); + if (edit.old_end.bytes < child_left.bytes) child_edit.old_end = length_zero(); + if (edit.new_end.bytes < child_left.bytes) child_edit.new_end = length_zero(); + if (edit.old_end.bytes > child_right.bytes) child_edit.old_end = child_size; + + // Interpret all inserted text as applying to the *first* child that touches the edit. + // Subsequent children are only never have any text inserted into them; they are only + // shrunk to compensate for the edit. + if (child_right.bytes > edit.start.bytes || + (child_right.bytes == edit.start.bytes && is_pure_insertion)) { + edit.new_end = edit.start; + } + + // Children that occur before the edit are not reshaped by the edit. + else { + child_edit.old_end = child_edit.start; + child_edit.new_end = child_edit.start; + } + + // Queue processing of this child's subtree. + array_push(&stack, ((StackEntry) { + .tree = child, + .edit = child_edit, + })); + } + } + + array_delete(&stack); + return self; +} + +Subtree ts_subtree_last_external_token(Subtree tree) { + if (!ts_subtree_has_external_tokens(tree)) return NULL_SUBTREE; + while (tree.ptr->child_count > 0) { + for (uint32_t i = tree.ptr->child_count - 1; i + 1 > 0; i--) { + Subtree child = tree.ptr->children[i]; + if (ts_subtree_has_external_tokens(child)) { + tree = child; + break; + } + } + } + return tree; +} + +static size_t ts_subtree__write_char_to_string(char *s, size_t n, int32_t c) { + if (c == 0) + return snprintf(s, n, "EOF"); + if (c == -1) + return snprintf(s, n, "INVALID"); + else if (c == '\n') + return snprintf(s, n, "'\\n'"); + else if (c == '\t') + return snprintf(s, n, "'\\t'"); + else if (c == '\r') + return snprintf(s, n, "'\\r'"); + else if (0 < c && c < 128 && isprint(c)) + return snprintf(s, n, "'%c'", c); + else + return snprintf(s, n, "%d", c); +} + +static void ts_subtree__write_dot_string(FILE *f, const char *string) { + for (const char *c = string; *c; c++) { + if (*c == '"') { + fputs("\\\"", f); + } else if (*c == '\n') { + fputs("\\n", f); + } else { + fputc(*c, f); + } + } +} + +static const char *ROOT_FIELD = "__ROOT__"; + +static size_t ts_subtree__write_to_string( + Subtree self, char *string, size_t limit, + const TSLanguage *language, bool include_all, + TSSymbol alias_symbol, bool alias_is_named, const char *field_name +) { + if (!self.ptr) return snprintf(string, limit, "(NULL)"); + + char *cursor = string; + char **writer = (limit > 0) ? &cursor : &string; + bool is_root = field_name == ROOT_FIELD; + bool is_visible = + include_all || + ts_subtree_missing(self) || + ( + alias_symbol + ? alias_is_named + : ts_subtree_visible(self) && ts_subtree_named(self) + ); + + if (is_visible) { + if (!is_root) { + cursor += snprintf(*writer, limit, " "); + if (field_name) { + cursor += snprintf(*writer, limit, "%s: ", field_name); + } + } + + if (ts_subtree_is_error(self) && ts_subtree_child_count(self) == 0 && self.ptr->size.bytes > 0) { + cursor += snprintf(*writer, limit, "(UNEXPECTED "); + cursor += ts_subtree__write_char_to_string(*writer, limit, self.ptr->lookahead_char); + } else { + TSSymbol symbol = alias_symbol ? alias_symbol : ts_subtree_symbol(self); + const char *symbol_name = ts_language_symbol_name(language, symbol); + if (ts_subtree_missing(self)) { + cursor += snprintf(*writer, limit, "(MISSING "); + if (alias_is_named || ts_subtree_named(self)) { + cursor += snprintf(*writer, limit, "%s", symbol_name); + } else { + cursor += snprintf(*writer, limit, "\"%s\"", symbol_name); + } + } else { + cursor += snprintf(*writer, limit, "(%s", symbol_name); + } + } + } else if (is_root) { + TSSymbol symbol = ts_subtree_symbol(self); + const char *symbol_name = ts_language_symbol_name(language, symbol); + cursor += snprintf(*writer, limit, "(\"%s\")", symbol_name); + } + + if (ts_subtree_child_count(self)) { + const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id); + const TSFieldMapEntry *field_map, *field_map_end; + ts_language_field_map( + language, + self.ptr->production_id, + &field_map, + &field_map_end + ); + + uint32_t structural_child_index = 0; + for (uint32_t i = 0; i < self.ptr->child_count; i++) { + Subtree child = self.ptr->children[i]; + if (ts_subtree_extra(child)) { + cursor += ts_subtree__write_to_string( + child, *writer, limit, + language, include_all, + 0, false, NULL + ); + } else { + TSSymbol alias_symbol = alias_sequence + ? alias_sequence[structural_child_index] + : 0; + bool alias_is_named = alias_symbol + ? ts_language_symbol_metadata(language, alias_symbol).named + : false; + + const char *child_field_name = is_visible ? NULL : field_name; + for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { + if (!i->inherited && i->child_index == structural_child_index) { + child_field_name = language->field_names[i->field_id]; + break; + } + } + + cursor += ts_subtree__write_to_string( + child, *writer, limit, + language, include_all, + alias_symbol, alias_is_named, child_field_name + ); + structural_child_index++; + } + } + } + + if (is_visible) cursor += snprintf(*writer, limit, ")"); + + return cursor - string; +} + +char *ts_subtree_string( + Subtree self, + const TSLanguage *language, + bool include_all +) { + char scratch_string[1]; + size_t size = ts_subtree__write_to_string( + self, scratch_string, 0, + language, include_all, + 0, false, ROOT_FIELD + ) + 1; + char *result = malloc(size * sizeof(char)); + ts_subtree__write_to_string( + self, result, size, + language, include_all, + 0, false, ROOT_FIELD + ); + return result; +} + +void ts_subtree__print_dot_graph(const Subtree *self, uint32_t start_offset, + const TSLanguage *language, TSSymbol alias_symbol, + FILE *f) { + TSSymbol subtree_symbol = ts_subtree_symbol(*self); + TSSymbol symbol = alias_symbol ? alias_symbol : subtree_symbol; + uint32_t end_offset = start_offset + ts_subtree_total_bytes(*self); + fprintf(f, "tree_%p [label=\"", self); + ts_subtree__write_dot_string(f, ts_language_symbol_name(language, symbol)); + fprintf(f, "\""); + + if (ts_subtree_child_count(*self) == 0) fprintf(f, ", shape=plaintext"); + if (ts_subtree_extra(*self)) fprintf(f, ", fontcolor=gray"); + + fprintf(f, ", tooltip=\"" + "range: %u - %u\n" + "state: %d\n" + "error-cost: %u\n" + "has-changes: %u\n" + "repeat-depth: %u\n" + "lookahead-bytes: %u", + start_offset, end_offset, + ts_subtree_parse_state(*self), + ts_subtree_error_cost(*self), + ts_subtree_has_changes(*self), + ts_subtree_repeat_depth(*self), + ts_subtree_lookahead_bytes(*self) + ); + + if (ts_subtree_is_error(*self) && ts_subtree_child_count(*self) == 0) { + fprintf(f, "\ncharacter: '%c'", self->ptr->lookahead_char); + } + + fprintf(f, "\"]\n"); + + uint32_t child_start_offset = start_offset; + uint32_t child_info_offset = + language->max_alias_sequence_length * + ts_subtree_production_id(*self); + for (uint32_t i = 0, n = ts_subtree_child_count(*self); i < n; i++) { + const Subtree *child = &self->ptr->children[i]; + TSSymbol alias_symbol = 0; + if (!ts_subtree_extra(*child) && child_info_offset) { + alias_symbol = language->alias_sequences[child_info_offset]; + child_info_offset++; + } + ts_subtree__print_dot_graph(child, child_start_offset, language, alias_symbol, f); + fprintf(f, "tree_%p -> tree_%p [tooltip=%u]\n", self, child, i); + child_start_offset += ts_subtree_total_bytes(*child); + } +} + +void ts_subtree_print_dot_graph(Subtree self, const TSLanguage *language, FILE *f) { + fprintf(f, "digraph tree {\n"); + fprintf(f, "edge [arrowhead=none]\n"); + ts_subtree__print_dot_graph(&self, 0, language, 0, f); + fprintf(f, "}\n"); +} + +bool ts_subtree_external_scanner_state_eq(Subtree self, Subtree other) { + const ExternalScannerState *state1 = &empty_state; + const ExternalScannerState *state2 = &empty_state; + if (self.ptr && ts_subtree_has_external_tokens(self) && !self.ptr->child_count) { + state1 = &self.ptr->external_scanner_state; + } + if (other.ptr && ts_subtree_has_external_tokens(other) && !other.ptr->child_count) { + state2 = &other.ptr->external_scanner_state; + } + return ts_external_scanner_state_eq(state1, state2); +} diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h new file mode 100644 index 0000000000..79ccd92390 --- /dev/null +++ b/src/tree_sitter/subtree.h @@ -0,0 +1,281 @@ +#ifndef TREE_SITTER_SUBTREE_H_ +#define TREE_SITTER_SUBTREE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include "./length.h" +#include "./array.h" +#include "./error_costs.h" +#include "tree_sitter/api.h" +#include "tree_sitter/parser.h" + +static const TSStateId TS_TREE_STATE_NONE = USHRT_MAX; +#define NULL_SUBTREE ((Subtree) {.ptr = NULL}) + +typedef union Subtree Subtree; +typedef union MutableSubtree MutableSubtree; + +typedef struct { + union { + char *long_data; + char short_data[24]; + }; + uint32_t length; +} ExternalScannerState; + +typedef struct { + bool is_inline : 1; + bool visible : 1; + bool named : 1; + bool extra : 1; + bool has_changes : 1; + bool is_missing : 1; + bool is_keyword : 1; + uint8_t symbol; + uint8_t padding_bytes; + uint8_t size_bytes; + uint8_t padding_columns; + uint8_t padding_rows : 4; + uint8_t lookahead_bytes : 4; + uint16_t parse_state; +} SubtreeInlineData; + +typedef struct { + volatile uint32_t ref_count; + Length padding; + Length size; + uint32_t lookahead_bytes; + uint32_t error_cost; + uint32_t child_count; + TSSymbol symbol; + TSStateId parse_state; + + bool visible : 1; + bool named : 1; + bool extra : 1; + bool fragile_left : 1; + bool fragile_right : 1; + bool has_changes : 1; + bool has_external_tokens : 1; + bool is_missing : 1; + bool is_keyword : 1; + + union { + // Non-terminal subtrees (`child_count > 0`) + struct { + Subtree *children; + uint32_t visible_child_count; + uint32_t named_child_count; + uint32_t node_count; + uint32_t repeat_depth; + int32_t dynamic_precedence; + uint16_t production_id; + struct { + TSSymbol symbol; + TSStateId parse_state; + } first_leaf; + }; + + // External terminal subtrees (`child_count == 0 && has_external_tokens`) + ExternalScannerState external_scanner_state; + + // Error terminal subtrees (`child_count == 0 && symbol == ts_builtin_sym_error`) + int32_t lookahead_char; + }; +} SubtreeHeapData; + +union Subtree { + SubtreeInlineData data; + const SubtreeHeapData *ptr; +}; + +union MutableSubtree { + SubtreeInlineData data; + SubtreeHeapData *ptr; +}; + +typedef Array(Subtree) SubtreeArray; +typedef Array(MutableSubtree) MutableSubtreeArray; + +typedef struct { + MutableSubtreeArray free_trees; + MutableSubtreeArray tree_stack; +} SubtreePool; + +void ts_external_scanner_state_init(ExternalScannerState *, const char *, unsigned); +const char *ts_external_scanner_state_data(const ExternalScannerState *); + +void ts_subtree_array_copy(SubtreeArray, SubtreeArray *); +void ts_subtree_array_delete(SubtreePool *, SubtreeArray *); +SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *); +void ts_subtree_array_reverse(SubtreeArray *); + +SubtreePool ts_subtree_pool_new(uint32_t capacity); +void ts_subtree_pool_delete(SubtreePool *); + +Subtree ts_subtree_new_leaf( + SubtreePool *, TSSymbol, Length, Length, uint32_t, + TSStateId, bool, bool, const TSLanguage * +); +Subtree ts_subtree_new_error( + SubtreePool *, int32_t, Length, Length, uint32_t, TSStateId, const TSLanguage * +); +MutableSubtree ts_subtree_new_node(SubtreePool *, TSSymbol, SubtreeArray *, unsigned, const TSLanguage *); +Subtree ts_subtree_new_error_node(SubtreePool *, SubtreeArray *, bool, const TSLanguage *); +Subtree ts_subtree_new_missing_leaf(SubtreePool *, TSSymbol, Length, const TSLanguage *); +MutableSubtree ts_subtree_make_mut(SubtreePool *, Subtree); +void ts_subtree_retain(Subtree); +void ts_subtree_release(SubtreePool *, Subtree); +bool ts_subtree_eq(Subtree, Subtree); +int ts_subtree_compare(Subtree, Subtree); +void ts_subtree_set_symbol(MutableSubtree *, TSSymbol, const TSLanguage *); +void ts_subtree_set_children(MutableSubtree, Subtree *, uint32_t, const TSLanguage *); +void ts_subtree_balance(Subtree, SubtreePool *, const TSLanguage *); +Subtree ts_subtree_edit(Subtree, const TSInputEdit *edit, SubtreePool *); +char *ts_subtree_string(Subtree, const TSLanguage *, bool include_all); +void ts_subtree_print_dot_graph(Subtree, const TSLanguage *, FILE *); +Subtree ts_subtree_last_external_token(Subtree); +bool ts_subtree_external_scanner_state_eq(Subtree, Subtree); + +#define SUBTREE_GET(self, name) (self.data.is_inline ? self.data.name : self.ptr->name) + +static inline TSSymbol ts_subtree_symbol(Subtree self) { return SUBTREE_GET(self, symbol); } +static inline bool ts_subtree_visible(Subtree self) { return SUBTREE_GET(self, visible); } +static inline bool ts_subtree_named(Subtree self) { return SUBTREE_GET(self, named); } +static inline bool ts_subtree_extra(Subtree self) { return SUBTREE_GET(self, extra); } +static inline bool ts_subtree_has_changes(Subtree self) { return SUBTREE_GET(self, has_changes); } +static inline bool ts_subtree_missing(Subtree self) { return SUBTREE_GET(self, is_missing); } +static inline bool ts_subtree_is_keyword(Subtree self) { return SUBTREE_GET(self, is_keyword); } +static inline TSStateId ts_subtree_parse_state(Subtree self) { return SUBTREE_GET(self, parse_state); } +static inline uint32_t ts_subtree_lookahead_bytes(Subtree self) { return SUBTREE_GET(self, lookahead_bytes); } + +#undef SUBTREE_GET + +static inline void ts_subtree_set_extra(MutableSubtree *self) { + if (self->data.is_inline) { + self->data.extra = true; + } else { + self->ptr->extra = true; + } +} + +static inline TSSymbol ts_subtree_leaf_symbol(Subtree self) { + if (self.data.is_inline) return self.data.symbol; + if (self.ptr->child_count == 0) return self.ptr->symbol; + return self.ptr->first_leaf.symbol; +} + +static inline TSStateId ts_subtree_leaf_parse_state(Subtree self) { + if (self.data.is_inline) return self.data.parse_state; + if (self.ptr->child_count == 0) return self.ptr->parse_state; + return self.ptr->first_leaf.parse_state; +} + +static inline Length ts_subtree_padding(Subtree self) { + if (self.data.is_inline) { + Length result = {self.data.padding_bytes, {self.data.padding_rows, self.data.padding_columns}}; + return result; + } else { + return self.ptr->padding; + } +} + +static inline Length ts_subtree_size(Subtree self) { + if (self.data.is_inline) { + Length result = {self.data.size_bytes, {0, self.data.size_bytes}}; + return result; + } else { + return self.ptr->size; + } +} + +static inline Length ts_subtree_total_size(Subtree self) { + return length_add(ts_subtree_padding(self), ts_subtree_size(self)); +} + +static inline uint32_t ts_subtree_total_bytes(Subtree self) { + return ts_subtree_total_size(self).bytes; +} + +static inline uint32_t ts_subtree_child_count(Subtree self) { + return self.data.is_inline ? 0 : self.ptr->child_count; +} + +static inline uint32_t ts_subtree_node_count(Subtree self) { + return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count; +} + +static inline uint32_t ts_subtree_visible_child_count(Subtree self) { + if (ts_subtree_child_count(self) > 0) { + return self.ptr->visible_child_count; + } else { + return 0; + } +} + +static inline uint32_t ts_subtree_error_cost(Subtree self) { + if (ts_subtree_missing(self)) { + return ERROR_COST_PER_MISSING_TREE + ERROR_COST_PER_RECOVERY; + } else { + return self.data.is_inline ? 0 : self.ptr->error_cost; + } +} + +static inline int32_t ts_subtree_dynamic_precedence(Subtree self) { + return (self.data.is_inline || self.ptr->child_count == 0) ? 0 : self.ptr->dynamic_precedence; +} + +static inline uint16_t ts_subtree_production_id(Subtree self) { + if (ts_subtree_child_count(self) > 0) { + return self.ptr->production_id; + } else { + return 0; + } +} + +static inline bool ts_subtree_fragile_left(Subtree self) { + return self.data.is_inline ? false : self.ptr->fragile_left; +} + +static inline bool ts_subtree_fragile_right(Subtree self) { + return self.data.is_inline ? false : self.ptr->fragile_right; +} + +static inline bool ts_subtree_has_external_tokens(Subtree self) { + return self.data.is_inline ? false : self.ptr->has_external_tokens; +} + +static inline bool ts_subtree_is_fragile(Subtree self) { + return self.data.is_inline ? false : (self.ptr->fragile_left || self.ptr->fragile_right); +} + +static inline bool ts_subtree_is_error(Subtree self) { + return ts_subtree_symbol(self) == ts_builtin_sym_error; +} + +static inline bool ts_subtree_is_eof(Subtree self) { + return ts_subtree_symbol(self) == ts_builtin_sym_end; +} + +static inline Subtree ts_subtree_from_mut(MutableSubtree self) { + Subtree result; + result.data = self.data; + return result; +} + +static inline MutableSubtree ts_subtree_to_mut_unsafe(Subtree self) { + MutableSubtree result; + result.data = self.data; + return result; +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_SUBTREE_H_ diff --git a/src/tree_sitter/tree.c b/src/tree_sitter/tree.c new file mode 100644 index 0000000000..04cb1d242f --- /dev/null +++ b/src/tree_sitter/tree.c @@ -0,0 +1,149 @@ +#include "tree_sitter/api.h" +#include "./array.h" +#include "./get_changed_ranges.h" +#include "./subtree.h" +#include "./tree_cursor.h" +#include "./tree.h" + +static const unsigned PARENT_CACHE_CAPACITY = 32; + +TSTree *ts_tree_new( + Subtree root, const TSLanguage *language, + const TSRange *included_ranges, unsigned included_range_count +) { + TSTree *result = ts_malloc(sizeof(TSTree)); + result->root = root; + result->language = language; + result->parent_cache = NULL; + result->parent_cache_start = 0; + result->parent_cache_size = 0; + result->included_ranges = ts_calloc(included_range_count, sizeof(TSRange)); + memcpy(result->included_ranges, included_ranges, included_range_count * sizeof(TSRange)); + result->included_range_count = included_range_count; + return result; +} + +TSTree *ts_tree_copy(const TSTree *self) { + ts_subtree_retain(self->root); + return ts_tree_new(self->root, self->language, self->included_ranges, self->included_range_count); +} + +void ts_tree_delete(TSTree *self) { + if (!self) return; + + SubtreePool pool = ts_subtree_pool_new(0); + ts_subtree_release(&pool, self->root); + ts_subtree_pool_delete(&pool); + ts_free(self->included_ranges); + if (self->parent_cache) ts_free(self->parent_cache); + ts_free(self); +} + +TSNode ts_tree_root_node(const TSTree *self) { + return ts_node_new(self, &self->root, ts_subtree_padding(self->root), 0); +} + +const TSLanguage *ts_tree_language(const TSTree *self) { + return self->language; +} + +void ts_tree_edit(TSTree *self, const TSInputEdit *edit) { + for (unsigned i = 0; i < self->included_range_count; i++) { + TSRange *range = &self->included_ranges[i]; + if (range->end_byte >= edit->old_end_byte) { + if (range->end_byte != UINT32_MAX) { + range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte); + range->end_point = point_add( + edit->new_end_point, + point_sub(range->end_point, edit->old_end_point) + ); + if (range->end_byte < edit->new_end_byte) { + range->end_byte = UINT32_MAX; + range->end_point = POINT_MAX; + } + } + if (range->start_byte >= edit->old_end_byte) { + range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte); + range->start_point = point_add( + edit->new_end_point, + point_sub(range->start_point, edit->old_end_point) + ); + if (range->start_byte < edit->new_end_byte) { + range->start_byte = UINT32_MAX; + range->start_point = POINT_MAX; + } + } + } + } + + SubtreePool pool = ts_subtree_pool_new(0); + self->root = ts_subtree_edit(self->root, edit, &pool); + self->parent_cache_start = 0; + self->parent_cache_size = 0; + ts_subtree_pool_delete(&pool); +} + +TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uint32_t *count) { + TSRange *result; + TreeCursor cursor1 = {NULL, array_new()}; + TreeCursor cursor2 = {NULL, array_new()}; + TSNode root = ts_tree_root_node(self); + ts_tree_cursor_init(&cursor1, root); + ts_tree_cursor_init(&cursor2, root); + + TSRangeArray included_range_differences = array_new(); + ts_range_array_get_changed_ranges( + self->included_ranges, self->included_range_count, + other->included_ranges, other->included_range_count, + &included_range_differences + ); + + *count = ts_subtree_get_changed_ranges( + &self->root, &other->root, &cursor1, &cursor2, + self->language, &included_range_differences, &result + ); + + array_delete(&included_range_differences); + array_delete(&cursor1.stack); + array_delete(&cursor2.stack); + return result; +} + +void ts_tree_print_dot_graph(const TSTree *self, FILE *file) { + ts_subtree_print_dot_graph(self->root, self->language, file); +} + +TSNode ts_tree_get_cached_parent(const TSTree *self, const TSNode *node) { + for (uint32_t i = 0; i < self->parent_cache_size; i++) { + uint32_t index = (self->parent_cache_start + i) % PARENT_CACHE_CAPACITY; + ParentCacheEntry *entry = &self->parent_cache[index]; + if (entry->child == node->id) { + return ts_node_new(self, entry->parent, entry->position, entry->alias_symbol); + } + } + return ts_node_new(NULL, NULL, length_zero(), 0); +} + +void ts_tree_set_cached_parent(const TSTree *_self, const TSNode *node, const TSNode *parent) { + TSTree *self = (TSTree *)_self; + if (!self->parent_cache) { + self->parent_cache = ts_calloc(PARENT_CACHE_CAPACITY, sizeof(ParentCacheEntry)); + } + + uint32_t index = (self->parent_cache_start + self->parent_cache_size) % PARENT_CACHE_CAPACITY; + self->parent_cache[index] = (ParentCacheEntry) { + .child = node->id, + .parent = (const Subtree *)parent->id, + .position = { + parent->context[0], + {parent->context[1], parent->context[2]} + }, + .alias_symbol = parent->context[3], + }; + + if (self->parent_cache_size == PARENT_CACHE_CAPACITY) { + self->parent_cache_start++; + } else { + self->parent_cache_size++; + } +} diff --git a/src/tree_sitter/tree.h b/src/tree_sitter/tree.h new file mode 100644 index 0000000000..92a7e64179 --- /dev/null +++ b/src/tree_sitter/tree.h @@ -0,0 +1,34 @@ +#ifndef TREE_SITTER_TREE_H_ +#define TREE_SITTER_TREE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const Subtree *child; + const Subtree *parent; + Length position; + TSSymbol alias_symbol; +} ParentCacheEntry; + +struct TSTree { + Subtree root; + const TSLanguage *language; + ParentCacheEntry *parent_cache; + uint32_t parent_cache_start; + uint32_t parent_cache_size; + TSRange *included_ranges; + unsigned included_range_count; +}; + +TSTree *ts_tree_new(Subtree root, const TSLanguage *language, const TSRange *, unsigned); +TSNode ts_node_new(const TSTree *, const Subtree *, Length, TSSymbol); +TSNode ts_tree_get_cached_parent(const TSTree *, const TSNode *); +void ts_tree_set_cached_parent(const TSTree *, const TSNode *, const TSNode *); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_TREE_H_ diff --git a/src/tree_sitter/tree_cursor.c b/src/tree_sitter/tree_cursor.c new file mode 100644 index 0000000000..7103fc411d --- /dev/null +++ b/src/tree_sitter/tree_cursor.c @@ -0,0 +1,302 @@ +#include "tree_sitter/api.h" +#include "./alloc.h" +#include "./tree_cursor.h" +#include "./language.h" +#include "./tree.h" + +typedef struct { + Subtree parent; + const TSTree *tree; + Length position; + uint32_t child_index; + uint32_t structural_child_index; + const TSSymbol *alias_sequence; +} CursorChildIterator; + +// CursorChildIterator + +static inline CursorChildIterator ts_tree_cursor_iterate_children(const TreeCursor *self) { + TreeCursorEntry *last_entry = array_back(&self->stack); + if (ts_subtree_child_count(*last_entry->subtree) == 0) { + return (CursorChildIterator) {NULL_SUBTREE, self->tree, length_zero(), 0, 0, NULL}; + } + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->tree->language, + last_entry->subtree->ptr->production_id + ); + return (CursorChildIterator) { + .tree = self->tree, + .parent = *last_entry->subtree, + .position = last_entry->position, + .child_index = 0, + .structural_child_index = 0, + .alias_sequence = alias_sequence, + }; +} + +static inline bool ts_tree_cursor_child_iterator_next(CursorChildIterator *self, + TreeCursorEntry *result, + bool *visible) { + if (!self->parent.ptr || self->child_index == self->parent.ptr->child_count) return false; + const Subtree *child = &self->parent.ptr->children[self->child_index]; + *result = (TreeCursorEntry) { + .subtree = child, + .position = self->position, + .child_index = self->child_index, + .structural_child_index = self->structural_child_index, + }; + *visible = ts_subtree_visible(*child); + bool extra = ts_subtree_extra(*child); + if (!extra && self->alias_sequence) { + *visible |= self->alias_sequence[self->structural_child_index]; + self->structural_child_index++; + } + + self->position = length_add(self->position, ts_subtree_size(*child)); + self->child_index++; + + if (self->child_index < self->parent.ptr->child_count) { + Subtree next_child = self->parent.ptr->children[self->child_index]; + self->position = length_add(self->position, ts_subtree_padding(next_child)); + } + + return true; +} + +// TSTreeCursor - lifecycle + +TSTreeCursor ts_tree_cursor_new(TSNode node) { + TSTreeCursor self = {NULL, NULL, {0, 0}}; + ts_tree_cursor_init((TreeCursor *)&self, node); + return self; +} + +void ts_tree_cursor_reset(TSTreeCursor *_self, TSNode node) { + ts_tree_cursor_init((TreeCursor *)_self, node); +} + +void ts_tree_cursor_init(TreeCursor *self, TSNode node) { + self->tree = node.tree; + array_clear(&self->stack); + array_push(&self->stack, ((TreeCursorEntry) { + .subtree = (const Subtree *)node.id, + .position = { + ts_node_start_byte(node), + ts_node_start_point(node) + }, + .child_index = 0, + .structural_child_index = 0, + })); +} + +void ts_tree_cursor_delete(TSTreeCursor *_self) { + TreeCursor *self = (TreeCursor *)_self; + array_delete(&self->stack); +} + +// TSTreeCursor - walking the tree + +bool ts_tree_cursor_goto_first_child(TSTreeCursor *_self) { + TreeCursor *self = (TreeCursor *)_self; + + bool did_descend; + do { + did_descend = false; + + bool visible; + TreeCursorEntry entry; + CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); + while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { + if (visible) { + array_push(&self->stack, entry); + return true; + } + + if (ts_subtree_visible_child_count(*entry.subtree) > 0) { + array_push(&self->stack, entry); + did_descend = true; + break; + } + } + } while (did_descend); + + return false; +} + +int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *_self, uint32_t goal_byte) { + TreeCursor *self = (TreeCursor *)_self; + uint32_t initial_size = self->stack.size; + uint32_t visible_child_index = 0; + + bool did_descend; + do { + did_descend = false; + + bool visible; + TreeCursorEntry entry; + CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); + while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { + uint32_t end_byte = entry.position.bytes + ts_subtree_size(*entry.subtree).bytes; + bool at_goal = end_byte > goal_byte; + uint32_t visible_child_count = ts_subtree_visible_child_count(*entry.subtree); + + if (at_goal) { + if (visible) { + array_push(&self->stack, entry); + return visible_child_index; + } + + if (visible_child_count > 0) { + array_push(&self->stack, entry); + did_descend = true; + break; + } + } else if (visible) { + visible_child_index++; + } else { + visible_child_index += visible_child_count; + } + } + } while (did_descend); + + if (self->stack.size > initial_size && + ts_tree_cursor_goto_next_sibling((TSTreeCursor *)self)) { + return visible_child_index; + } + + self->stack.size = initial_size; + return -1; +} + +bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *_self) { + TreeCursor *self = (TreeCursor *)_self; + uint32_t initial_size = self->stack.size; + + while (self->stack.size > 1) { + TreeCursorEntry entry = array_pop(&self->stack); + CursorChildIterator iterator = ts_tree_cursor_iterate_children(self); + iterator.child_index = entry.child_index; + iterator.structural_child_index = entry.structural_child_index; + iterator.position = entry.position; + + bool visible = false; + ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible); + if (visible && self->stack.size + 1 < initial_size) break; + + while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) { + if (visible) { + array_push(&self->stack, entry); + return true; + } + + if (ts_subtree_visible_child_count(*entry.subtree)) { + array_push(&self->stack, entry); + ts_tree_cursor_goto_first_child(_self); + return true; + } + } + } + + self->stack.size = initial_size; + return false; +} + +bool ts_tree_cursor_goto_parent(TSTreeCursor *_self) { + TreeCursor *self = (TreeCursor *)_self; + for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) { + TreeCursorEntry *entry = &self->stack.contents[i]; + bool is_aliased = false; + if (i > 0) { + TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->tree->language, + parent_entry->subtree->ptr->production_id + ); + is_aliased = alias_sequence && alias_sequence[entry->structural_child_index]; + } + if (ts_subtree_visible(*entry->subtree) || is_aliased) { + self->stack.size = i + 1; + return true; + } + } + return false; +} + +TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) { + const TreeCursor *self = (const TreeCursor *)_self; + TreeCursorEntry *last_entry = array_back(&self->stack); + TSSymbol alias_symbol = 0; + if (self->stack.size > 1) { + TreeCursorEntry *parent_entry = &self->stack.contents[self->stack.size - 2]; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->tree->language, + parent_entry->subtree->ptr->production_id + ); + if (alias_sequence && !ts_subtree_extra(*last_entry->subtree)) { + alias_symbol = alias_sequence[last_entry->structural_child_index]; + } + } + return ts_node_new( + self->tree, + last_entry->subtree, + last_entry->position, + alias_symbol + ); +} + +TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) { + const TreeCursor *self = (const TreeCursor *)_self; + + // Walk up the tree, visiting the current node and its invisible ancestors. + for (unsigned i = self->stack.size - 1; i > 0; i--) { + TreeCursorEntry *entry = &self->stack.contents[i]; + TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; + + // Stop walking up when another visible node is found. + if (i != self->stack.size - 1) { + if (ts_subtree_visible(*entry->subtree)) break; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->tree->language, + parent_entry->subtree->ptr->production_id + ); + if (alias_sequence && alias_sequence[entry->structural_child_index]) { + break; + } + } + + const TSFieldMapEntry *field_map, *field_map_end; + ts_language_field_map( + self->tree->language, + parent_entry->subtree->ptr->production_id, + &field_map, &field_map_end + ); + + while (field_map < field_map_end) { + if ( + !field_map->inherited && + field_map->child_index == entry->structural_child_index + ) return field_map->field_id; + field_map++; + } + } + return 0; +} + +const char *ts_tree_cursor_current_field_name(const TSTreeCursor *_self) { + TSFieldId id = ts_tree_cursor_current_field_id(_self); + if (id) { + const TreeCursor *self = (const TreeCursor *)_self; + return self->tree->language->field_names[id]; + } else { + return NULL; + } +} + +TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *_cursor) { + const TreeCursor *cursor = (const TreeCursor *)_cursor; + TSTreeCursor res = {NULL, NULL, {0, 0}}; + TreeCursor *copy = (TreeCursor *)&res; + copy->tree = cursor->tree; + array_push_all(©->stack, &cursor->stack); + return res; +} diff --git a/src/tree_sitter/tree_cursor.h b/src/tree_sitter/tree_cursor.h new file mode 100644 index 0000000000..55bdad86da --- /dev/null +++ b/src/tree_sitter/tree_cursor.h @@ -0,0 +1,20 @@ +#ifndef TREE_SITTER_TREE_CURSOR_H_ +#define TREE_SITTER_TREE_CURSOR_H_ + +#include "./subtree.h" + +typedef struct { + const Subtree *subtree; + Length position; + uint32_t child_index; + uint32_t structural_child_index; +} TreeCursorEntry; + +typedef struct { + const TSTree *tree; + Array(TreeCursorEntry) stack; +} TreeCursor; + +void ts_tree_cursor_init(TreeCursor *, TSNode); + +#endif // TREE_SITTER_TREE_CURSOR_H_ diff --git a/src/tree_sitter/utf16.c b/src/tree_sitter/utf16.c new file mode 100644 index 0000000000..3956c01cb9 --- /dev/null +++ b/src/tree_sitter/utf16.c @@ -0,0 +1,33 @@ +#include "./utf16.h" + +utf8proc_ssize_t utf16_iterate( + const utf8proc_uint8_t *string, + utf8proc_ssize_t length, + utf8proc_int32_t *code_point +) { + if (length < 2) { + *code_point = -1; + return 0; + } + + uint16_t *units = (uint16_t *)string; + uint16_t unit = units[0]; + + if (unit < 0xd800 || unit >= 0xe000) { + *code_point = unit; + return 2; + } + + if (unit < 0xdc00) { + if (length >= 4) { + uint16_t next_unit = units[1]; + if (next_unit >= 0xdc00 && next_unit < 0xe000) { + *code_point = 0x10000 + ((unit - 0xd800) << 10) + (next_unit - 0xdc00); + return 4; + } + } + } + + *code_point = -1; + return 2; +} diff --git a/src/tree_sitter/utf16.h b/src/tree_sitter/utf16.h new file mode 100644 index 0000000000..32fd05e6db --- /dev/null +++ b/src/tree_sitter/utf16.h @@ -0,0 +1,21 @@ +#ifndef TREE_SITTER_UTF16_H_ +#define TREE_SITTER_UTF16_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "utf8proc.h" + +// Analogous to utf8proc's utf8proc_iterate function. Reads one code point from +// the given UTF16 string and stores it in the location pointed to by `code_point`. +// Returns the number of bytes in `string` that were read. +utf8proc_ssize_t utf16_iterate(const utf8proc_uint8_t *, utf8proc_ssize_t, utf8proc_int32_t *); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_UTF16_H_ -- cgit From 8ff2f193bb3ed94ee215c83c13431d45d382949b Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 7 Jun 2019 14:05:26 +0200 Subject: tree-sitter: change vendored tree-sitter to use nvim memory management --- src/tree_sitter/alloc.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h index c8fe6c6e6d..2229995bd1 100644 --- a/src/tree_sitter/alloc.h +++ b/src/tree_sitter/alloc.h @@ -9,7 +9,20 @@ extern "C" { #include #include -#if defined(TREE_SITTER_TEST) +#include "nvim/memory.h" + +#if 1 + +static inline bool ts_toggle_allocation_recording(bool value) { + return false; +} + +#define ts_malloc xmalloc +#define ts_calloc xcalloc +#define ts_realloc xrealloc +#define ts_free xfree + +#elif defined(TREE_SITTER_TEST) void *ts_record_malloc(size_t); void *ts_record_calloc(size_t, size_t); -- cgit From cd100963866b2c33a286cbf6aac8e42cd16fd248 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 29 Oct 2018 19:11:41 +0100 Subject: tree-sitter: initial tree-sitter support --- src/nvim/CMakeLists.txt | 15 +- src/nvim/lua/executor.c | 42 ++++ src/nvim/lua/tree_sitter.c | 472 +++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/tree_sitter.h | 10 + 4 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 src/nvim/lua/tree_sitter.c create mode 100644 src/nvim/lua/tree_sitter.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index aa8100873b..ebc7e96d66 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -74,6 +74,8 @@ include_directories(${GENERATED_DIR}) include_directories(${CACHED_GENERATED_DIR}) include_directories(${GENERATED_INCLUDES_DIR}) +include_directories(${LIBUTF8PROC_INCLUDE_DIRS}) + file(MAKE_DIRECTORY ${TOUCHES_DIR}) file(MAKE_DIRECTORY ${GENERATED_DIR}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) @@ -85,6 +87,10 @@ file(GLOB NVIM_HEADERS *.h) file(GLOB XDIFF_SOURCES xdiff/*.c) file(GLOB XDIFF_HEADERS xdiff/*.h) +# when LIBUTF8PROC build is fixed, don't use lib.c with amalgamated utf8proc.c +#file(GLOB TS_SOURCES tree_sitter/*.c) +file(GLOB TS_SOURCES ../tree_sitter/lib.c) + foreach(subdir os api @@ -141,6 +147,7 @@ set(CONV_SOURCES ex_cmds.c ex_docmd.c fileio.c + lua/tree_sitter.c mbyte.c memline.c message.c @@ -158,7 +165,7 @@ foreach(sfile ${CONV_SOURCES}) endif() endforeach() # xdiff: inlined external project, we don't maintain it. #9306 -list(APPEND CONV_SOURCES ${XDIFF_SOURCES}) +list(APPEND CONV_SOURCES ${XDIFF_SOURCES} ${TS_SOURCES}) if(NOT MSVC) set_source_files_properties( @@ -414,7 +421,7 @@ endif() add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS}) + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TS_SOURCES}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) @@ -500,7 +507,7 @@ add_library( EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TS_SOURCES} ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -525,7 +532,7 @@ else() EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TS_SOURCES} ${UNIT_TEST_FIXTURES} ) target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f51aa3c6d4..8b0140f794 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -31,6 +31,7 @@ #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" +#include "nvim/lua/tree_sitter.h" #include "luv/luv.h" @@ -310,7 +311,11 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "luv"); lua_pop(lstate, 3); + // internal vim._treesitter... API + nlua_add_treesitter(lstate); + lua_setglobal(lstate, "vim"); + return 0; } @@ -816,3 +821,40 @@ void ex_luafile(exarg_T *const eap) return; } } + +static int unsafe_ptr_to_ts_tree(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + TSTree *const *ptr = lua_topointer(L,1); + tslua_push_tree(L, *ptr); + return 1; +} + +static int create_tslua_parser(lua_State *L) +{ + TSLanguage *tree_sitter_c(void), *tree_sitter_javascript(void); + + if (!lua_gettop(L)) { + return 0; + } + char *str = lua_tostring(L,1); + + TSLanguage *lang = tree_sitter_c(); + if (str && striequal(str, "javascript")) { + lang = tree_sitter_javascript(); + } + tslua_push_parser(L, lang); + return 1; +} + +static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + tslua_init(lstate); + lua_pushcfunction(lstate, unsafe_ptr_to_ts_tree); + lua_setfield(lstate, -2, "unsafe_ts_tree"); + + lua_pushcfunction(lstate, create_tslua_parser); + lua_setfield(lstate, -2, "ts_parser"); +} diff --git a/src/nvim/lua/tree_sitter.c b/src/nvim/lua/tree_sitter.c new file mode 100644 index 0000000000..1ecb2bcac4 --- /dev/null +++ b/src/nvim/lua/tree_sitter.c @@ -0,0 +1,472 @@ +// 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 + +// lua bindings for tree-siter. +// NB: this file should contain a generic lua interface for +// tree-sitter trees and nodes, and could be broken out as a reusable library + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tree_sitter/api.h" + +// NOT state-safe, delete when GC is confimed working: +static int debug_n_trees = 0, debug_n_cursors = 0; + +#define REG_KEY "tree_sitter-private" + +#include "nvim/lua/tree_sitter.h" +#include "nvim/api/private/handle.h" +#include "nvim/memline.h" + +typedef struct { + TSParser *parser; + TSTree *tree; +} Tslua_parser; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/tree_sitter.c.generated.h" +#endif + +static struct luaL_Reg parser_meta[] = { + {"__gc", parser_gc}, + {"__tostring", parser_tostring}, + {"parse_buf", parser_parse_buf}, + {"edit", parser_edit}, + {"tree", parser_tree}, + {NULL, NULL} +}; + +static struct luaL_Reg tree_meta[] = { + {"__gc", tree_gc}, + {"__tostring", tree_tostring}, + {"root", tree_root}, + {NULL, NULL} +}; + +static struct luaL_Reg node_meta[] = { + {"__tostring", node_tostring}, + {"__len", node_child_count}, + {"range", node_range}, + {"start", node_start}, + {"type", node_type}, + {"symbol", node_symbol}, + {"child_count", node_child_count}, + {"child", node_child}, + {"descendant_for_point_range", node_descendant_for_point_range}, + {"parent", node_parent}, + {NULL, NULL} +}; + +void build_meta(lua_State *L, const luaL_Reg *meta) +{ + // [env, target] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [env, target, func] + lua_pushvalue(L, -3); // [env, target, func, env] + lua_setfenv(L, -2); // [env, target, func] + lua_setfield(L, -2, meta[i].name); // [env, target] + } + + lua_pushvalue(L, -1); // [env, target, target] + lua_setfield(L, -2, "__index"); // [env, target] +} + + + +/// init the tslua library +/// +/// all global state is stored in the regirstry of the lua_State +void tslua_init(lua_State *L) +{ + lua_createtable(L, 0, 0); + + // type metatables + lua_createtable(L, 0, 0); + build_meta(L, parser_meta); + lua_setfield(L, -2, "parser-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, tree_meta); + lua_setfield(L, -2, "tree-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, node_meta); + lua_setfield(L, -2, "node-meta"); + + lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + + lua_pushcfunction(L, tslua_debug); + lua_setglobal(L, "_tslua_debug"); +} + +static int tslua_debug(lua_State *L) +{ + lua_pushinteger(L, debug_n_trees); + lua_pushinteger(L, debug_n_cursors); + return 2; +} + +void tslua_push_parser(lua_State *L, TSLanguage *lang) +{ + TSParser *parser = ts_parser_new(); + ts_parser_set_language(parser, lang); + Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + p->parser = parser; + p->tree = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] +} + +static Tslua_parser *parser_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + return lua_touserdata(L, 1); +} + +static int parser_gc(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + ts_parser_delete(p->parser); + if (p->tree) { + ts_tree_delete(p->tree); + } + + return 0; +} + +static int parser_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +{ + buf_T *bp = payload; + static char buf[200]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column,200); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +} + +static int parser_parse_buf(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + long bufnr = lua_tointeger(L, 2); + void *payload = handle_get_buffer(bufnr); + TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + if (p->tree) { + ts_tree_delete(p->tree); + } + p->tree = new_tree; + + tslua_push_tree(L, ts_tree_copy(p->tree)); + return 1; +} + +static int parser_tree(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (p->tree) { + tslua_push_tree(L, ts_tree_copy(p->tree)); + } else { + lua_pushnil(L); + } + return 1; +} + +static int parser_edit(lua_State *L) +{ + if(lua_gettop(L) < 10) { + lua_pushstring(L, "not enough args to parser:edit()"); + lua_error(L); + return 0; // unreachable + } + + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (!p->tree) { + return 0; + } + + long start_byte = lua_tointeger(L, 2); + long old_end_byte = lua_tointeger(L, 3); + long new_end_byte = lua_tointeger(L, 4); + TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; + TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; + TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; + + TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, + start_point, old_end_point, new_end_point }; + + ts_tree_edit(p->tree, &edit); + + return 0; +} + + +// Tree methods + +/// push tree interface on lua stack. +/// +/// This takes "ownership" of the tree and will free it +/// when the wrapper object is garbage collected +void tslua_push_tree(lua_State *L, TSTree *tree) +{ + TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] + *ud = tree; + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + + // table used for node wrappers to keep a reference to tree wrapper + // NB: in lua 5.3 the uservalue for the node could just be the tree, but + // in lua 5.1 the uservalue (fenv) must be a table. + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] + debug_n_trees++; +} + +static TSTree *tree_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSTree **ud = lua_touserdata(L, 1); + return *ud; +} + +static int tree_gc(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + + ts_tree_delete(tree); + debug_n_trees--; + return 0; +} + +static int tree_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int tree_root(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + TSNode root = ts_tree_root_node(tree); + push_node(L, root); + return 1; +} + +// Node methods + +/// push node interface on lua stack +/// +/// top of stack must either be the tree this node belongs to or another node +/// of the same tree! This value is not popped. Can only be called inside a +/// cfunction with the tslua environment. +static void push_node(lua_State *L, TSNode node) +{ + if (ts_node_is_null(node)) { + lua_pushnil(L); // [src, nil] + return; + } + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + *ud = node; + lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_setmetatable(L, -2); // [src, udata] + lua_getfenv(L, -2); // [src, udata, reftable] + lua_setfenv(L, -2); // [src, udata] +} + +static bool node_check(lua_State *L, TSNode *res) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSNode *ud = lua_touserdata(L, 1); + *res = *ud; + return true; +} + + +static int node_tostring(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ""); + lua_concat(L, 3); + return 1; +} + +static int node_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + TSPoint end = ts_node_end_point(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + return 4; +} + +static int node_start(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + uint32_t start_byte = ts_node_start_byte(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, start_byte); + return 3; +} + +static int node_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_child_count(node); + lua_pushnumber(L, count); + return 1; +} + +static int node_type(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ts_node_type(node)); + return 1; +} + +static int node_symbol(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSSymbol symbol = ts_node_symbol(node); + lua_pushnumber(L, symbol); + return 1; +} + +static int node_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = {(uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3)}; + TSPoint end = {(uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5)}; + TSNode child = ts_node_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_parent(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSNode parent = ts_node_parent(node); + push_node(L, parent); + return 1; +} + diff --git a/src/nvim/lua/tree_sitter.h b/src/nvim/lua/tree_sitter.h new file mode 100644 index 0000000000..2ae0ec8371 --- /dev/null +++ b/src/nvim/lua/tree_sitter.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LUA_TREE_SITTER_H +#define NVIM_LUA_TREE_SITTER_H + +#include "tree_sitter/api.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/tree_sitter.h.generated.h" +#endif + +#endif // NVIM_LUA_TREE_SITTER_H -- cgit From 0e0beef85e4d3932e0d49528d8474794f7b69b01 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 6 Jun 2019 12:20:07 +0200 Subject: tree-sitter: load parsers as .so files --- src/nvim/lua/executor.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 8b0140f794..1794cee8d8 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -834,16 +834,31 @@ static int unsafe_ptr_to_ts_tree(lua_State *L) static int create_tslua_parser(lua_State *L) { - TSLanguage *tree_sitter_c(void), *tree_sitter_javascript(void); - - if (!lua_gettop(L)) { + if (lua_gettop(L) < 2) { return 0; } - char *str = lua_tostring(L,1); + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "uv_dlsym: %s", uv_dlerror(&lib)); + } - TSLanguage *lang = tree_sitter_c(); - if (str && striequal(str, "javascript")) { - lang = tree_sitter_javascript(); + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "failed to load parser"); } tslua_push_parser(L, lang); return 1; -- cgit From b871100be7a6ae5ce1db23a688c593ecaa2390b8 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 6 Jun 2019 14:13:10 +0200 Subject: Create BuildUtf8proc.cmake and FindUtf8proc.cmake Using advanced search and replace technology --- src/nvim/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index ebc7e96d66..53a4089e10 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -74,8 +74,6 @@ include_directories(${GENERATED_DIR}) include_directories(${CACHED_GENERATED_DIR}) include_directories(${GENERATED_INCLUDES_DIR}) -include_directories(${LIBUTF8PROC_INCLUDE_DIRS}) - file(MAKE_DIRECTORY ${TOUCHES_DIR}) file(MAKE_DIRECTORY ${GENERATED_DIR}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) @@ -88,8 +86,9 @@ file(GLOB XDIFF_SOURCES xdiff/*.c) file(GLOB XDIFF_HEADERS xdiff/*.h) # when LIBUTF8PROC build is fixed, don't use lib.c with amalgamated utf8proc.c -#file(GLOB TS_SOURCES tree_sitter/*.c) -file(GLOB TS_SOURCES ../tree_sitter/lib.c) +file(GLOB TS_SOURCES ../tree_sitter/*.c) +file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c) +list(REMOVE_ITEM TS_SOURCES ${TS_SOURCE_AMALGAM}) foreach(subdir os @@ -402,6 +401,7 @@ list(APPEND NVIM_LINK_LIBRARIES ${LIBVTERM_LIBRARIES} ${LIBTERMKEY_LIBRARIES} ${UNIBILIUM_LIBRARIES} + ${UTF8PROC_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ) -- cgit From c07e1e8696826579f017c9b401fe264d70384ceb Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 7 Jun 2019 13:15:23 +0200 Subject: tree-sitter: cleanup build code --- src/nvim/CMakeLists.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 53a4089e10..3056c108b4 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -85,10 +85,9 @@ file(GLOB NVIM_HEADERS *.h) file(GLOB XDIFF_SOURCES xdiff/*.c) file(GLOB XDIFF_HEADERS xdiff/*.h) -# when LIBUTF8PROC build is fixed, don't use lib.c with amalgamated utf8proc.c -file(GLOB TS_SOURCES ../tree_sitter/*.c) +file(GLOB TREESITTER_SOURCES ../tree_sitter/*.c) file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c) -list(REMOVE_ITEM TS_SOURCES ${TS_SOURCE_AMALGAM}) +list(REMOVE_ITEM TREESITTER_SOURCES ${TS_SOURCE_AMALGAM}) foreach(subdir os @@ -164,7 +163,7 @@ foreach(sfile ${CONV_SOURCES}) endif() endforeach() # xdiff: inlined external project, we don't maintain it. #9306 -list(APPEND CONV_SOURCES ${XDIFF_SOURCES} ${TS_SOURCES}) +list(APPEND CONV_SOURCES ${XDIFF_SOURCES}) if(NOT MSVC) set_source_files_properties( @@ -178,6 +177,9 @@ if(NOT MSVC) set_source_files_properties( eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") endif() + + # tree-sitter: inlined external project, we don't maintain it. #10124 + set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable") endif() if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$") @@ -421,7 +423,7 @@ endif() add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TS_SOURCES}) + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) @@ -507,7 +509,7 @@ add_library( EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TS_SOURCES} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES} ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -532,7 +534,7 @@ else() EXCLUDE_FROM_ALL ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TS_SOURCES} + ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES} ${UNIT_TEST_FIXTURES} ) target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) -- cgit From 1e9e2451bef21ff705e677802d1b0980356f1f86 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 7 Jun 2019 18:19:59 +0200 Subject: tree-sitter: objectify API --- src/nvim/lua/executor.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1794cee8d8..a6447ebb2b 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -822,16 +822,6 @@ void ex_luafile(exarg_T *const eap) } } -static int unsafe_ptr_to_ts_tree(lua_State *L) -{ - if (!lua_gettop(L)) { - return 0; - } - TSTree *const *ptr = lua_topointer(L,1); - tslua_push_tree(L, *ptr); - return 1; -} - static int create_tslua_parser(lua_State *L) { if (lua_gettop(L) < 2) { @@ -867,9 +857,7 @@ static int create_tslua_parser(lua_State *L) static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); - lua_pushcfunction(lstate, unsafe_ptr_to_ts_tree); - lua_setfield(lstate, -2, "unsafe_ts_tree"); lua_pushcfunction(lstate, create_tslua_parser); - lua_setfield(lstate, -2, "ts_parser"); + lua_setfield(lstate, -2, "_create_ts_parser"); } -- cgit From afba23099fccc929fd0319a9a965a7b727407c7a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 8 Jun 2019 15:51:38 +0200 Subject: tree-sitter: support pre-registration of languages --- src/nvim/lua/executor.c | 32 ++++++--------------------- src/nvim/lua/tree_sitter.c | 54 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index a6447ebb2b..ae53bfce6a 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -824,34 +824,13 @@ void ex_luafile(exarg_T *const eap) static int create_tslua_parser(lua_State *L) { - if (lua_gettop(L) < 2) { - return 0; + if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { + return luaL_error(L, "string expected"); } - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); + const char *lang_name = lua_tostring(L,1); - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. - uv_lib_t lib; - if (uv_dlopen(path, &lib)) { - return luaL_error(L, "uv_dlopen: %s", uv_dlerror(&lib)); - } - - TSLanguage *(*lang_parser)(void); - if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "uv_dlsym: %s", uv_dlerror(&lib)); - } - - TSLanguage *lang = lang_parser(); - if (lang == NULL) { - return luaL_error(L, "failed to load parser"); - } - tslua_push_parser(L, lang); - return 1; + return tslua_push_parser(L, lang_name); } static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL @@ -860,4 +839,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); + + lua_pushcfunction(lstate, ts_lua_register_lang); + lua_setfield(lstate, -2, "ts_add_language"); } diff --git a/src/nvim/lua/tree_sitter.c b/src/nvim/lua/tree_sitter.c index 1ecb2bcac4..f992639955 100644 --- a/src/nvim/lua/tree_sitter.c +++ b/src/nvim/lua/tree_sitter.c @@ -65,6 +65,8 @@ static struct luaL_Reg node_meta[] = { {NULL, NULL} }; +PMap(cstr_t) *langs; + void build_meta(lua_State *L, const luaL_Reg *meta) { // [env, target] @@ -86,6 +88,9 @@ void build_meta(lua_State *L, const luaL_Reg *meta) /// all global state is stored in the regirstry of the lua_State void tslua_init(lua_State *L) { + + langs = pmap_new(cstr_t)(); + lua_createtable(L, 0, 0); // type metatables @@ -114,9 +119,55 @@ static int tslua_debug(lua_State *L) return 2; } -void tslua_push_parser(lua_State *L, TSLanguage *lang) + +int ts_lua_register_lang(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "string expected"); + } + + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + if (pmap_has(cstr_t)(langs, lang_name)) { + return 0; + } + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + } + + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "Failed to load parser: internal error"); + } + + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); + + lua_pushboolean(L, true); + return 1; +} + +int tslua_push_parser(lua_State *L, const char *lang_name) { TSParser *parser = ts_parser_new(); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + ts_parser_set_language(parser, lang); Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] p->parser = parser; @@ -126,6 +177,7 @@ void tslua_push_parser(lua_State *L, TSLanguage *lang) lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] lua_setmetatable(L, -3); // [udata, env] lua_pop(L, 1); // [udata] + return 1; } static Tslua_parser *parser_check(lua_State *L) -- cgit From 4ea5e63aa8c866b4fcc9d10f1a26078d2517f96a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 9 Jun 2019 13:26:48 +0200 Subject: tree-sitter: add basic testing on ci build tree-sitter c parser on ci for testing purposes --- src/nvim/lua/executor.c | 2 +- src/nvim/lua/vim.lua | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index ae53bfce6a..23c4fcabbc 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -841,5 +841,5 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "_create_ts_parser"); lua_pushcfunction(lstate, ts_lua_register_lang); - lua_setfield(lstate, -2, "ts_add_language"); + lua_setfield(lstate, -2, "_ts_add_language"); } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b1a684b977..c38926fe24 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -232,6 +232,9 @@ local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') return t.inspect + elseif key == 'tree_sitter' then + t.tree_sitter = require('vim.tree_sitter') + return t.tree_sitter elseif require('vim.shared')[key] ~= nil then -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] -- cgit From c8f861b739b4703b1198dc1f88b09edbeb0d9f2e Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 12:10:12 +0200 Subject: tree-sitter: rename tree_sitter => treesitter for consistency --- src/nvim/CMakeLists.txt | 2 +- src/nvim/lua/executor.c | 2 +- src/nvim/lua/tree_sitter.c | 524 --------------------------------------------- src/nvim/lua/tree_sitter.h | 10 - src/nvim/lua/treesitter.c | 524 +++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/treesitter.h | 10 + src/nvim/lua/vim.lua | 6 +- 7 files changed, 539 insertions(+), 539 deletions(-) delete mode 100644 src/nvim/lua/tree_sitter.c delete mode 100644 src/nvim/lua/tree_sitter.h create mode 100644 src/nvim/lua/treesitter.c create mode 100644 src/nvim/lua/treesitter.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 3056c108b4..27977e3a40 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -145,7 +145,7 @@ set(CONV_SOURCES ex_cmds.c ex_docmd.c fileio.c - lua/tree_sitter.c + lua/treesitter.c mbyte.c memline.c message.c diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 23c4fcabbc..8a35f11c71 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -31,7 +31,7 @@ #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" -#include "nvim/lua/tree_sitter.h" +#include "nvim/lua/treesitter.h" #include "luv/luv.h" diff --git a/src/nvim/lua/tree_sitter.c b/src/nvim/lua/tree_sitter.c deleted file mode 100644 index f992639955..0000000000 --- a/src/nvim/lua/tree_sitter.c +++ /dev/null @@ -1,524 +0,0 @@ -// 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 - -// lua bindings for tree-siter. -// NB: this file should contain a generic lua interface for -// tree-sitter trees and nodes, and could be broken out as a reusable library - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "tree_sitter/api.h" - -// NOT state-safe, delete when GC is confimed working: -static int debug_n_trees = 0, debug_n_cursors = 0; - -#define REG_KEY "tree_sitter-private" - -#include "nvim/lua/tree_sitter.h" -#include "nvim/api/private/handle.h" -#include "nvim/memline.h" - -typedef struct { - TSParser *parser; - TSTree *tree; -} Tslua_parser; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/tree_sitter.c.generated.h" -#endif - -static struct luaL_Reg parser_meta[] = { - {"__gc", parser_gc}, - {"__tostring", parser_tostring}, - {"parse_buf", parser_parse_buf}, - {"edit", parser_edit}, - {"tree", parser_tree}, - {NULL, NULL} -}; - -static struct luaL_Reg tree_meta[] = { - {"__gc", tree_gc}, - {"__tostring", tree_tostring}, - {"root", tree_root}, - {NULL, NULL} -}; - -static struct luaL_Reg node_meta[] = { - {"__tostring", node_tostring}, - {"__len", node_child_count}, - {"range", node_range}, - {"start", node_start}, - {"type", node_type}, - {"symbol", node_symbol}, - {"child_count", node_child_count}, - {"child", node_child}, - {"descendant_for_point_range", node_descendant_for_point_range}, - {"parent", node_parent}, - {NULL, NULL} -}; - -PMap(cstr_t) *langs; - -void build_meta(lua_State *L, const luaL_Reg *meta) -{ - // [env, target] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [env, target, func] - lua_pushvalue(L, -3); // [env, target, func, env] - lua_setfenv(L, -2); // [env, target, func] - lua_setfield(L, -2, meta[i].name); // [env, target] - } - - lua_pushvalue(L, -1); // [env, target, target] - lua_setfield(L, -2, "__index"); // [env, target] -} - - - -/// init the tslua library -/// -/// all global state is stored in the regirstry of the lua_State -void tslua_init(lua_State *L) -{ - - langs = pmap_new(cstr_t)(); - - lua_createtable(L, 0, 0); - - // type metatables - lua_createtable(L, 0, 0); - build_meta(L, parser_meta); - lua_setfield(L, -2, "parser-meta"); - - lua_createtable(L, 0, 0); - build_meta(L, tree_meta); - lua_setfield(L, -2, "tree-meta"); - - lua_createtable(L, 0, 0); - build_meta(L, node_meta); - lua_setfield(L, -2, "node-meta"); - - lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); - - lua_pushcfunction(L, tslua_debug); - lua_setglobal(L, "_tslua_debug"); -} - -static int tslua_debug(lua_State *L) -{ - lua_pushinteger(L, debug_n_trees); - lua_pushinteger(L, debug_n_cursors); - return 2; -} - - -int ts_lua_register_lang(lua_State *L) -{ - if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { - return luaL_error(L, "string expected"); - } - - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); - - if (pmap_has(cstr_t)(langs, lang_name)) { - return 0; - } - - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); - - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. - uv_lib_t lib; - if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); - } - - TSLanguage *(*lang_parser)(void); - if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); - } - - TSLanguage *lang = lang_parser(); - if (lang == NULL) { - return luaL_error(L, "Failed to load parser: internal error"); - } - - pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); - - lua_pushboolean(L, true); - return 1; -} - -int tslua_push_parser(lua_State *L, const char *lang_name) -{ - TSParser *parser = ts_parser_new(); - TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); - if (!lang) { - return luaL_error(L, "no such language: %s", lang_name); - } - - ts_parser_set_language(parser, lang); - Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] - p->parser = parser; - p->tree = NULL; - - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] - return 1; -} - -static Tslua_parser *parser_check(lua_State *L) -{ - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - return lua_touserdata(L, 1); -} - -static int parser_gc(lua_State *L) -{ - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - ts_parser_delete(p->parser); - if (p->tree) { - ts_tree_delete(p->tree); - } - - return 0; -} - -static int parser_tostring(lua_State *L) -{ - lua_pushstring(L, ""); - return 1; -} - -static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) -{ - buf_T *bp = payload; - static char buf[200]; - if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { - *bytes_read = 0; - return ""; - } - char_u *line = ml_get_buf(bp, position.row+1, false); - size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column,200); - - // TODO: translate embedded \n to \000 - memcpy(buf, line+position.column, tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { - buf[tocopy] = '\n'; - (*bytes_read)++; - } - return buf; -} - -static int parser_parse_buf(lua_State *L) -{ - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - long bufnr = lua_tointeger(L, 2); - void *payload = handle_get_buffer(bufnr); - TSInput input = {payload, input_cb, TSInputEncodingUTF8}; - TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); - if (p->tree) { - ts_tree_delete(p->tree); - } - p->tree = new_tree; - - tslua_push_tree(L, ts_tree_copy(p->tree)); - return 1; -} - -static int parser_tree(lua_State *L) -{ - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (p->tree) { - tslua_push_tree(L, ts_tree_copy(p->tree)); - } else { - lua_pushnil(L); - } - return 1; -} - -static int parser_edit(lua_State *L) -{ - if(lua_gettop(L) < 10) { - lua_pushstring(L, "not enough args to parser:edit()"); - lua_error(L); - return 0; // unreachable - } - - Tslua_parser *p = parser_check(L); - if (!p) { - return 0; - } - - if (!p->tree) { - return 0; - } - - long start_byte = lua_tointeger(L, 2); - long old_end_byte = lua_tointeger(L, 3); - long new_end_byte = lua_tointeger(L, 4); - TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; - TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; - TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; - - TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, - start_point, old_end_point, new_end_point }; - - ts_tree_edit(p->tree, &edit); - - return 0; -} - - -// Tree methods - -/// push tree interface on lua stack. -/// -/// This takes "ownership" of the tree and will free it -/// when the wrapper object is garbage collected -void tslua_push_tree(lua_State *L, TSTree *tree) -{ - TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = tree; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] - - // table used for node wrappers to keep a reference to tree wrapper - // NB: in lua 5.3 the uservalue for the node could just be the tree, but - // in lua 5.1 the uservalue (fenv) must be a table. - lua_createtable(L, 1, 0); // [udata, reftable] - lua_pushvalue(L, -2); // [udata, reftable, udata] - lua_rawseti(L, -2, 1); // [udata, reftable] - lua_setfenv(L, -2); // [udata] - debug_n_trees++; -} - -static TSTree *tree_check(lua_State *L) -{ - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSTree **ud = lua_touserdata(L, 1); - return *ud; -} - -static int tree_gc(lua_State *L) -{ - TSTree *tree = tree_check(L); - if (!tree) { - return 0; - } - - ts_tree_delete(tree); - debug_n_trees--; - return 0; -} - -static int tree_tostring(lua_State *L) -{ - lua_pushstring(L, ""); - return 1; -} - -static int tree_root(lua_State *L) -{ - TSTree *tree = tree_check(L); - if (!tree) { - return 0; - } - TSNode root = ts_tree_root_node(tree); - push_node(L, root); - return 1; -} - -// Node methods - -/// push node interface on lua stack -/// -/// top of stack must either be the tree this node belongs to or another node -/// of the same tree! This value is not popped. Can only be called inside a -/// cfunction with the tslua environment. -static void push_node(lua_State *L, TSNode node) -{ - if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] - return; - } - TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] - *ud = node; - lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] - lua_setmetatable(L, -2); // [src, udata] - lua_getfenv(L, -2); // [src, udata, reftable] - lua_setfenv(L, -2); // [src, udata] -} - -static bool node_check(lua_State *L, TSNode *res) -{ - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSNode *ud = lua_touserdata(L, 1); - *res = *ud; - return true; -} - - -static int node_tostring(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - lua_pushstring(L, ""); - lua_concat(L, 3); - return 1; -} - -static int node_range(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSPoint start = ts_node_start_point(node); - TSPoint end = ts_node_end_point(node); - lua_pushnumber(L, start.row); - lua_pushnumber(L, start.column); - lua_pushnumber(L, end.row); - lua_pushnumber(L, end.column); - return 4; -} - -static int node_start(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSPoint start = ts_node_start_point(node); - uint32_t start_byte = ts_node_start_byte(node); - lua_pushnumber(L, start.row); - lua_pushnumber(L, start.column); - lua_pushnumber(L, start_byte); - return 3; -} - -static int node_child_count(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - uint32_t count = ts_node_child_count(node); - lua_pushnumber(L, count); - return 1; -} - -static int node_type(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - lua_pushstring(L, ts_node_type(node)); - return 1; -} - -static int node_symbol(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSSymbol symbol = ts_node_symbol(node); - lua_pushnumber(L, symbol); - return 1; -} - -static int node_child(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - long num = lua_tointeger(L, 2); - TSNode child = ts_node_child(node, (uint32_t)num); - - lua_pushvalue(L, 1); - push_node(L, child); - return 1; -} - -static int node_descendant_for_point_range(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSPoint start = {(uint32_t)lua_tointeger(L, 2), - (uint32_t)lua_tointeger(L, 3)}; - TSPoint end = {(uint32_t)lua_tointeger(L, 4), - (uint32_t)lua_tointeger(L, 5)}; - TSNode child = ts_node_descendant_for_point_range(node, start, end); - - lua_pushvalue(L, 1); - push_node(L, child); - return 1; -} - -static int node_parent(lua_State *L) -{ - TSNode node; - if (!node_check(L, &node)) { - return 0; - } - TSNode parent = ts_node_parent(node); - push_node(L, parent); - return 1; -} - diff --git a/src/nvim/lua/tree_sitter.h b/src/nvim/lua/tree_sitter.h deleted file mode 100644 index 2ae0ec8371..0000000000 --- a/src/nvim/lua/tree_sitter.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_LUA_TREE_SITTER_H -#define NVIM_LUA_TREE_SITTER_H - -#include "tree_sitter/api.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/tree_sitter.h.generated.h" -#endif - -#endif // NVIM_LUA_TREE_SITTER_H diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c new file mode 100644 index 0000000000..794bdc6749 --- /dev/null +++ b/src/nvim/lua/treesitter.c @@ -0,0 +1,524 @@ +// 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 + +// lua bindings for tree-siter. +// NB: this file should contain a generic lua interface for +// tree-sitter trees and nodes, and could be broken out as a reusable library + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "tree_sitter/api.h" + +// NOT state-safe, delete when GC is confimed working: +static int debug_n_trees = 0, debug_n_cursors = 0; + +#define REG_KEY "treesitter-private" + +#include "nvim/lua/treesitter.h" +#include "nvim/api/private/handle.h" +#include "nvim/memline.h" + +typedef struct { + TSParser *parser; + TSTree *tree; +} Tslua_parser; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/treesitter.c.generated.h" +#endif + +static struct luaL_Reg parser_meta[] = { + {"__gc", parser_gc}, + {"__tostring", parser_tostring}, + {"parse_buf", parser_parse_buf}, + {"edit", parser_edit}, + {"tree", parser_tree}, + {NULL, NULL} +}; + +static struct luaL_Reg tree_meta[] = { + {"__gc", tree_gc}, + {"__tostring", tree_tostring}, + {"root", tree_root}, + {NULL, NULL} +}; + +static struct luaL_Reg node_meta[] = { + {"__tostring", node_tostring}, + {"__len", node_child_count}, + {"range", node_range}, + {"start", node_start}, + {"type", node_type}, + {"symbol", node_symbol}, + {"child_count", node_child_count}, + {"child", node_child}, + {"descendant_for_point_range", node_descendant_for_point_range}, + {"parent", node_parent}, + {NULL, NULL} +}; + +PMap(cstr_t) *langs; + +void build_meta(lua_State *L, const luaL_Reg *meta) +{ + // [env, target] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [env, target, func] + lua_pushvalue(L, -3); // [env, target, func, env] + lua_setfenv(L, -2); // [env, target, func] + lua_setfield(L, -2, meta[i].name); // [env, target] + } + + lua_pushvalue(L, -1); // [env, target, target] + lua_setfield(L, -2, "__index"); // [env, target] +} + + + +/// init the tslua library +/// +/// all global state is stored in the regirstry of the lua_State +void tslua_init(lua_State *L) +{ + + langs = pmap_new(cstr_t)(); + + lua_createtable(L, 0, 0); + + // type metatables + lua_createtable(L, 0, 0); + build_meta(L, parser_meta); + lua_setfield(L, -2, "parser-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, tree_meta); + lua_setfield(L, -2, "tree-meta"); + + lua_createtable(L, 0, 0); + build_meta(L, node_meta); + lua_setfield(L, -2, "node-meta"); + + lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + + lua_pushcfunction(L, tslua_debug); + lua_setglobal(L, "_tslua_debug"); +} + +static int tslua_debug(lua_State *L) +{ + lua_pushinteger(L, debug_n_trees); + lua_pushinteger(L, debug_n_cursors); + return 2; +} + + +int ts_lua_register_lang(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "string expected"); + } + + const char *path = lua_tostring(L,1); + const char *lang_name = lua_tostring(L,2); + + if (pmap_has(cstr_t)(langs, lang_name)) { + return 0; + } + + // TODO: unsafe! + char symbol_buf[128] = "tree_sitter_"; + STRCAT(symbol_buf, lang_name); + + // TODO: we should maybe keep the uv_lib_t around, and close them + // at exit, to keep LeakSanitizer happy. + uv_lib_t lib; + if (uv_dlopen(path, &lib)) { + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + } + + TSLanguage *(*lang_parser)(void); + if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + } + + TSLanguage *lang = lang_parser(); + if (lang == NULL) { + return luaL_error(L, "Failed to load parser: internal error"); + } + + pmap_put(cstr_t)(langs, xstrdup(lang_name), lang); + + lua_pushboolean(L, true); + return 1; +} + +int tslua_push_parser(lua_State *L, const char *lang_name) +{ + TSParser *parser = ts_parser_new(); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + ts_parser_set_language(parser, lang); + Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + p->parser = parser; + p->tree = NULL; + + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + return 1; +} + +static Tslua_parser *parser_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + return lua_touserdata(L, 1); +} + +static int parser_gc(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + ts_parser_delete(p->parser); + if (p->tree) { + ts_tree_delete(p->tree); + } + + return 0; +} + +static int parser_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +{ + buf_T *bp = payload; + static char buf[200]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column,200); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +} + +static int parser_parse_buf(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + long bufnr = lua_tointeger(L, 2); + void *payload = handle_get_buffer(bufnr); + TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + if (p->tree) { + ts_tree_delete(p->tree); + } + p->tree = new_tree; + + tslua_push_tree(L, ts_tree_copy(p->tree)); + return 1; +} + +static int parser_tree(lua_State *L) +{ + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (p->tree) { + tslua_push_tree(L, ts_tree_copy(p->tree)); + } else { + lua_pushnil(L); + } + return 1; +} + +static int parser_edit(lua_State *L) +{ + if(lua_gettop(L) < 10) { + lua_pushstring(L, "not enough args to parser:edit()"); + lua_error(L); + return 0; // unreachable + } + + Tslua_parser *p = parser_check(L); + if (!p) { + return 0; + } + + if (!p->tree) { + return 0; + } + + long start_byte = lua_tointeger(L, 2); + long old_end_byte = lua_tointeger(L, 3); + long new_end_byte = lua_tointeger(L, 4); + TSPoint start_point = { lua_tointeger(L, 5), lua_tointeger(L, 6) }; + TSPoint old_end_point = { lua_tointeger(L, 7), lua_tointeger(L, 8) }; + TSPoint new_end_point = { lua_tointeger(L, 9), lua_tointeger(L, 10) }; + + TSInputEdit edit = { start_byte, old_end_byte, new_end_byte, + start_point, old_end_point, new_end_point }; + + ts_tree_edit(p->tree, &edit); + + return 0; +} + + +// Tree methods + +/// push tree interface on lua stack. +/// +/// This takes "ownership" of the tree and will free it +/// when the wrapper object is garbage collected +void tslua_push_tree(lua_State *L, TSTree *tree) +{ + TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] + *ud = tree; + lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] + lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] + lua_setmetatable(L, -3); // [udata, env] + lua_pop(L, 1); // [udata] + + // table used for node wrappers to keep a reference to tree wrapper + // NB: in lua 5.3 the uservalue for the node could just be the tree, but + // in lua 5.1 the uservalue (fenv) must be a table. + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] + debug_n_trees++; +} + +static TSTree *tree_check(lua_State *L) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSTree **ud = lua_touserdata(L, 1); + return *ud; +} + +static int tree_gc(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + + ts_tree_delete(tree); + debug_n_trees--; + return 0; +} + +static int tree_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int tree_root(lua_State *L) +{ + TSTree *tree = tree_check(L); + if (!tree) { + return 0; + } + TSNode root = ts_tree_root_node(tree); + push_node(L, root); + return 1; +} + +// Node methods + +/// push node interface on lua stack +/// +/// top of stack must either be the tree this node belongs to or another node +/// of the same tree! This value is not popped. Can only be called inside a +/// cfunction with the tslua environment. +static void push_node(lua_State *L, TSNode node) +{ + if (ts_node_is_null(node)) { + lua_pushnil(L); // [src, nil] + return; + } + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + *ud = node; + lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_setmetatable(L, -2); // [src, udata] + lua_getfenv(L, -2); // [src, udata, reftable] + lua_setfenv(L, -2); // [src, udata] +} + +static bool node_check(lua_State *L, TSNode *res) +{ + if (!lua_gettop(L)) { + return 0; + } + if (!lua_isuserdata(L, 1)) { + return 0; + } + // TODO: typecheck! + TSNode *ud = lua_touserdata(L, 1); + *res = *ud; + return true; +} + + +static int node_tostring(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ""); + lua_concat(L, 3); + return 1; +} + +static int node_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + TSPoint end = ts_node_end_point(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + return 4; +} + +static int node_start(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = ts_node_start_point(node); + uint32_t start_byte = ts_node_start_byte(node); + lua_pushnumber(L, start.row); + lua_pushnumber(L, start.column); + lua_pushnumber(L, start_byte); + return 3; +} + +static int node_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_child_count(node); + lua_pushnumber(L, count); + return 1; +} + +static int node_type(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushstring(L, ts_node_type(node)); + return 1; +} + +static int node_symbol(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSSymbol symbol = ts_node_symbol(node); + lua_pushnumber(L, symbol); + return 1; +} + +static int node_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = {(uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3)}; + TSPoint end = {(uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5)}; + TSNode child = ts_node_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + +static int node_parent(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSNode parent = ts_node_parent(node); + push_node(L, parent); + return 1; +} + diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h new file mode 100644 index 0000000000..d59b5e47a0 --- /dev/null +++ b/src/nvim/lua/treesitter.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LUA_TREESITTER_H +#define NVIM_LUA_TREESITTER_H + +#include "tree_sitter/api.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/treesitter.h.generated.h" +#endif + +#endif // NVIM_LUA_TREESITTER_H diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c38926fe24..b67762e48e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -232,9 +232,9 @@ local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') return t.inspect - elseif key == 'tree_sitter' then - t.tree_sitter = require('vim.tree_sitter') - return t.tree_sitter + elseif key == 'treesitter' then + t.treesitter = require('vim.treesitter') + return t.treesitter elseif require('vim.shared')[key] ~= nil then -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] -- cgit From a361e09cc531caf9dfb41bf860ca2d540ac2789d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 12:50:41 +0200 Subject: tree-sitter: use standard luaL_newmetatable and luaL_checkudata pattern --- src/nvim/lua/treesitter.c | 94 +++++++++++++++-------------------------------- 1 file changed, 30 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 794bdc6749..8d24d15ccd 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -20,15 +20,13 @@ // NOT state-safe, delete when GC is confimed working: static int debug_n_trees = 0, debug_n_cursors = 0; -#define REG_KEY "treesitter-private" - #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" typedef struct { TSParser *parser; - TSTree *tree; + TSTree *tree; // internal tree, used for editing/reparsing } Tslua_parser; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -67,18 +65,18 @@ static struct luaL_Reg node_meta[] = { PMap(cstr_t) *langs; -void build_meta(lua_State *L, const luaL_Reg *meta) +void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) { - // [env, target] - for (size_t i = 0; meta[i].name != NULL; i++) { - lua_pushcfunction(L, meta[i].func); // [env, target, func] - lua_pushvalue(L, -3); // [env, target, func, env] - lua_setfenv(L, -2); // [env, target, func] - lua_setfield(L, -2, meta[i].name); // [env, target] - } + if (luaL_newmetatable(L, tname)) { // [meta] + for (size_t i = 0; meta[i].name != NULL; i++) { + lua_pushcfunction(L, meta[i].func); // [meta, func] + lua_setfield(L, -2, meta[i].name); // [meta] + } - lua_pushvalue(L, -1); // [env, target, target] - lua_setfield(L, -2, "__index"); // [env, target] + lua_pushvalue(L, -1); // [meta, meta] + lua_setfield(L, -2, "__index"); // [meta] + } + lua_pop(L, 1); // [] (don't use it now) } @@ -91,22 +89,12 @@ void tslua_init(lua_State *L) langs = pmap_new(cstr_t)(); - lua_createtable(L, 0, 0); - // type metatables - lua_createtable(L, 0, 0); - build_meta(L, parser_meta); - lua_setfield(L, -2, "parser-meta"); + build_meta(L, "treesitter_parser", parser_meta); - lua_createtable(L, 0, 0); - build_meta(L, tree_meta); - lua_setfield(L, -2, "tree-meta"); + build_meta(L, "treesitter_tree", tree_meta); - lua_createtable(L, 0, 0); - build_meta(L, node_meta); - lua_setfield(L, -2, "node-meta"); - - lua_setfield(L, LUA_REGISTRYINDEX, REG_KEY); + build_meta(L, "treesitter_node", node_meta); lua_pushcfunction(L, tslua_debug); lua_setglobal(L, "_tslua_debug"); @@ -173,23 +161,14 @@ int tslua_push_parser(lua_State *L, const char *lang_name) p->parser = parser; p->tree = NULL; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "parser-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] return 1; } static Tslua_parser *parser_check(lua_State *L) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - return lua_touserdata(L, 1); + return luaL_checkudata(L, 1, "treesitter_parser"); } static int parser_gc(lua_State *L) @@ -313,31 +292,22 @@ void tslua_push_tree(lua_State *L, TSTree *tree) { TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] *ud = tree; - lua_getfield(L, LUA_REGISTRYINDEX, REG_KEY); // [udata, env] - lua_getfield(L, -1, "tree-meta"); // [udata, env, meta] - lua_setmetatable(L, -3); // [udata, env] - lua_pop(L, 1); // [udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] // table used for node wrappers to keep a reference to tree wrapper // NB: in lua 5.3 the uservalue for the node could just be the tree, but // in lua 5.1 the uservalue (fenv) must be a table. - lua_createtable(L, 1, 0); // [udata, reftable] - lua_pushvalue(L, -2); // [udata, reftable, udata] - lua_rawseti(L, -2, 1); // [udata, reftable] - lua_setfenv(L, -2); // [udata] + lua_createtable(L, 1, 0); // [udata, reftable] + lua_pushvalue(L, -2); // [udata, reftable, udata] + lua_rawseti(L, -2, 1); // [udata, reftable] + lua_setfenv(L, -2); // [udata] debug_n_trees++; } static TSTree *tree_check(lua_State *L) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; - } - // TODO: typecheck! - TSTree **ud = lua_touserdata(L, 1); + TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree"); return *ud; } @@ -385,7 +355,7 @@ static void push_node(lua_State *L, TSNode node) } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] *ud = node; - lua_getfield(L, LUA_ENVIRONINDEX, "node-meta"); // [src, udata, meta] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [src, udata, meta] lua_setmetatable(L, -2); // [src, udata] lua_getfenv(L, -2); // [src, udata, reftable] lua_setfenv(L, -2); // [src, udata] @@ -393,16 +363,12 @@ static void push_node(lua_State *L, TSNode node) static bool node_check(lua_State *L, TSNode *res) { - if (!lua_gettop(L)) { - return 0; - } - if (!lua_isuserdata(L, 1)) { - return 0; + TSNode *ud = luaL_checkudata(L, 1, "treesitter_node"); + if (ud) { + *res = *ud; + return true; } - // TODO: typecheck! - TSNode *ud = lua_touserdata(L, 1); - *res = *ud; - return true; + return false; } -- cgit From c1dc1bedba4e0e3db2cd2e52d9241991567f8218 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 13:12:59 +0200 Subject: tree-sitter: style --- src/nvim/lua/executor.c | 3 +- src/nvim/lua/treesitter.c | 113 ++++++++++++++++++++++++---------------------- 2 files changed, 59 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 8a35f11c71..c208711985 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -828,8 +828,7 @@ static int create_tslua_parser(lua_State *L) return luaL_error(L, "string expected"); } - const char *lang_name = lua_tostring(L,1); - + const char *lang_name = lua_tostring(L, 1); return tslua_push_parser(L, lang_name); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 8d24d15ccd..2337d598b3 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -34,38 +34,38 @@ typedef struct { #endif static struct luaL_Reg parser_meta[] = { - {"__gc", parser_gc}, - {"__tostring", parser_tostring}, - {"parse_buf", parser_parse_buf}, - {"edit", parser_edit}, - {"tree", parser_tree}, - {NULL, NULL} + { "__gc", parser_gc }, + { "__tostring", parser_tostring }, + { "parse_buf", parser_parse_buf }, + { "edit", parser_edit }, + { "tree", parser_tree }, + { NULL, NULL } }; static struct luaL_Reg tree_meta[] = { - {"__gc", tree_gc}, - {"__tostring", tree_tostring}, - {"root", tree_root}, - {NULL, NULL} + { "__gc", tree_gc }, + { "__tostring", tree_tostring }, + { "root", tree_root }, + { NULL, NULL } }; static struct luaL_Reg node_meta[] = { - {"__tostring", node_tostring}, - {"__len", node_child_count}, - {"range", node_range}, - {"start", node_start}, - {"type", node_type}, - {"symbol", node_symbol}, - {"child_count", node_child_count}, - {"child", node_child}, - {"descendant_for_point_range", node_descendant_for_point_range}, - {"parent", node_parent}, - {NULL, NULL} + { "__tostring", node_tostring }, + { "__len", node_child_count }, + { "range", node_range }, + { "start", node_start }, + { "type", node_type }, + { "symbol", node_symbol }, + { "child_count", node_child_count }, + { "child", node_child }, + { "descendant_for_point_range", node_descendant_for_point_range }, + { "parent", node_parent }, + { NULL, NULL } }; PMap(cstr_t) *langs; -void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) +void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] for (size_t i = 0; meta[i].name != NULL; i++) { @@ -86,7 +86,6 @@ void build_meta(lua_State *L, const char* tname, const luaL_Reg *meta) /// all global state is stored in the regirstry of the lua_State void tslua_init(lua_State *L) { - langs = pmap_new(cstr_t)(); // type metatables @@ -114,8 +113,8 @@ int ts_lua_register_lang(lua_State *L) return luaL_error(L, "string expected"); } - const char *path = lua_tostring(L,1); - const char *lang_name = lua_tostring(L,2); + const char *path = lua_tostring(L, 1); + const char *lang_name = lua_tostring(L, 2); if (pmap_has(cstr_t)(langs, lang_name)) { return 0; @@ -129,12 +128,14 @@ int ts_lua_register_lang(lua_State *L) // at exit, to keep LeakSanitizer happy. uv_lib_t lib; if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", uv_dlerror(&lib)); + return luaL_error(L, "Failed to load parser: uv_dlopen: %s", + uv_dlerror(&lib)); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); + return luaL_error(L, "Failed to load parser: uv_dlsym: %s", + uv_dlerror(&lib)); } TSLanguage *lang = lang_parser(); @@ -192,26 +193,29 @@ static int parser_tostring(lua_State *L) return 1; } -static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read) +static const char *input_cb(void *payload, uint32_t byte_index, + TSPoint position, uint32_t *bytes_read) { - buf_T *bp = payload; - static char buf[200]; - if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { - *bytes_read = 0; - return ""; - } - char_u *line = ml_get_buf(bp, position.row+1, false); - size_t len = STRLEN(line); - size_t tocopy = MIN(len-position.column,200); - - // TODO: translate embedded \n to \000 - memcpy(buf, line+position.column, tocopy); - *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { - buf[tocopy] = '\n'; - (*bytes_read)++; - } - return buf; + buf_T *bp = payload; +#define BUFSIZE 256 + static char buf[BUFSIZE]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { + *bytes_read = 0; + return ""; + } + char_u *line = ml_get_buf(bp, position.row+1, false); + size_t len = STRLEN(line); + size_t tocopy = MIN(len-position.column, BUFSIZE); + + // TODO: translate embedded \n to \000 + memcpy(buf, line+position.column, tocopy); + *bytes_read = (uint32_t)tocopy; + if (tocopy < 200) { + buf[tocopy] = '\n'; + (*bytes_read)++; + } + return buf; +#undef BUFSIZE } static int parser_parse_buf(lua_State *L) @@ -223,7 +227,7 @@ static int parser_parse_buf(lua_State *L) long bufnr = lua_tointeger(L, 2); void *payload = handle_get_buffer(bufnr); - TSInput input = {payload, input_cb, TSInputEncodingUTF8}; + TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); if (p->tree) { ts_tree_delete(p->tree); @@ -251,10 +255,9 @@ static int parser_tree(lua_State *L) static int parser_edit(lua_State *L) { - if(lua_gettop(L) < 10) { + if (lua_gettop(L) < 10) { lua_pushstring(L, "not enough args to parser:edit()"); - lua_error(L); - return 0; // unreachable + return lua_error(L); } Tslua_parser *p = parser_check(L); @@ -350,7 +353,7 @@ static int tree_root(lua_State *L) static void push_node(lua_State *L, TSNode node) { if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] + lua_pushnil(L); // [src, nil] return; } TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] @@ -466,10 +469,10 @@ static int node_descendant_for_point_range(lua_State *L) if (!node_check(L, &node)) { return 0; } - TSPoint start = {(uint32_t)lua_tointeger(L, 2), - (uint32_t)lua_tointeger(L, 3)}; - TSPoint end = {(uint32_t)lua_tointeger(L, 4), - (uint32_t)lua_tointeger(L, 5)}; + TSPoint start = { (uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3) }; + TSPoint end = { (uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_descendant_for_point_range(node, start, end); lua_pushvalue(L, 1); -- cgit From a88a9f128e29b27315a87d0119fbc649196999bc Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 13:43:30 +0200 Subject: tree-sitter: add some more API --- src/nvim/lua/treesitter.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) (limited to 'src') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 2337d598b3..9d599da85f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -54,11 +54,19 @@ static struct luaL_Reg node_meta[] = { { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, + { "end_", node_end }, { "type", node_type }, { "symbol", node_symbol }, + { "named", node_named }, + { "missing", node_missing }, + { "has_error", node_has_error }, + { "sexpr", node_sexpr }, { "child_count", node_child_count }, + { "named_child_count", node_named_child_count }, { "child", node_child }, + { "named_child", node_named_child }, { "descendant_for_point_range", node_descendant_for_point_range }, + { "named_descendant_for_point_range", node_named_descendant_for_point_range }, { "parent", node_parent }, { NULL, NULL } }; @@ -417,6 +425,20 @@ static int node_start(lua_State *L) return 3; } +static int node_end(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint end = ts_node_end_point(node); + uint32_t end_byte = ts_node_end_byte(node); + lua_pushnumber(L, end.row); + lua_pushnumber(L, end.column); + lua_pushnumber(L, end_byte); + return 3; +} + static int node_child_count(lua_State *L) { TSNode node; @@ -428,6 +450,17 @@ static int node_child_count(lua_State *L) return 1; } +static int node_named_child_count(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + uint32_t count = ts_node_named_child_count(node); + lua_pushnumber(L, count); + return 1; +} + static int node_type(lua_State *L) { TSNode node; @@ -449,6 +482,48 @@ static int node_symbol(lua_State *L) return 1; } +static int node_named(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_is_named(node)); + return 1; +} + +static int node_sexpr(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + char *allocated = ts_node_string(node); + lua_pushstring(L, allocated); + xfree(allocated); + return 1; +} + +static int node_missing(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_is_missing(node)); + return 1; +} + +static int node_has_error(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + lua_pushboolean(L, ts_node_has_error(node)); + return 1; +} + static int node_child(lua_State *L) { TSNode node; @@ -463,6 +538,20 @@ static int node_child(lua_State *L) return 1; } +static int node_named_child(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + long num = lua_tointeger(L, 2); + TSNode child = ts_node_named_child(node, (uint32_t)num); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + static int node_descendant_for_point_range(lua_State *L) { TSNode node; @@ -480,6 +569,23 @@ static int node_descendant_for_point_range(lua_State *L) return 1; } +static int node_named_descendant_for_point_range(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSPoint start = { (uint32_t)lua_tointeger(L, 2), + (uint32_t)lua_tointeger(L, 3) }; + TSPoint end = { (uint32_t)lua_tointeger(L, 4), + (uint32_t)lua_tointeger(L, 5) }; + TSNode child = ts_node_named_descendant_for_point_range(node, start, end); + + lua_pushvalue(L, 1); + push_node(L, child); + return 1; +} + static int node_parent(lua_State *L) { TSNode node; -- cgit From d24dec596c25690aba0aca658546ffdfcc6a952c Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 15 Jun 2019 14:05:35 +0200 Subject: tree-sitter: inspect language --- src/nvim/lua/executor.c | 3 +++ src/nvim/lua/treesitter.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index c208711985..aa83e3c1ba 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -841,4 +841,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, ts_lua_register_lang); lua_setfield(lstate, -2, "_ts_add_language"); + + lua_pushcfunction(lstate, ts_lua_inspect_lang); + lua_setfield(lstate, -2, "_ts_inspect_language"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 9d599da85f..db337db533 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -157,6 +157,51 @@ int ts_lua_register_lang(lua_State *L) return 1; } +int ts_lua_inspect_lang(lua_State *L) +{ + if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { + return luaL_error(L, "string expected"); + } + const char *lang_name = lua_tostring(L, 1); + + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + lua_createtable(L, 0, 2); // [retval] + + size_t nsymbols = (size_t)ts_language_symbol_count(lang); + + lua_createtable(L, nsymbols-1, 1); // [retval, symbols] + for (size_t i = 0; i < nsymbols; i++) { + TSSymbolType t = ts_language_symbol_type(lang, i); + if (t == TSSymbolTypeAuxiliary) { + // not used by the API + continue; + } + lua_createtable(L, 2, 0); // [retval, symbols, elem] + lua_pushstring(L, ts_language_symbol_name(lang, i)); + lua_rawseti(L, -2, 1); + lua_pushboolean(L, t == TSSymbolTypeRegular); + lua_rawseti(L, -2, 2); // [retval, symbols, elem] + lua_rawseti(L, -2, i); // [retval, symbols] + } + + lua_setfield(L, -2, "symbols"); // [retval] + + // TODO: this seems to be empty, what langs have fields? + size_t nfields = (size_t)ts_language_field_count(lang); + lua_createtable(L, nfields-1, 1); // [retval, fields] + for (size_t i = 0; i < nfields; i++) { + lua_pushstring(L, ts_language_field_name_for_id(lang, i)); + lua_rawseti(L, -2, i); // [retval, fields] + } + + lua_setfield(L, -2, "fields"); // [retval] + return 1; +} + int tslua_push_parser(lua_State *L, const char *lang_name) { TSParser *parser = ts_parser_new(); -- cgit From d697841a9d3030efaf10dbddaee9f3c0a8fe1b78 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 16 Jun 2019 14:09:18 +0200 Subject: tree-sitter: cleanup tree refcounting --- src/nvim/lua/treesitter.c | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index db337db533..016c175b59 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -17,9 +17,6 @@ #include "tree_sitter/api.h" -// NOT state-safe, delete when GC is confimed working: -static int debug_n_trees = 0, debug_n_cursors = 0; - #include "nvim/lua/treesitter.h" #include "nvim/api/private/handle.h" #include "nvim/memline.h" @@ -102,19 +99,8 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_tree", tree_meta); build_meta(L, "treesitter_node", node_meta); - - lua_pushcfunction(L, tslua_debug); - lua_setglobal(L, "_tslua_debug"); } -static int tslua_debug(lua_State *L) -{ - lua_pushinteger(L, debug_n_trees); - lua_pushinteger(L, debug_n_cursors); - return 2; -} - - int ts_lua_register_lang(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { @@ -280,6 +266,9 @@ static int parser_parse_buf(lua_State *L) long bufnr = lua_tointeger(L, 2); void *payload = handle_get_buffer(bufnr); + if (!payload) { + return luaL_error(L, "invalid buffer handle: %d", bufnr); + } TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); if (p->tree) { @@ -287,7 +276,7 @@ static int parser_parse_buf(lua_State *L) } p->tree = new_tree; - tslua_push_tree(L, ts_tree_copy(p->tree)); + tslua_push_tree(L, p->tree); return 1; } @@ -298,11 +287,7 @@ static int parser_tree(lua_State *L) return 0; } - if (p->tree) { - tslua_push_tree(L, ts_tree_copy(p->tree)); - } else { - lua_pushnil(L); - } + tslua_push_tree(L, p->tree); return 1; } @@ -342,12 +327,15 @@ static int parser_edit(lua_State *L) /// push tree interface on lua stack. /// -/// This takes "ownership" of the tree and will free it -/// when the wrapper object is garbage collected +/// This makes a copy of the tree, so ownership of the argument is unaffected. void tslua_push_tree(lua_State *L, TSTree *tree) { + if (tree == NULL) { + lua_pushnil(L); + return; + } TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata] - *ud = tree; + *ud = ts_tree_copy(tree); lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta] lua_setmetatable(L, -2); // [udata] @@ -358,7 +346,6 @@ void tslua_push_tree(lua_State *L, TSTree *tree) lua_pushvalue(L, -2); // [udata, reftable, udata] lua_rawseti(L, -2, 1); // [udata, reftable] lua_setfenv(L, -2); // [udata] - debug_n_trees++; } static TSTree *tree_check(lua_State *L) @@ -375,7 +362,6 @@ static int tree_gc(lua_State *L) } ts_tree_delete(tree); - debug_n_trees--; return 0; } -- cgit From 06ee45b9b1c14c7ce6cb23403cdbe2852d495cad Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 21 Jun 2019 14:14:51 +0200 Subject: tree-sitter: fix lint, delete "demo" plugin (replaced by functional tests) --- src/nvim/lua/executor.c | 4 +-- src/nvim/lua/treesitter.c | 70 +++++++++++++++++++++++++---------------------- src/nvim/lua/treesitter.h | 4 +++ 3 files changed, 43 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index aa83e3c1ba..127458fe39 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -839,9 +839,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); - lua_pushcfunction(lstate, ts_lua_register_lang); + lua_pushcfunction(lstate, tslua_register_lang); lua_setfield(lstate, -2, "_ts_add_language"); - lua_pushcfunction(lstate, ts_lua_inspect_lang); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 016c175b59..c27ae8c877 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1,9 +1,9 @@ // 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 -// lua bindings for tree-siter. -// NB: this file should contain a generic lua interface for -// tree-sitter trees and nodes, and could be broken out as a reusable library +// lua bindings for tree-sitter. +// NB: this file mostly contains a generic lua interface for tree-sitter +// trees and nodes, and could be broken out as a reusable lua package #include #include @@ -22,9 +22,9 @@ #include "nvim/memline.h" typedef struct { - TSParser *parser; - TSTree *tree; // internal tree, used for editing/reparsing -} Tslua_parser; + TSParser *parser; + TSTree *tree; // internal tree, used for editing/reparsing +} TSLua_parser; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" @@ -68,9 +68,9 @@ static struct luaL_Reg node_meta[] = { { NULL, NULL } }; -PMap(cstr_t) *langs; +static PMap(cstr_t) *langs; -void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) +static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) { if (luaL_newmetatable(L, tname)) { // [meta] for (size_t i = 0; meta[i].name != NULL; i++) { @@ -84,8 +84,6 @@ void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta) lua_pop(L, 1); // [] (don't use it now) } - - /// init the tslua library /// /// all global state is stored in the regirstry of the lua_State @@ -95,13 +93,11 @@ void tslua_init(lua_State *L) // type metatables build_meta(L, "treesitter_parser", parser_meta); - build_meta(L, "treesitter_tree", tree_meta); - build_meta(L, "treesitter_node", node_meta); } -int ts_lua_register_lang(lua_State *L) +int tslua_register_lang(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); @@ -114,22 +110,27 @@ int ts_lua_register_lang(lua_State *L) return 0; } - // TODO: unsafe! - char symbol_buf[128] = "tree_sitter_"; - STRCAT(symbol_buf, lang_name); +#define BUFSIZE 128 + char symbol_buf[BUFSIZE]; + snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); +#undef BUFSIZE - // TODO: we should maybe keep the uv_lib_t around, and close them - // at exit, to keep LeakSanitizer happy. uv_lib_t lib; if (uv_dlopen(path, &lib)) { - return luaL_error(L, "Failed to load parser: uv_dlopen: %s", - uv_dlerror(&lib)); + snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s", + uv_dlerror(&lib)); + uv_dlclose(&lib); + lua_pushstring(L, (char *)IObuff); + return lua_error(L); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - return luaL_error(L, "Failed to load parser: uv_dlsym: %s", - uv_dlerror(&lib)); + snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", + uv_dlerror(&lib)); + uv_dlclose(&lib); + lua_pushstring(L, (char *)IObuff); + return lua_error(L); } TSLanguage *lang = lang_parser(); @@ -143,7 +144,7 @@ int ts_lua_register_lang(lua_State *L) return 1; } -int ts_lua_inspect_lang(lua_State *L) +int tslua_inspect_lang(lua_State *L) { if (lua_gettop(L) < 1 || !lua_isstring(L, 1)) { return luaL_error(L, "string expected"); @@ -176,7 +177,6 @@ int ts_lua_inspect_lang(lua_State *L) lua_setfield(L, -2, "symbols"); // [retval] - // TODO: this seems to be empty, what langs have fields? size_t nfields = (size_t)ts_language_field_count(lang); lua_createtable(L, nfields-1, 1); // [retval, fields] for (size_t i = 0; i < nfields; i++) { @@ -190,14 +190,14 @@ int ts_lua_inspect_lang(lua_State *L) int tslua_push_parser(lua_State *L, const char *lang_name) { - TSParser *parser = ts_parser_new(); TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); if (!lang) { return luaL_error(L, "no such language: %s", lang_name); } + TSParser *parser = ts_parser_new(); ts_parser_set_language(parser, lang); - Tslua_parser *p = lua_newuserdata(L, sizeof(Tslua_parser)); // [udata] + TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata] p->parser = parser; p->tree = NULL; @@ -206,14 +206,14 @@ int tslua_push_parser(lua_State *L, const char *lang_name) return 1; } -static Tslua_parser *parser_check(lua_State *L) +static TSLua_parser *parser_check(lua_State *L) { return luaL_checkudata(L, 1, "treesitter_parser"); } static int parser_gc(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -238,6 +238,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, buf_T *bp = payload; #define BUFSIZE 256 static char buf[BUFSIZE]; + if ((linenr_T)position.row >= bp->b_ml.ml_line_count) { *bytes_read = 0; return ""; @@ -246,10 +247,13 @@ static const char *input_cb(void *payload, uint32_t byte_index, size_t len = STRLEN(line); size_t tocopy = MIN(len-position.column, BUFSIZE); - // TODO: translate embedded \n to \000 memcpy(buf, line+position.column, tocopy); + // Translate embedded \n to NUL + memchrsub(buf, '\n', '\0', tocopy); *bytes_read = (uint32_t)tocopy; - if (tocopy < 200) { + if (tocopy < BUFSIZE) { + // now add the final \n. If it didn't fit, input_cb will be called again + // on the same line with advanced column. buf[tocopy] = '\n'; (*bytes_read)++; } @@ -259,7 +263,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, static int parser_parse_buf(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -282,7 +286,7 @@ static int parser_parse_buf(lua_State *L) static int parser_tree(lua_State *L) { - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } @@ -298,7 +302,7 @@ static int parser_edit(lua_State *L) return lua_error(L); } - Tslua_parser *p = parser_check(L); + TSLua_parser *p = parser_check(L); if (!p) { return 0; } diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h index d59b5e47a0..812166f67b 100644 --- a/src/nvim/lua/treesitter.h +++ b/src/nvim/lua/treesitter.h @@ -1,6 +1,10 @@ #ifndef NVIM_LUA_TREESITTER_H #define NVIM_LUA_TREESITTER_H +#include +#include +#include + #include "tree_sitter/api.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -- cgit From e0d6228978dd1389f75a3e0351f6e6d5625643ae Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 21 Sep 2019 10:10:47 +0200 Subject: tree-sitter: use "range" instead of "point_range" consistently in lua API --- src/nvim/lua/treesitter.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index c27ae8c877..a71234d2c4 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -62,8 +62,8 @@ static struct luaL_Reg node_meta[] = { { "named_child_count", node_named_child_count }, { "child", node_child }, { "named_child", node_named_child }, - { "descendant_for_point_range", node_descendant_for_point_range }, - { "named_descendant_for_point_range", node_named_descendant_for_point_range }, + { "descendant_for_range", node_descendant_for_range }, + { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, { NULL, NULL } }; @@ -587,7 +587,7 @@ static int node_named_child(lua_State *L) return 1; } -static int node_descendant_for_point_range(lua_State *L) +static int node_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, &node)) { @@ -604,7 +604,7 @@ static int node_descendant_for_point_range(lua_State *L) return 1; } -static int node_named_descendant_for_point_range(lua_State *L) +static int node_named_descendant_for_range(lua_State *L) { TSNode node; if (!node_check(L, &node)) { -- cgit From d5a69eb07648a515d03aa5c9e268aef852015ea9 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 22 Sep 2019 11:33:55 +0200 Subject: tree-sitter: handle node equality --- src/nvim/lua/treesitter.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index a71234d2c4..d2072402bb 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -48,6 +48,7 @@ static struct luaL_Reg tree_meta[] = { static struct luaL_Reg node_meta[] = { { "__tostring", node_tostring }, + { "__eq", node_eq }, { "__len", node_child_count }, { "range", node_range }, { "start", node_start }, @@ -431,6 +432,23 @@ static int node_tostring(lua_State *L) return 1; } +static int node_eq(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + // This should only be called if both x and y in "x == y" has the + // treesitter_node metatable. So it is ok to error out otherwise. + TSNode *ud = luaL_checkudata(L, 2, "treesitter_node"); + if (!ud) { + return 0; + } + TSNode node2 = *ud; + lua_pushboolean(L, ts_node_eq(node, node2)); + return 1; +} + static int node_range(lua_State *L) { TSNode node; -- cgit From 0636b25f28e408c8b16026354db7edfef079614a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 28 Sep 2019 21:00:27 +0200 Subject: cmdline: wildmenumode() should be true with wildoptions+=pum --- src/nvim/eval.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 2ddcd389fe..cb1dd1d631 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18845,8 +18845,9 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - if (wild_menu_showing) + if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { rettv->vval.v_number = 1; + } } /// "win_findbuf()" function -- cgit From 34d55f86077e8a937c1ac1b0a0c551a5968fa7f8 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 29 Sep 2019 03:06:36 +0200 Subject: terminfo_start: keep first flushing of ui buffer (#11118) Initially done in 3626d2107 (#11074, for #11062), it was reverted then in 445f2f409 (#11083, which added flushing later). But it is still required here to avoid the reporting of the background response with urxvt/kitty (`nvim -u NONE -cq`). Apparently I've tested this not enough with 445f2f409 (probably only within tmux). --- src/nvim/tui/tui.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 55c4a955c2..0c282a3f1f 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -312,6 +312,7 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } + flush_buf(ui); } static void terminfo_stop(UI *ui) -- cgit From dd26bd59745c9fd358624312feb315ec0f106de8 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 1 Sep 2019 11:13:13 +0200 Subject: screen: don't crash on invalid grid cells being recomposed --- src/nvim/option_defs.h | 3 ++- src/nvim/ui_compositor.c | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 08df495250..108a3dde7c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -518,10 +518,11 @@ EXTERN long p_pyx; // 'pyxversion' EXTERN char_u *p_rdb; // 'redrawdebug' EXTERN unsigned rdb_flags; # ifdef IN_OPTION_C -static char *(p_rdb_values[]) = { "compositor", "nothrottle", NULL }; +static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", NULL }; # endif # define RDB_COMPOSITOR 0x001 # define RDB_NOTHROTTLE 0x002 +# define RDB_INVALID 0x004 EXTERN long p_rdt; // 'redrawtime' EXTERN int p_remap; // 'remap' diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 20ffc1b88e..7d3ecfa0b8 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -425,6 +425,15 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, flags = flags & ~kLineFlagWrap; } + for (int i = skipstart; i < (endcol-skipend)-startcol; i++) { + if (attrbuf[i] < 0) { + if (rdb_flags & RDB_INVALID) { + abort(); + } else { + attrbuf[i] = 0; + } + } + } ui_composed_call_raw_line(1, row, startcol+skipstart, endcol-skipend, endcol-skipend, 0, flags, (const schar_T *)linebuf+skipstart, @@ -535,6 +544,11 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, } else { compose_debug(row, row+1, startcol, endcol, dbghl_normal, false); compose_debug(row, row+1, endcol, clearcol, dbghl_clear, true); +#ifndef NDEBUG + for (int i = 0; i < endcol-startcol; i++) { + assert(attrs[i] >= 0); + } +#endif ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, flags, chunk, attrs); } -- cgit From 8a4ae3d664a22cfaa3ec05635d26a8d662458c7e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 30 Sep 2019 22:00:55 +0200 Subject: tui: improve handle_background_color: short-circuit (#11067) * handle_background_color: short-circuit if handled already * Unit tests for handle_background_color * set waiting_for_bg_response to false in tui_terminal_after_startup By then it should have been received. --- src/nvim/tui/input.c | 11 +++++++++++ src/nvim/tui/input.h | 5 +++++ src/nvim/tui/tui.c | 6 ++++++ 3 files changed, 22 insertions(+) (limited to 'src') diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index ed9b410a19..876f00e03e 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -27,6 +27,7 @@ void tinput_init(TermInput *input, Loop *loop) input->loop = loop; input->paste = 0; input->in_fd = 0; + input->waiting_for_bg_response = false; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); uv_mutex_init(&input->key_buffer_mutex); uv_cond_init(&input->key_buffer_cond); @@ -443,6 +444,9 @@ static void set_bg_deferred(void **argv) // [1] https://en.wikipedia.org/wiki/Luma_%28video%29 static bool handle_background_color(TermInput *input) { + if (!input->waiting_for_bg_response) { + return false; + } size_t count = 0; size_t component = 0; size_t header_size = 0; @@ -463,6 +467,7 @@ static bool handle_background_color(TermInput *input) } else { return false; } + input->waiting_for_bg_response = false; rbuffer_consumed(input->read_stream.buffer, header_size); RBUFFER_EACH(input->read_stream.buffer, c, i) { count = i + 1; @@ -503,6 +508,12 @@ static bool handle_background_color(TermInput *input) } return true; } +#ifdef UNIT_TESTING +bool ut_handle_background_color(TermInput *input) +{ + return handle_background_color(input); +} +#endif static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, bool eof) diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index a4071fab40..49ae32f00e 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -12,6 +12,7 @@ typedef struct term_input { // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk int8_t paste; bool waiting; + bool waiting_for_bg_response; TermKey *tk; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook @@ -28,4 +29,8 @@ typedef struct term_input { # include "tui/input.h.generated.h" #endif +#ifdef UNIT_TESTING +bool ut_handle_background_color(TermInput *input); +#endif + #endif // NVIM_TUI_INPUT_H diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 0c282a3f1f..4ca44b25f0 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -296,6 +296,7 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); // Ask the terminal to send us the background color. + data->input.waiting_for_bg_response = true; unibi_out_ext(ui, data->unibi_ext.get_bg); // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); @@ -365,6 +366,11 @@ static void tui_terminal_after_startup(UI *ui) // 2.3 bug(?) which caused slow drawing during startup. #7649 unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); flush_buf(ui); + + if (data->input.waiting_for_bg_response) { + DLOG("did not get a response for terminal background query"); + data->input.waiting_for_bg_response = false; + } } static void tui_terminal_stop(UI *ui) -- cgit From 30ae60e7cac7e77005aa429bc13f8ffa3ce64eb1 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 2 Oct 2019 03:45:59 +0200 Subject: Fix/revisit git-describe enhancement (#11124) * Fix/keep massaging git-describe result Ref: https://github.com/neovim/neovim/pull/11117#issuecomment-536416223 * build: revisit generation of version from Git Fixes "make clean && make", where "auto/versiondef.h" would be missing since b18b84d - because BYPRODUCTS are apparently removed when cleaning. This includes the following improvements/changes: - do not run git-describe during CMake's configure phase just for reporting - do not print with changed Git version (too noisy, simplifies code) * Move to src/nvim (included before config) for easier flow * fallback to describe always, write empty include file * update_version_stamp.lua: use prefix always --- src/nvim/CMakeLists.txt | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 27977e3a40..a64944ab0d 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -217,6 +217,34 @@ function(get_preproc_output varname iname) endif() endfunction() +# Handle generating version from Git. +set(use_git_version 0) +if(NVIM_VERSION_MEDIUM) + message(STATUS "NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") +elseif(${CMAKE_VERSION} VERSION_LESS "3.2.0") + message(STATUS "Skipping version-string generation (requires CMake 3.2.0+)") +elseif(EXISTS ${PROJECT_SOURCE_DIR}/.git) + find_program(GIT_EXECUTABLE git) + if(GIT_EXECUTABLE) + message(STATUS "Using NVIM_VERSION_MEDIUM from Git") + set(use_git_version 1) + else() + message(STATUS "Skipping version-string generation (cannot find git)") + endif() +endif() +if(use_git_version) + # Create a update_version_stamp target to update the version during build. + file(RELATIVE_PATH relbuild "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") + add_custom_target(update_version_stamp ALL + COMMAND ${LUA_PRG} scripts/update_version_stamp.lua + ${relbuild}/config/auto/versiondef_git.h + "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + BYPRODUCTS ${CMAKE_BINARY_DIR}/config/auto/versiondef_git.h) +else() + file(WRITE ${CMAKE_BINARY_DIR}/config/auto/versiondef_git.h "") +endif() + # NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers # NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources # NVIM_GENERATED_SOURCES: generated source files @@ -245,12 +273,16 @@ foreach(sfile ${NVIM_SOURCES} get_preproc_output(PREPROC_OUTPUT ${gf_i}) + set(depends "${HEADER_GENERATOR}" "${sfile}") + if(use_git_version AND "${f}" STREQUAL "version.c") + # Ensure auto/versiondef_git.h exists after "make clean". + list(APPEND depends update_version_stamp) + endif() add_custom_command( OUTPUT "${gf_c_h}" "${gf_h_h}" COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags} ${C_FLAGS_ARRAY} COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}" - DEPENDS "${HEADER_GENERATOR}" "${sfile}" - ) + DEPENDS ${depends}) list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}") list(APPEND NVIM_GENERATED_FOR_HEADERS "${gf_h_h}") if(${d} MATCHES "^api$" AND NOT ${f} MATCHES "^api/helpers.c$") -- cgit From b7d6caaa036c3d1be716bb6e4b0f56c08fb8dcf5 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 2 Oct 2019 03:51:46 +0200 Subject: Fix redraw regression with w_p_cole in visual mode Fixes https://github.com/neovim/neovim/issues/11024, regressed in 23c71d51. Closes https://github.com/neovim/neovim/pull/11120. --- src/nvim/screen.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 17a91f69d5..187c89b28c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -578,10 +578,13 @@ void conceal_check_cursor_line(void) /// /// If true, both old and new cursorline will need /// need to be redrawn when moving cursor within windows. +/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch +/// caused by scrolling. bool win_cursorline_standout(const win_T *wp) FUNC_ATTR_NONNULL_ALL { - return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); + return wp->w_p_cul + || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); } /* -- cgit From 8e25cf3881bbc3d65645d1b2abc6fa46863b1765 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 2 Oct 2019 04:07:10 +0200 Subject: patch_terminfo_bugs: TERM=xterm with non-xterm: ignore smglr (#11132) "smglr" was added for TERM=xterm recently to the terminfo database, which causes display issues with terminals that use `TERM=xterm` by default for themselves, although not supporting it. This patch makes "smglr" to be ignored then. Fixes https://github.com/neovim/neovim/issues/10562 --- src/nvim/tui/tui.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 4ca44b25f0..956d4eb9da 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -93,7 +93,7 @@ typedef struct { int out_fd; bool scroll_region_is_full_screen; bool can_change_scroll_region; - bool can_set_lr_margin; + bool can_set_lr_margin; // smglr bool can_set_left_right_margin; bool can_scroll; bool can_erase_chars; @@ -1603,6 +1603,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds"); unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds"); unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds"); + } else { + // Fix things advertised via TERM=xterm, for non-xterm. + if (unibi_get_str(ut, unibi_set_lr_margin)) { + ILOG("Disabling smglr with TERM=xterm for non-xterm."); + unibi_set_str(ut, unibi_set_lr_margin, NULL); + } } #ifdef WIN32 -- cgit From 0253f0cd929a59e1516359eab9ae84ce39643a7b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 04:22:54 -0400 Subject: vim-patch:8.1.0010: efm_to_regpat() is too long Problem: efm_to_regpat() is too long. Solution: Split off three functions. (Yegappan Lakshmanan, closes vim/vim#2924) https://github.com/vim/vim/commit/6bff719f7e472e918c60aa336de03e799b806c4f --- src/nvim/quickfix.c | 239 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 147 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 528829e63d..5083e573d3 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -264,15 +264,152 @@ static struct fmtpattern { 'o', ".\\+" } }; +// Convert an errorformat pattern to a regular expression pattern. +// See fmt_pat definition above for the list of supported patterns. +static char_u *fmtpat_to_regpat( + const char_u *efmp, + efm_T *fmt_ptr, + int idx, + int round, + char_u *ptr, + char_u *errmsg, + size_t errmsglen) + FUNC_ATTR_NONNULL_ALL +{ + if (fmt_ptr->addr[idx]) { + // Each errorformat pattern can occur only once + snprintf((char *)errmsg, errmsglen, + _("E372: Too many %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + if ((idx && idx < 6 + && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) + || (idx == 6 + && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { + snprintf((char *)errmsg, errmsglen, + _("E373: Unexpected %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + fmt_ptr->addr[idx] = (char_u)++round; + *ptr++ = '\\'; + *ptr++ = '('; +#ifdef BACKSLASH_IN_FILENAME + if (*efmp == 'f') { + // Also match "c:" in the file name, even when + // checking for a colon next: "%f:". + // "\%(\a:\)\=" + STRCPY(ptr, "\\%(\\a:\\)\\="); + ptr += 10; + } +#endif + if (*efmp == 'f' && efmp[1] != NUL) { + if (efmp[1] != '\\' && efmp[1] != '%') { + // A file name may contain spaces, but this isn't + // in "\f". For "%f:%l:%m" there may be a ":" in + // the file name. Use ".\{-1,}x" instead (x is + // the next character), the requirement that :999: + // follows should work. + STRCPY(ptr, ".\\{-1,}"); + ptr += 7; + } else { + // File name followed by '\\' or '%': include as + // many file name chars as possible. + STRCPY(ptr, "\\f\\+"); + ptr += 4; + } + } else { + char_u *srcptr = (char_u *)fmt_pat[idx].pattern; + while ((*ptr = *srcptr++) != NUL) { + ptr++; + } + } + *ptr++ = '\\'; + *ptr++ = ')'; + + return ptr; +} + +// Convert a scanf like format in 'errorformat' to a regular expression. +static char_u *scanf_fmt_to_regpat( + const char_u *efm, + int len, + const char_u **pefmp, + char_u *ptr, + char_u *errmsg, + size_t errmsglen) + FUNC_ATTR_NONNULL_ALL +{ + const char_u *efmp = *pefmp; + + if (*++efmp == '[' || *efmp == '\\') { + if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. + if (efmp[1] == '^') { + *ptr++ = *++efmp; + } + if (efmp < efm + len) { + *ptr++ = *++efmp; // could be ']' + while (efmp < efm + len && (*ptr++ = *++efmp) != ']') { + } + if (efmp == efm + len) { + EMSG(_("E374: Missing ] in format string")); + return NULL; + } + } + } else if (efmp < efm + len) { // %*\D, %*\s etc. + *ptr++ = *++efmp; + } + *ptr++ = '\\'; + *ptr++ = '+'; + } else { + // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? + snprintf((char *)errmsg, errmsglen, + _("E375: Unsupported %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + + *pefmp = efmp; + + return ptr; +} + +// Analyze/parse an errorformat prefix. +static int efm_analyze_prefix(const char_u **pefmp, efm_T *fmt_ptr, + char_u *errmsg, size_t errmsglen) + FUNC_ATTR_NONNULL_ALL +{ + const char_u *efmp = *pefmp; + + if (vim_strchr((char_u *)"+-", *efmp) != NULL) { + fmt_ptr->flags = *efmp++; + } + if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { + fmt_ptr->prefix = *efmp; + } else { + snprintf((char *)errmsg, errmsglen, + _("E376: Invalid %%%c in format string prefix"), *efmp); + EMSG(errmsg); + return FAIL; + } + + *pefmp = efmp; + + return OK; +} + + // Converts a 'errorformat' string to regular expression pattern -static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, - char_u *regpat, char_u *errmsg) +static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, + char_u *regpat, char_u *errmsg, size_t errmsglen) + FUNC_ATTR_NONNULL_ALL { // Build regexp pattern from current 'errorformat' option char_u *ptr = regpat; *ptr++ = '^'; int round = 0; - for (char_u *efmp = efm; efmp < efm + len; efmp++) { + for (const char_u *efmp = efm; efmp < efm + len; efmp++) { if (*efmp == '%') { efmp++; int idx; @@ -282,89 +419,15 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, } } if (idx < FMT_PATTERNS) { - if (fmt_ptr->addr[idx]) { - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E372: Too many %%%c in format string"), *efmp); - EMSG(errmsg); - return -1; - } - if ((idx - && idx < 6 - && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) - || (idx == 6 - && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E373: Unexpected %%%c in format string"), *efmp); - EMSG(errmsg); + ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, + errmsg, errmsglen); + if (ptr == NULL) { return -1; } round++; - fmt_ptr->addr[idx] = (char_u)round; - *ptr++ = '\\'; - *ptr++ = '('; -#ifdef BACKSLASH_IN_FILENAME - if (*efmp == 'f') { - // Also match "c:" in the file name, even when - // checking for a colon next: "%f:". - // "\%(\a:\)\=" - STRCPY(ptr, "\\%(\\a:\\)\\="); - ptr += 10; - } -#endif - if (*efmp == 'f' && efmp[1] != NUL) { - if (efmp[1] != '\\' && efmp[1] != '%') { - // A file name may contain spaces, but this isn't - // in "\f". For "%f:%l:%m" there may be a ":" in - // the file name. Use ".\{-1,}x" instead (x is - // the next character), the requirement that :999: - // follows should work. - STRCPY(ptr, ".\\{-1,}"); - ptr += 7; - } else { - // File name followed by '\\' or '%': include as - // many file name chars as possible. - STRCPY(ptr, "\\f\\+"); - ptr += 4; - } - } else { - char_u *srcptr = (char_u *)fmt_pat[idx].pattern; - while ((*ptr = *srcptr++) != NUL) { - ptr++; - } - } - *ptr++ = '\\'; - *ptr++ = ')'; } else if (*efmp == '*') { - if (*++efmp == '[' || *efmp == '\\') { - if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. - if (efmp[1] == '^') { - *ptr++ = *++efmp; - } - if (efmp < efm + len) { - efmp++; - *ptr++ = *efmp; // could be ']' - while (efmp < efm + len) { - efmp++; - if ((*ptr++ = *efmp) == ']') { - break; - } - } - if (efmp == efm + len) { - EMSG(_("E374: Missing ] in format string")); - return -1; - } - } - } else if (efmp < efm + len) { // %*\D, %*\s etc. - efmp++; - *ptr++ = *efmp; - } - *ptr++ = '\\'; - *ptr++ = '+'; - } else { - // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E375: Unsupported %%%c in format string"), *efmp); - EMSG(errmsg); + ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg, errmsglen); + if (ptr == NULL) { return -1; } } else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) { @@ -374,15 +437,7 @@ static int efm_to_regpat(char_u *efm, int len, efm_T *fmt_ptr, } else if (*efmp == '>') { fmt_ptr->conthere = true; } else if (efmp == efm + 1) { // analyse prefix - if (vim_strchr((char_u *)"+-", *efmp) != NULL) { - fmt_ptr->flags = *efmp++; - } - if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { - fmt_ptr->prefix = *efmp; - } else { - snprintf((char *)errmsg, CMDBUFFSIZE + 1, - _("E376: Invalid %%%c in format string prefix"), *efmp); - EMSG(errmsg); + if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg, errmsglen) == FAIL) { return -1; } } else { @@ -461,7 +516,7 @@ static efm_T * parse_efm_option(char_u *efm) } } - if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1) { + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == -1) { goto parse_efm_error; } if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) { -- cgit From 24c4d4e1258f8ca34eb581550776ef613c27a689 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 20:01:58 -0400 Subject: vim-patch:8.1.2072: "gk" moves to start of line instead of upwards Problem: "gk" moves to start of line instead of upwards. Solution: Fix off-by-one error. (Christian Brabandt, closes vim/vim#4969) https://github.com/vim/vim/commit/03ac52fc025790c474030ea556cec799400aa046 --- src/nvim/normal.c | 8 ++++---- src/nvim/testdir/test_normal.vim | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 4dfde96e94..d4065cc06e 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3932,11 +3932,11 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) while (dist--) { if (dir == BACKWARD) { - if ((long)curwin->w_curswant >= width2) - /* move back within line */ + if (curwin->w_curswant > width2) { + // move back within line curwin->w_curswant -= width2; - else { - /* to previous line */ + } else { + // to previous line if (curwin->w_cursor.lnum == 1) { retval = false; break; diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 532beb9c39..5ff2cf66c9 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2617,3 +2617,25 @@ Piece of Java close! endfunc + +func Test_normal_gk() + " needs 80 column new window + new + vert 80new + put =[repeat('x',90)..' {{{1', 'x {{{1'] + norm! gk + " In a 80 column wide terminal the window will be only 78 char + " (because Vim will leave space for the other window), + " but if the terminal is larger, it will be 80 chars, so verify the + " cursor column correctly. + call assert_equal(winwidth(0)+1, col('.')) + call assert_equal(winwidth(0)+1, virtcol('.')) + norm! j + call assert_equal(6, col('.')) + call assert_equal(6, virtcol('.')) + norm! gk + call assert_equal(95, col('.')) + call assert_equal(95, virtcol('.')) + bw! + bw! +endfunc -- cgit From 8d0bc3c18964db51fec4b204a122e946393f2d6d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 20:11:30 -0400 Subject: vim-patch:8.1.1758: count of g$ not used correctly when text is not wrapped Problem: Count of g$ not used correctly when text is not wrapped. Solution: Do use the count. (Christian Brabandt, closes vim/vim#4729, closes vim/vim#4566) https://github.com/vim/vim/commit/d5c8234517c18fa059b78f59eb96c35eda323dae --- src/nvim/normal.c | 6 ++++- src/nvim/testdir/test_normal.vim | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d4065cc06e..6b76082772 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6795,10 +6795,14 @@ static void nv_g_cmd(cmdarg_T *cap) } else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) clearopbeep(oap); } else { + if (cap->count1 > 1) { + // if it fails, let the cursor still move to the last char + cursor_down(cap->count1 - 1, false); + } i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; coladvance((colnr_T)i); - /* Make sure we stick in this column. */ + // Make sure we stick in this column. validate_virtcol(); curwin->w_curswant = curwin->w_virtcol; curwin->w_set_curswant = false; diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 5ff2cf66c9..0c71e2af4c 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2618,6 +2618,61 @@ Piece of Java close! endfunc +fun! Test_normal_gdollar_cmd() + if !has("jumplist") + return + endif + " Tests for g cmds + call Setup_NewWindow() + " Make long lines that will wrap + %s/$/\=repeat(' foobar', 10)/ + 20vsp + set wrap + " Test for g$ with count + norm! gg + norm! 0vg$y + call assert_equal(20, col("'>")) + call assert_equal('1 foobar foobar foob', getreg(0)) + norm! gg + norm! 0v4g$y + call assert_equal(72, col("'>")) + call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.."\n", getreg(0)) + norm! gg + norm! 0v6g$y + call assert_equal(40, col("'>")) + call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '2 foobar foobar foobar foobar foobar foo', getreg(0)) + set nowrap + " clean up + norm! gg + norm! 0vg$y + call assert_equal(20, col("'>")) + call assert_equal('1 foobar foobar foob', getreg(0)) + norm! gg + norm! 0v4g$y + call assert_equal(20, col("'>")) + call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '4 foobar foobar foob', getreg(0)) + norm! gg + norm! 0v6g$y + call assert_equal(20, col("'>")) + call assert_equal('1 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '2 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '3 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '4 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '5 foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar'.. "\n".. + \ '6 foobar foobar foob', getreg(0)) + " Move to last line, also down movement is not possible, should still move + " the cursor to the last visible char + norm! G + norm! 0v6g$y + call assert_equal(20, col("'>")) + call assert_equal('100 foobar foobar fo', getreg(0)) + bw! +endfunc + func Test_normal_gk() " needs 80 column new window new -- cgit From 17e96d96bac4ea9074a337b263fe85d4755106b6 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 22:53:20 -0400 Subject: vim-patch:8.1.0514: CTRL-W ^ does not work when alternate buffer has no name Problem: CTRL-W ^ does not work when alternate buffer has no name. Solution: Use another method to split and edit the alternate buffer. (Jason Franklin) https://github.com/vim/vim/commit/1bbb61948342b5cf6e363629f145c65eb455c388 --- src/nvim/normal.c | 5 +- src/nvim/testdir/test_normal.vim | 163 +++++++++++++++++++++-------------- src/nvim/testdir/test_window_cmd.vim | 60 +++++++++++-- src/nvim/window.c | 16 +++- 4 files changed, 171 insertions(+), 73 deletions(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 6b76082772..e32b738c7e 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4680,9 +4680,8 @@ static void nv_ctrlo(cmdarg_T *cap) } } -/* - * CTRL-^ command, short for ":e #" - */ +// CTRL-^ command, short for ":e #". Works even when the alternate buffer is +// not named. static void nv_hat(cmdarg_T *cap) { if (!checkclearopq(cap->oap)) diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 0c71e2af4c..b3e43640bb 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2,12 +2,12 @@ source shared.vim -func! Setup_NewWindow() +func Setup_NewWindow() 10new call setline(1, range(1,100)) endfunc -func! MyFormatExpr() +func MyFormatExpr() " Adds '->$' at lines having numbers followed by trailing whitespace for ln in range(v:lnum, v:lnum+v:count-1) let line = getline(ln) @@ -17,7 +17,7 @@ func! MyFormatExpr() endfor endfunc -func! CountSpaces(type, ...) +func CountSpaces(type, ...) " for testing operatorfunc " will count the number of spaces " and return the result in g:a @@ -37,7 +37,7 @@ func! CountSpaces(type, ...) let @@ = reg_save endfunc -func! OpfuncDummy(type, ...) +func OpfuncDummy(type, ...) " for testing operatorfunc let g:opt=&linebreak @@ -81,7 +81,7 @@ fun! Test_normal00_optrans() bw! endfunc -func! Test_normal01_keymodel() +func Test_normal01_keymodel() call Setup_NewWindow() " Test 1: depending on 'keymodel' does something different 50 @@ -115,7 +115,7 @@ func! Test_normal01_keymodel() bw! endfunc -func! Test_normal02_selectmode() +func Test_normal02_selectmode() " some basic select mode tests call Setup_NewWindow() 50 @@ -129,7 +129,7 @@ func! Test_normal02_selectmode() bw! endfunc -func! Test_normal02_selectmode2() +func Test_normal02_selectmode2() " some basic select mode tests call Setup_NewWindow() 50 @@ -139,7 +139,7 @@ func! Test_normal02_selectmode2() bw! endfunc -func! Test_normal03_join() +func Test_normal03_join() " basic join test call Setup_NewWindow() 50 @@ -159,7 +159,7 @@ func! Test_normal03_join() bw! endfunc -func! Test_normal04_filter() +func Test_normal04_filter() " basic filter test " only test on non windows platform if has('win32') @@ -185,7 +185,7 @@ func! Test_normal04_filter() bw! endfunc -func! Test_normal05_formatexpr() +func Test_normal05_formatexpr() " basic formatexpr test call Setup_NewWindow() %d_ @@ -222,7 +222,7 @@ func Test_normal05_formatexpr_setopt() set formatexpr= endfunc -func! Test_normal06_formatprg() +func Test_normal06_formatprg() " basic test for formatprg " only test on non windows platform if has('win32') @@ -256,7 +256,7 @@ func! Test_normal06_formatprg() call delete('Xsed_format.sh') endfunc -func! Test_normal07_internalfmt() +func Test_normal07_internalfmt() " basic test for internal formmatter to textwidth of 12 let list=range(1,11) call map(list, 'v:val." "') @@ -270,7 +270,7 @@ func! Test_normal07_internalfmt() bw! endfunc -func! Test_normal08_fold() +func Test_normal08_fold() " basic tests for foldopen/folddelete if !has("folding") return @@ -329,7 +329,7 @@ func! Test_normal08_fold() bw! endfunc -func! Test_normal09_operatorfunc() +func Test_normal09_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -359,7 +359,7 @@ func! Test_normal09_operatorfunc() bw! endfunc -func! Test_normal09a_operatorfunc() +func Test_normal09a_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -385,7 +385,7 @@ func! Test_normal09a_operatorfunc() unlet! g:opt endfunc -func! Test_normal10_expand() +func Test_normal10_expand() " Test for expand() 10new call setline(1, ['1', 'ifooar,,cbar']) @@ -420,7 +420,7 @@ func! Test_normal10_expand() bw! endfunc -func! Test_normal11_showcmd() +func Test_normal11_showcmd() " test for 'showcmd' 10new exe "norm! ofoobar\" @@ -435,7 +435,7 @@ func! Test_normal11_showcmd() bw! endfunc -func! Test_normal12_nv_error() +func Test_normal12_nv_error() " Test for nv_error 10new call setline(1, range(1,5)) @@ -445,7 +445,7 @@ func! Test_normal12_nv_error() bw! endfunc -func! Test_normal13_help() +func Test_normal13_help() " Test for F1 call assert_equal(1, winnr()) call feedkeys("\", 'txi') @@ -454,7 +454,7 @@ func! Test_normal13_help() bw! endfunc -func! Test_normal14_page() +func Test_normal14_page() " basic test for Ctrl-F and Ctrl-B call Setup_NewWindow() exe "norm! \" @@ -488,7 +488,7 @@ func! Test_normal14_page() bw! endfunc -func! Test_normal14_page_eol() +func Test_normal14_page_eol() 10new norm oxxxxxxx exe "norm 2\" @@ -497,7 +497,7 @@ func! Test_normal14_page_eol() bw! endfunc -func! Test_normal15_z_scroll_vert() +func Test_normal15_z_scroll_vert() " basic test for z commands that scroll the window call Setup_NewWindow() 100 @@ -586,7 +586,7 @@ func! Test_normal15_z_scroll_vert() bw! endfunc -func! Test_normal16_z_scroll_hor() +func Test_normal16_z_scroll_hor() " basic test for z commands that scroll the window 10new 15vsp @@ -652,7 +652,7 @@ func! Test_normal16_z_scroll_hor() bw! endfunc -func! Test_normal17_z_scroll_hor2() +func Test_normal17_z_scroll_hor2() " basic test for z commands that scroll the window " using 'sidescrolloff' setting 10new @@ -719,7 +719,7 @@ func! Test_normal17_z_scroll_hor2() bw! endfunc -func! Test_normal18_z_fold() +func Test_normal18_z_fold() " basic tests for foldopen/folddelete if !has("folding") return @@ -1090,7 +1090,7 @@ func! Test_normal18_z_fold() bw! endfunc -func! Test_normal19_z_spell() +func Test_normal19_z_spell() if !has("spell") || !has('syntax') return endif @@ -1245,7 +1245,7 @@ func! Test_normal19_z_spell() bw! endfunc -func! Test_normal20_exmode() +func Test_normal20_exmode() if !has("unix") " Reading from redirected file doesn't work on MS-Windows return @@ -1263,24 +1263,38 @@ func! Test_normal20_exmode() bw! endfunc -func! Test_normal21_nv_hat() - set hidden - new - " to many buffers opened already, will not work - "call assert_fails(":b#", 'E23') - "call assert_equal('', @#) - e Xfoobar - e Xfile2 - call feedkeys("\", 't') - call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t')) - call feedkeys("f\", 't') - call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t')) - " clean up - set nohidden - bw! +func Test_normal21_nv_hat() + + " Edit a fresh file and wipe the buffer list so that there is no alternate + " file present. Next, check for the expected command failures. + edit Xfoo | %bw + call assert_fails(':buffer #', 'E86') + call assert_fails(':execute "normal! \"', 'E23') + + " Test for the expected behavior when switching between two named buffers. + edit Xfoo | edit Xbar + call feedkeys("\", 'tx') + call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t')) + call feedkeys("\", 'tx') + call assert_equal('Xbar', fnamemodify(bufname('%'), ':t')) + + " Test for the expected behavior when only one buffer is named. + enew | let l:nr = bufnr('%') + call feedkeys("\", 'tx') + call assert_equal('Xbar', fnamemodify(bufname('%'), ':t')) + call feedkeys("\", 'tx') + call assert_equal('', bufname('%')) + call assert_equal(l:nr, bufnr('%')) + + " Test that no action is taken by "" when an operator is pending. + edit Xfoo + call feedkeys("ci\", 'tx') + call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t')) + + %bw! endfunc -func! Test_normal22_zet() +func Test_normal22_zet() " Test for ZZ " let shell = &shell " let &shell = 'sh' @@ -1308,7 +1322,7 @@ func! Test_normal22_zet() " let &shell = shell endfunc -func! Test_normal23_K() +func Test_normal23_K() " Test for K command new call append(0, ['helphelp.txt', 'man', 'aa%bb', 'cc|dd']) @@ -1373,7 +1387,7 @@ func! Test_normal23_K() bw! endfunc -func! Test_normal24_rot13() +func Test_normal24_rot13() " Testing for g?? g?g? new call append(0, 'abcdefghijklmnopqrstuvwxyzäüö') @@ -1387,7 +1401,7 @@ func! Test_normal24_rot13() bw! endfunc -func! Test_normal25_tag() +func Test_normal25_tag() " Testing for CTRL-] g CTRL-] g] " CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-] h @@ -1454,7 +1468,7 @@ func! Test_normal25_tag() helpclose endfunc -func! Test_normal26_put() +func Test_normal26_put() " Test for ]p ]P [p and [P new call append(0, ['while read LINE', 'do', ' ((count++))', ' if [ $? -ne 0 ]; then', " echo 'Error writing file'", ' fi', 'done']) @@ -1473,7 +1487,7 @@ func! Test_normal26_put() bw! endfunc -func! Test_normal27_bracket() +func Test_normal27_bracket() " Test for [' [` ]' ]` call Setup_NewWindow() 1,21s/.\+/ & b/ @@ -1524,7 +1538,7 @@ func! Test_normal27_bracket() bw! endfunc -func! Test_normal28_parenthesis() +func Test_normal28_parenthesis() " basic testing for ( and ) new call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) @@ -1718,7 +1732,7 @@ fun! Test_normal31_r_cmd() bw! endfunc -func! Test_normal32_g_cmd1() +func Test_normal32_g_cmd1() " Test for g*, g# new call append(0, ['abc.x_foo', 'x_foobar.abc']) @@ -1849,7 +1863,7 @@ fun! Test_normal33_g_cmd2() bw! endfunc -func! Test_g_ctrl_g() +func Test_g_ctrl_g() new let a = execute(":norm! g\") @@ -2139,7 +2153,7 @@ fun! Test_normal41_insert_reg() bw! endfunc -func! Test_normal42_halfpage() +func Test_normal42_halfpage() " basic test for Ctrl-D and Ctrl-U call Setup_NewWindow() call assert_equal(5, &scroll) @@ -2207,7 +2221,7 @@ fun! Test_normal43_textobject1() bw! endfunc -func! Test_normal44_textobjects2() +func Test_normal44_textobjects2() " basic testing for is and as text objects new call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) @@ -2262,7 +2276,7 @@ func! Test_normal44_textobjects2() bw! endfunc -func! Test_normal45_drop() +func Test_normal45_drop() if !has('dnd') " The ~ register does not exist call assert_beeps('norm! "~') @@ -2280,7 +2294,7 @@ func! Test_normal45_drop() bw! endfunc -func! Test_normal46_ignore() +func Test_normal46_ignore() new " How to test this? " let's just for now test, that the buffer @@ -2299,7 +2313,7 @@ func! Test_normal46_ignore() bw! endfunc -func! Test_normal47_visual_buf_wipe() +func Test_normal47_visual_buf_wipe() " This was causing a crash or ml_get error. enew! call setline(1,'xxx') @@ -2313,7 +2327,7 @@ func! Test_normal47_visual_buf_wipe() set nomodified endfunc -func! Test_normal47_autocmd() +func Test_normal47_autocmd() " disabled, does not seem to be possible currently throw "Skipped: not possible to test cursorhold autocmd while waiting for input in normal_cmd" new @@ -2331,14 +2345,14 @@ func! Test_normal47_autocmd() bw! endfunc -func! Test_normal48_wincmd() +func Test_normal48_wincmd() new exe "norm! \c" call assert_equal(1, winnr('$')) call assert_fails(":norm! \c", "E444") endfunc -func! Test_normal49_counts() +func Test_normal49_counts() new call setline(1, 'one two three four five six seven eight nine ten') 1 @@ -2347,7 +2361,7 @@ func! Test_normal49_counts() bw! endfunc -func! Test_normal50_commandline() +func Test_normal50_commandline() if !has("timers") || !has("cmdline_hist") || !has("vertsplit") return endif @@ -2378,7 +2392,7 @@ func! Test_normal50_commandline() bw! endfunc -func! Test_normal51_FileChangedRO() +func Test_normal51_FileChangedRO() if !has("autocmd") return endif @@ -2395,7 +2409,7 @@ func! Test_normal51_FileChangedRO() call delete("Xreadonly.log") endfunc -func! Test_normal52_rl() +func Test_normal52_rl() if !has("rightleft") return endif @@ -2428,7 +2442,7 @@ func! Test_normal52_rl() bw! endfunc -func! Test_normal53_digraph() +func Test_normal53_digraph() if !has('digraphs') return endif @@ -2516,6 +2530,29 @@ func Test_changelist() let &ul = save_ul endfunc +func Test_nv_hat_count() + %bwipeout! + let l:nr = bufnr('%') + 1 + call assert_fails(':execute "normal! ' . l:nr . '\"', 'E92') + + edit Xfoo + let l:foo_nr = bufnr('Xfoo') + + edit Xbar + let l:bar_nr = bufnr('Xbar') + + " Make sure we are not just using the alternate file. + edit Xbaz + + call feedkeys(l:foo_nr . "\", 'tx') + call assert_equal('Xfoo', fnamemodify(bufname('%'), ':t')) + + call feedkeys(l:bar_nr . "\", 'tx') + call assert_equal('Xbar', fnamemodify(bufname('%'), ':t')) + + %bwipeout! +endfunc + func Test_delete_until_paragraph() new normal grádv} diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index c87c0a0af4..9f899fba04 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -117,15 +117,65 @@ func Test_window_vertical_split() bw endfunc +" Test the ":wincmd ^" and "^" commands. func Test_window_split_edit_alternate() - e Xa - e Xb + " Test for failure when the alternate buffer/file no longer exists. + edit Xfoo | %bw + call assert_fails(':wincmd ^', 'E23') + + " Test for the expected behavior when we have two named buffers. + edit Xfoo | edit Xbar wincmd ^ - call assert_equal('Xa', bufname(winbufnr(1))) - call assert_equal('Xb', bufname(winbufnr(2))) + call assert_equal('Xfoo', bufname(winbufnr(1))) + call assert_equal('Xbar', bufname(winbufnr(2))) + only - bw Xa Xb + " Test for the expected behavior when the alternate buffer is not named. + enew | let l:nr1 = bufnr('%') + edit Xfoo | let l:nr2 = bufnr('%') + wincmd ^ + call assert_equal(l:nr1, winbufnr(1)) + call assert_equal(l:nr2, winbufnr(2)) + only + + " Test the Normal mode command. + call feedkeys("\\", 'tx') + call assert_equal(l:nr2, winbufnr(1)) + call assert_equal(l:nr1, winbufnr(2)) + + %bw! +endfunc + +" Test the ":[count]wincmd ^" and "[count]^" commands. +func Test_window_split_edit_bufnr() + + %bwipeout + let l:nr = bufnr('%') + 1 + call assert_fails(':execute "normal! ' . l:nr . '\\"', 'E92') + call assert_fails(':' . l:nr . 'wincmd ^', 'E16') + call assert_fails(':0wincmd ^', 'E16') + + edit Xfoo | edit Xbar | edit Xbaz + let l:foo_nr = bufnr('Xfoo') + let l:bar_nr = bufnr('Xbar') + let l:baz_nr = bufnr('Xbaz') + + call feedkeys(l:foo_nr . "\\", 'tx') + call assert_equal('Xfoo', bufname(winbufnr(1))) + call assert_equal('Xbaz', bufname(winbufnr(2))) + only + + call feedkeys(l:bar_nr . "\\", 'tx') + call assert_equal('Xbar', bufname(winbufnr(1))) + call assert_equal('Xfoo', bufname(winbufnr(2))) + only + + execute l:baz_nr . 'wincmd ^' + call assert_equal('Xbaz', bufname(winbufnr(1))) + call assert_equal('Xbar', bufname(winbufnr(2))) + + %bw! endfunc func Test_window_preview() diff --git a/src/nvim/window.c b/src/nvim/window.c index 1e6de73549..d7df048588 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -131,8 +131,20 @@ do_window ( case '^': CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("split #", (char_u *)cbuf, sizeof(cbuf), Prenum); - do_cmdline_cmd(cbuf); + + if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) { + if (Prenum == 0) { + EMSG(_(e_noalt)); + } else { + EMSGN(_("E92: Buffer %" PRId64 " not found"), Prenum); + } + break; + } + + if (!curbuf_locked() && win_split(0, 0) == OK) { + (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : Prenum, + (linenr_T)0, GETF_ALT, false); + } break; /* open new window */ -- cgit From 14f3287b9813f5a3bb88596e859a2fb6c6b2e4ae Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 23:28:30 -0400 Subject: vim-patch:8.1.0517: Test_window_split_edit_alternate() fails on AppVeyor Problem: Test_window_split_edit_alternate() fails on AppVeyor. Solution: Disable the failing part for now. https://github.com/vim/vim/commit/d42333d8e9f6c157884f4f1acb458aa992f94f3d --- src/nvim/testdir/test_window_cmd.vim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 9f899fba04..4c93e990ee 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -139,10 +139,13 @@ func Test_window_split_edit_alternate() call assert_equal(l:nr2, winbufnr(2)) only - " Test the Normal mode command. - call feedkeys("\\", 'tx') - call assert_equal(l:nr2, winbufnr(1)) - call assert_equal(l:nr1, winbufnr(2)) + " FIXME: this currently fails on AppVeyor, but passes locally + if !has('win32') + " Test the Normal mode command. + call feedkeys("\\", 'tx') + call assert_equal(l:nr2, winbufnr(1)) + call assert_equal(l:nr1, winbufnr(2)) + endif %bw! endfunc -- cgit From e8144d204c02be1fb4e248420610ec98074ae9f9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Sep 2019 23:30:23 -0400 Subject: vim-patch:8.1.0518: Test_window_split_edit_bufnr() fails on AppVeyor Problem: Test_window_split_edit_bufnr() fails on AppVeyor. Solution: Disable the failing part for now. https://github.com/vim/vim/commit/8617b401599451187fa0c0561a84944978536a90 --- src/nvim/testdir/test_window_cmd.vim | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 4c93e990ee..72f1baf39e 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -164,19 +164,22 @@ func Test_window_split_edit_bufnr() let l:bar_nr = bufnr('Xbar') let l:baz_nr = bufnr('Xbaz') - call feedkeys(l:foo_nr . "\\", 'tx') - call assert_equal('Xfoo', bufname(winbufnr(1))) - call assert_equal('Xbaz', bufname(winbufnr(2))) - only - - call feedkeys(l:bar_nr . "\\", 'tx') - call assert_equal('Xbar', bufname(winbufnr(1))) - call assert_equal('Xfoo', bufname(winbufnr(2))) - only - - execute l:baz_nr . 'wincmd ^' - call assert_equal('Xbaz', bufname(winbufnr(1))) - call assert_equal('Xbar', bufname(winbufnr(2))) + " FIXME: this currently fails on AppVeyor, but passes locally + if !has('win32') + call feedkeys(l:foo_nr . "\\", 'tx') + call assert_equal('Xfoo', bufname(winbufnr(1))) + call assert_equal('Xbaz', bufname(winbufnr(2))) + only + + call feedkeys(l:bar_nr . "\\", 'tx') + call assert_equal('Xbar', bufname(winbufnr(1))) + call assert_equal('Xfoo', bufname(winbufnr(2))) + only + + execute l:baz_nr . 'wincmd ^' + call assert_equal('Xbaz', bufname(winbufnr(1))) + call assert_equal('Xbar', bufname(winbufnr(2))) + endif %bw! endfunc -- cgit From 6ed20ff25cd738ab2b9e79af8e3a9c37ba52dbcf Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 May 2019 09:08:31 -0400 Subject: vim-patch:8.1.1327: unnecessary scroll after horizontal split Problem: Unnecessary scroll after horizontal split. Solution: Don't adjust to fraction if all the text fits in the window. (Martin Kunev, closes vim/vim#4367) https://github.com/vim/vim/commit/a9b2535f44f3265940a18d08520a9ad4ef7bda82 --- src/nvim/testdir/test_window_cmd.vim | 36 ++++++++++++++++++++++++++++++++++++ src/nvim/window.c | 7 +++++-- 2 files changed, 41 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 72f1baf39e..c41f4f9412 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -756,6 +756,42 @@ func Test_relative_cursor_second_line_after_resize() let &so = so_save endfunc +func Test_split_noscroll() + let so_save = &so + new + only + + " Make sure windows can hold all content after split. + for i in range(1, 20) + wincmd + + redraw! + endfor + + call setline (1, range(1, 8)) + normal 100% + split + + 1wincmd w + let winid1 = win_getid() + let info1 = getwininfo(winid1)[0] + + 2wincmd w + let winid2 = win_getid() + let info2 = getwininfo(winid2)[0] + + call assert_equal(1, info1.topline) + call assert_equal(1, info2.topline) + + " Restore original state. + for i in range(1, 20) + wincmd - + redraw! + endfor + only! + bwipe! + let &so = so_save +endfunc + " Tests for the winnr() function func Test_winnr() only | tabonly diff --git a/src/nvim/window.c b/src/nvim/window.c index d7df048588..1f23646bdf 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5610,10 +5610,13 @@ void scroll_to_fraction(win_T *wp, int prev_height) int sline, line_size; int height = wp->w_height_inner; - // Don't change w_topline when height is zero. Don't set w_topline when - // 'scrollbind' is set and this isn't the current window. + // Don't change w_topline in any of these cases: + // - window height is 0 + // - 'scrollbind' is set and this isn't the current window + // - window height is sufficient to display the whole buffer if (height > 0 && (!wp->w_p_scb || wp == curwin) + && (height < wp->w_buffer->b_ml.ml_line_count) ) { /* * Find a value for w_topline that shows the cursor at the same -- cgit From 90c2abc53faed9aab8ad71395068e7b09d6dea85 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 18 May 2019 18:54:25 -0400 Subject: vim-patch:8.1.1347: fractional scroll position not restored after closing window Problem: Fractional scroll position not restored after closing window. Solution: Do restore fraction if topline is not one. https://github.com/vim/vim/commit/bd2d68c2f42c7689f681aeaf82606d17f8a0312f --- src/nvim/testdir/test_window_cmd.vim | 30 +++++++++++++++--------------- src/nvim/window.c | 5 +++-- 2 files changed, 18 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index c41f4f9412..43c1f06c44 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -758,16 +758,8 @@ endfunc func Test_split_noscroll() let so_save = &so - new - only - - " Make sure windows can hold all content after split. - for i in range(1, 20) - wincmd + - redraw! - endfor - - call setline (1, range(1, 8)) + enew + call setline(1, range(1, 8)) normal 100% split @@ -782,12 +774,20 @@ func Test_split_noscroll() call assert_equal(1, info1.topline) call assert_equal(1, info2.topline) - " Restore original state. - for i in range(1, 20) - wincmd - - redraw! - endfor + " window that fits all lines by itself, but not when split: closing other + " window should restore fraction. only! + call setline(1, range(1, &lines - 10)) + exe &lines / 4 + let winid1 = win_getid() + let info1 = getwininfo(winid1)[0] + call assert_equal(1, info1.topline) + new + redraw + close + let info1 = getwininfo(winid1)[0] + call assert_equal(1, info1.topline) + bwipe! let &so = so_save endfunc diff --git a/src/nvim/window.c b/src/nvim/window.c index 1f23646bdf..4d8eaa9dcc 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5613,10 +5613,11 @@ void scroll_to_fraction(win_T *wp, int prev_height) // Don't change w_topline in any of these cases: // - window height is 0 // - 'scrollbind' is set and this isn't the current window - // - window height is sufficient to display the whole buffer + // - window height is sufficient to display the whole buffer and first line + // is visible. if (height > 0 && (!wp->w_p_scb || wp == curwin) - && (height < wp->w_buffer->b_ml.ml_line_count) + && (height < wp->w_buffer->b_ml.ml_line_count || wp->w_topline > 1) ) { /* * Find a value for w_topline that shows the cursor at the same -- cgit From 8732cce3150869d116a912ffea7686bfa73a6e0b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Sep 2019 20:06:01 -0400 Subject: vim-patch:8.1.2074: test for SafeState autocommand is a bit flaky Problem: Test for SafeState autocommand is a bit flaky. Solution: Add to list of flaky tests. https://github.com/vim/vim/commit/0d0c3ca007940cdb64ccbfd0e70846eedfe6a4a6 --- src/nvim/testdir/runtest.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 593ce6fcdc..8f5f3f82e7 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -282,6 +282,7 @@ endif " Names of flaky tests. let s:flaky_tests = [ + \ 'Test_autocmd_SafeState()', \ 'Test_cursorhold_insert()', \ 'Test_exit_callback_interval()', \ 'Test_map_timeout_with_timer_interrupt()', -- cgit From 56c860ac4a8897cdce8c7e6a9d238f0775de1979 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 26 Sep 2019 00:56:56 -0400 Subject: quickfix: fix pvs/v547 errors --- src/nvim/quickfix.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 5083e573d3..310b074cfb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1554,6 +1554,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, * Allocate a new location list */ static qf_info_T *ll_new_list(void) + FUNC_ATTR_NONNULL_RET { qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; @@ -5650,7 +5651,7 @@ void ex_cexpr(exarg_T *eap) // Get the location list for ":lhelpgrep" static qf_info_T *hgr_get_ll(bool *new_ll) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { win_T *wp; qf_info_T *qi; @@ -5670,9 +5671,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) } if (qi == NULL) { // Allocate a new location list for help text matches - if ((qi = ll_new_list()) == NULL) { - return NULL; - } + qi = ll_new_list(); *new_ll = true; } @@ -5810,9 +5809,6 @@ void ex_helpgrep(exarg_T *eap) if (eap->cmdidx == CMD_lhelpgrep) { qi = hgr_get_ll(&new_qi); - if (qi == NULL) { - return; - } } regmatch_T regmatch = { -- cgit From 74947203afedd0227ee383ce4396745b2d5f6c4f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 28 Sep 2019 14:45:35 -0400 Subject: vim-patch:8.1.2091: double free when memory allocation fails Problem: Double free when memory allocation fails. (Zu-Ming Jiang) Solution: Use VIM_CLEAR() instead of vim_free(). (closes vim/vim#4991) https://github.com/vim/vim/commit/0f1c6708fdf17bb9c7305b8af5d12189956195b6 --- src/nvim/getchar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 2469bb5baa..af642a8e11 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1165,12 +1165,12 @@ void free_typebuf(void) if (typebuf.tb_buf == typebuf_init) { internal_error("Free typebuf 1"); } else { - xfree(typebuf.tb_buf); + XFREE_CLEAR(typebuf.tb_buf); } if (typebuf.tb_noremap == noremapbuf_init) { internal_error("Free typebuf 2"); } else { - xfree(typebuf.tb_noremap); + XFREE_CLEAR(typebuf.tb_noremap); } } -- cgit From 8b67c8f8c6538f22c6b6868a2603109b6670874e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 28 Sep 2019 14:52:17 -0400 Subject: vim-patch:8.1.2095: leaking memory when getting item from dict Problem: Leaking memory when getting item from dict. Solution: Also free the key when not evaluating. https://github.com/vim/vim/commit/a893194d91a2942d4d54085d746ed137a9251b69 --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cb1dd1d631..e3e5bb9a90 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5746,13 +5746,13 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) goto failret; } item = tv_dict_item_alloc((const char *)key); - tv_clear(&tvkey); item->di_tv = tv; item->di_tv.v_lock = 0; if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); } } + tv_clear(&tvkey); if (**arg == '}') break; -- cgit From 655085204e36cdf91750db5e09ae3feb4884c670 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 29 Sep 2019 09:29:16 -0400 Subject: vim-patch:8.1.0230: directly checking 'buftype' value Problem: Directly checking 'buftype' value. Solution: Add the bt_normal() function. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/91335e5a67aaa9937e65f1e779b9f3f10fd33ee4 --- src/nvim/buffer.c | 7 +++++++ src/nvim/ex_docmd.c | 2 +- src/nvim/fileio.c | 2 +- src/nvim/quickfix.c | 6 +++--- 4 files changed, 12 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index b50c764ac3..e5b80693a4 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -5179,6 +5179,13 @@ bool bt_help(const buf_T *const buf) return buf != NULL && buf->b_help; } +// Return true if "buf" is a normal buffer, 'buftype' is empty. +bool bt_normal(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return buf != NULL && buf->b_p_bt[0] == NUL; +} + // Return true if "buf" is the quickfix buffer. bool bt_quickfix(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a6931f3acd..34b4c10d3e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9553,7 +9553,7 @@ put_view( */ if ((*flagp & SSOP_FOLDS) && wp->w_buffer->b_ffname != NULL - && (*wp->w_buffer->b_p_bt == NUL || bt_help(wp->w_buffer)) + && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) ) { if (put_folds(fd, wp) == FAIL) return FAIL; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 4cf42b41e9..0394639a16 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -4834,7 +4834,7 @@ buf_check_timestamp( if (buf->terminal || buf->b_ffname == NULL || buf->b_ml.ml_mfp == NULL - || *buf->b_p_bt != NUL + || !bt_normal(buf) || buf->b_saving || busy ) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 310b074cfb..0897367b2d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2140,7 +2140,7 @@ static win_T *qf_find_win_with_normal_buf(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_p_bt[0] == NUL) { + if (bt_normal(wp->w_buffer)) { return wp; } } @@ -2204,7 +2204,7 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, // Find a previous usable window win = curwin; do { - if (win->w_buffer->b_p_bt[0] == NUL) { + if (bt_normal(win->w_buffer)) { break; } if (win->w_prev == NULL) { @@ -2258,7 +2258,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) // Remember a usable window. if (altwin == NULL && !win->w_p_pvw - && win->w_buffer->b_p_bt[0] == NUL) { + && bt_normal(win->w_buffer)) { altwin = win; } } -- cgit From ada2ec441617077110e503c550dd3227eb9da072 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Sep 2019 22:58:42 -0400 Subject: vim-patch:8.1.0315: helpgrep with language doesn't work properly Problem: Helpgrep with language doesn't work properly. (Takuya Fujiwara) Solution: Check for the language earlier. (Hirohito Higashi) https://github.com/vim/vim/commit/c631f2df624954184509df49479d52ad7fe5233b --- src/nvim/quickfix.c | 29 ++++++++++++++++------------- src/nvim/testdir/test_quickfix.vim | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 0897367b2d..511fb037fb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4468,7 +4468,7 @@ void ex_vimgrep(exarg_T *eap) goto theend; } - /* Jump to first match. */ + // Jump to first match. if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, @@ -5767,14 +5767,14 @@ static void hgr_search_files_in_dir( } } -// Search for a pattern in all the help files in the 'runtimepath'. +// Search for a pattern in all the help files in the 'runtimepath' +// and add the matches to a quickfix list. +// 'lang' is the language specifier. If supplied, then only matches in the +// specified language are found. static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch, - char_u *arg) - FUNC_ATTR_NONNULL_ALL + const char_u *lang) + FUNC_ATTR_NONNULL_ARG(1, 2) { - // Check for a specified language - char_u *const lang = check_help_lang(arg); - // Go through all directories in 'runtimepath' char_u *p = p_rtp; while (*p != NUL && !got_int) { @@ -5811,6 +5811,8 @@ void ex_helpgrep(exarg_T *eap) qi = hgr_get_ll(&new_qi); } + // Check for a specified language + char_u *const lang = check_help_lang(eap->arg); regmatch_T regmatch = { .regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING), .rm_ic = false, @@ -5819,7 +5821,7 @@ void ex_helpgrep(exarg_T *eap) // Create a new quickfix list. qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep)); - hgr_search_in_rtp(qi, ®match, eap->arg); + hgr_search_in_rtp(qi, ®match, lang); vim_regfree(regmatch.regprog); @@ -5829,11 +5831,12 @@ void ex_helpgrep(exarg_T *eap) qi->qf_lists[qi->qf_curlist].qf_index = 1; } - if (p_cpo == empty_option) + if (p_cpo == empty_option) { p_cpo = save_cpo; - else - /* Darn, some plugin changed the value. */ + } else { + // Darn, some plugin changed the value. free_string_option(save_cpo); + } qf_list_changed(qi, qi->qf_curlist); qf_update_buffer(qi, NULL); @@ -5854,8 +5857,8 @@ void ex_helpgrep(exarg_T *eap) EMSG2(_(e_nomatch2), eap->arg); if (eap->cmdidx == CMD_lhelpgrep) { - /* If the help window is not opened or if it already points to the - * correct location list, then free the new location list. */ + // If the help window is not opened or if it already points to the + // correct location list, then free the new location list. if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) { if (new_qi) { ll_free_all(&qi); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 83ef3c2fce..597be0aa89 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -3315,6 +3315,20 @@ func Test_qfjump() call Xqfjump_tests('l') endfunc +" Test helpgrep with lang specifier +func Xtest_helpgrep_with_lang_specifier(cchar) + call s:setup_commands(a:cchar) + Xhelpgrep Vim@en + call assert_equal('help', &filetype) + call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr) + new | only +endfunc + +func Test_helpgrep_with_lang_specifier() + call Xtest_helpgrep_with_lang_specifier('c') + call Xtest_helpgrep_with_lang_specifier('l') +endfunc + " The following test used to crash Vim. " Open the location list window and close the regular window associated with " the location list. When the garbage collection runs now, it incorrectly -- cgit From 0c1be45ea0b7f6702816e18f7d02641a2df47970 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 2 Oct 2019 06:26:57 +0200 Subject: shell: improve displaying of pulse (#11130) - output "[...]" to indicate throttling is being used, instead of just an empty line - go to beginning of line after displaying the pulse, so that following output is displayed over it --- src/nvim/os/shell.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6956410401..f4377b1457 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -423,10 +423,11 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick > 1) ? '.' : ' '; pulse_msg[2] = (tick > 2) ? '.' : ' '; if (visit == 1) { - msg_putchar('\n'); + msg_puts("[...]\n"); } msg_putchar('\r'); // put cursor at start of line msg_puts(pulse_msg); + msg_putchar('\r'); ui_flush(); return true; } -- cgit From 4518f230fa84e66737f6fc313fb669984974a1fd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 2 Oct 2019 00:06:37 -0400 Subject: vim-patch:8.1.0330: the qf_add_entries() function is too long Problem: The qf_add_entries() function is too long. Solution: Split in two parts. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/6f6ef7c1951b080843f3da049d3f5d0679de7348 --- src/nvim/quickfix.c | 136 +++++++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 511fb037fb..c5e8d4b490 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5030,15 +5030,86 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) return status; } +// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the +// items in the dict 'd'. +static int qf_add_entry_from_dict( + qf_info_T *qi, + int qf_idx, + const dict_T *d, + bool first_entry) + FUNC_ATTR_NONNULL_ALL +{ + static bool did_bufnr_emsg; + + if (first_entry) { + did_bufnr_emsg = false; + } + + char *const filename = tv_dict_get_string(d, "filename", true); + char *const module = tv_dict_get_string(d, "module", true); + int bufnum = (int)tv_dict_get_number(d, "bufnr"); + const long lnum = (long)tv_dict_get_number(d, "lnum"); + const int col = (int)tv_dict_get_number(d, "col"); + const char_u vcol = (char_u)tv_dict_get_number(d, "vcol"); + const int nr = (int)tv_dict_get_number(d, "nr"); + const char *const type = tv_dict_get_string(d, "type", false); + char *const pattern = tv_dict_get_string(d, "pattern", true); + char *text = tv_dict_get_string(d, "text", true); + if (text == NULL) { + text = xcalloc(1, 1); + } + bool valid = true; + if ((filename == NULL && bufnum == 0) + || (lnum == 0 && pattern == NULL)) { + valid = false; + } + + // Mark entries with non-existing buffer number as not valid. Give the + // error message only once. + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { + if (!did_bufnr_emsg) { + did_bufnr_emsg = true; + EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum); + } + valid = false; + bufnum = 0; + } + + // If the 'valid' field is present it overrules the detected value. + if (tv_dict_find(d, "valid", -1) != NULL) { + valid = tv_dict_get_number(d, "valid"); + } + + const int status = qf_add_entry(qi, + qf_idx, + NULL, // dir + (char_u *)filename, + (char_u *)module, + bufnum, + (char_u *)text, + lnum, + col, + vcol, // vis_col + (char_u *)pattern, // search pattern + nr, + (char_u)(type == NULL ? NUL : *type), + valid); + + xfree(filename); + xfree(module); + xfree(pattern); + xfree(text); + + return status; +} + /// Add list of entries to quickfix/location list. Each list entry is /// a dictionary with item information. static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char_u *title, int action) { - dict_T *d; qfline_T *old_last = NULL; int retval = OK; - bool did_bufnr_emsg = false; if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list @@ -5057,68 +5128,13 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, continue; // Skip non-dict items. } - d = TV_LIST_ITEM_TV(li)->vval.v_dict; + const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict; if (d == NULL) { continue; } - char *const filename = tv_dict_get_string(d, "filename", true); - char *const module = tv_dict_get_string(d, "module", true); - int bufnum = (int)tv_dict_get_number(d, "bufnr"); - long lnum = (long)tv_dict_get_number(d, "lnum"); - int col = (int)tv_dict_get_number(d, "col"); - char_u vcol = (char_u)tv_dict_get_number(d, "vcol"); - int nr = (int)tv_dict_get_number(d, "nr"); - const char *type_str = tv_dict_get_string(d, "type", false); - const char_u type = (char_u)(uint8_t)(type_str == NULL ? NUL : *type_str); - char *const pattern = tv_dict_get_string(d, "pattern", true); - char *text = tv_dict_get_string(d, "text", true); - if (text == NULL) { - text = xcalloc(1, 1); - } - bool valid = true; - if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) { - valid = false; - } - - /* Mark entries with non-existing buffer number as not valid. Give the - * error message only once. */ - if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { - if (!did_bufnr_emsg) { - did_bufnr_emsg = TRUE; - EMSGN(_("E92: Buffer %" PRId64 " not found"), bufnum); - } - valid = false; - bufnum = 0; - } - - // If the 'valid' field is present it overrules the detected value. - if (tv_dict_find(d, "valid", -1) != NULL) { - valid = (int)tv_dict_get_number(d, "valid"); - } - - int status = qf_add_entry(qi, - qf_idx, - NULL, // dir - (char_u *)filename, - (char_u *)module, - bufnum, - (char_u *)text, - lnum, - col, - vcol, // vis_col - (char_u *)pattern, // search pattern - nr, - type, - valid); - - xfree(filename); - xfree(module); - xfree(pattern); - xfree(text); - - if (status == FAIL) { - retval = FAIL; + retval = qf_add_entry_from_dict(qi, qf_idx, d, li == tv_list_first(list)); + if (retval == FAIL) { break; } }); -- cgit From f96d1e6bc416fe0d1d11321234637695ff0e514e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 3 Oct 2019 08:04:24 +0200 Subject: tui: fix handling of bg response after suspend (#11145) `tui_terminal_after_startup` gets called right after resuming from suspending (via `Ctrl-z`) already (not delayed as with the startup itself), and would set `waiting_for_bg_response` to false then directly. This results in the terminal response not being processed then anymore, and leaking into Neovim itself. This changes it to try 5 times always, which means that it typically would stop after a few characters of input from the user typically, e.g. with tmux, which does not send a reply. While it might be better to have something based on the time (e.g. only wait for max 1s), this appears to be easier to do. Fixes regression in 8a4ae3d. --- src/nvim/tui/input.c | 10 +++++++--- src/nvim/tui/input.h | 2 +- src/nvim/tui/tui.c | 7 +------ 3 files changed, 9 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 876f00e03e..844bc0db40 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -27,7 +27,7 @@ void tinput_init(TermInput *input, Loop *loop) input->loop = loop; input->paste = 0; input->in_fd = 0; - input->waiting_for_bg_response = false; + input->waiting_for_bg_response = 0; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); uv_mutex_init(&input->key_buffer_mutex); uv_cond_init(&input->key_buffer_cond); @@ -444,7 +444,7 @@ static void set_bg_deferred(void **argv) // [1] https://en.wikipedia.org/wiki/Luma_%28video%29 static bool handle_background_color(TermInput *input) { - if (!input->waiting_for_bg_response) { + if (input->waiting_for_bg_response <= 0) { return false; } size_t count = 0; @@ -465,9 +465,13 @@ static bool handle_background_color(TermInput *input) header_size = 10; num_components = 4; } else { + input->waiting_for_bg_response--; + if (input->waiting_for_bg_response == 0) { + DLOG("did not get a response for terminal background query"); + } return false; } - input->waiting_for_bg_response = false; + input->waiting_for_bg_response = 0; rbuffer_consumed(input->read_stream.buffer, header_size); RBUFFER_EACH(input->read_stream.buffer, c, i) { count = i + 1; diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 49ae32f00e..77bd6fa132 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -12,7 +12,7 @@ typedef struct term_input { // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk int8_t paste; bool waiting; - bool waiting_for_bg_response; + int8_t waiting_for_bg_response; TermKey *tk; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 956d4eb9da..150862bb18 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -296,7 +296,7 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); // Ask the terminal to send us the background color. - data->input.waiting_for_bg_response = true; + data->input.waiting_for_bg_response = 5; unibi_out_ext(ui, data->unibi_ext.get_bg); // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); @@ -366,11 +366,6 @@ static void tui_terminal_after_startup(UI *ui) // 2.3 bug(?) which caused slow drawing during startup. #7649 unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); flush_buf(ui); - - if (data->input.waiting_for_bg_response) { - DLOG("did not get a response for terminal background query"); - data->input.waiting_for_bg_response = false; - } } static void tui_terminal_stop(UI *ui) -- cgit From 8d68a37c5a4d3258ccee1b5d6cdeda8f40d024c5 Mon Sep 17 00:00:00 2001 From: Zach Wegner Date: Thu, 3 Oct 2019 01:06:05 -0500 Subject: refactor: wrap common plines() usage in plines_win_full() #11141 --- src/nvim/misc1.c | 42 ++++++++++++++++++++++++++++-------------- src/nvim/move.c | 53 +++++++++++++++++------------------------------------ 2 files changed, 45 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index ab3520dd73..c1de7ab9a4 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -484,25 +484,39 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) return lines; } +/// Get the number of screen lines lnum takes up. This takes care of +/// both folds and topfill, and limits to the current window height. +/// +/// @param[in] wp window line is in +/// @param[in] lnum line number +/// @param[out] nextp if not NULL, the line after a fold +/// @param[out] foldedp if not NULL, whether lnum is on a fold +/// @param[in] cache whether to use the window's cache for folds +/// +/// @return the total number of screen lines +int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, + bool *const foldedp, const bool cache) +{ + bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL); + if (foldedp) { + *foldedp = folded; + } + if (folded) { + return 1; + } else if (lnum == wp->w_topline) { + return plines_win_nofill(wp, lnum, true) + wp->w_topfill; + } + return plines_win(wp, lnum, true); +} + int plines_m_win(win_T *wp, linenr_T first, linenr_T last) { int count = 0; while (first <= last) { - // Check if there are any really folded lines, but also included lines - // that are maybe folded. - linenr_T x = foldedCount(wp, first, NULL); - if (x > 0) { - ++count; /* count 1 for "+-- folded" line */ - first += x; - } else { - if (first == wp->w_topline) { - count += plines_win_nofill(wp, first, true) + wp->w_topfill; - } else { - count += plines_win(wp, first, true); - } - first++; - } + linenr_T next = first; + count += plines_win_full(wp, first, &next, NULL, false); + first = next + 1; } return count; } diff --git a/src/nvim/move.c b/src/nvim/move.c index e6fee9999f..efbc548620 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -65,17 +65,10 @@ static void comp_botline(win_T *wp) done = 0; } - for (; lnum <= wp->w_buffer->b_ml.ml_line_count; ++lnum) { - int n; + for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) { linenr_T last = lnum; - bool folded = hasFoldingWin(wp, lnum, NULL, &last, true, NULL); - if (folded) { - n = 1; - } else if (lnum == wp->w_topline) { - n = plines_win_nofill(wp, lnum, true) + wp->w_topfill; - } else { - n = plines_win(wp, lnum, true); - } + bool folded; + int n = plines_win_full(wp, lnum, &last, &folded, true); if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) { wp->w_cline_row = done; wp->w_cline_height = n; @@ -571,18 +564,14 @@ static void curs_rows(win_T *wp) break; wp->w_cline_row += wp->w_lines[i].wl_size; } else { - long fold_count = foldedCount(wp, lnum, NULL); - if (fold_count) { - lnum += fold_count; - if (lnum > wp->w_cursor.lnum) - break; - ++wp->w_cline_row; - } else if (lnum == wp->w_topline) { - wp->w_cline_row += plines_win_nofill(wp, lnum++, true) - + wp->w_topfill; - } else { - wp->w_cline_row += plines_win(wp, lnum++, true); + linenr_T last = lnum; + bool folded; + int n = plines_win_full(wp, lnum, &last, &folded, false); + lnum = last + 1; + if (folded && lnum > wp->w_cursor.lnum) { + break; } + wp->w_cline_row += n; } } @@ -593,18 +582,13 @@ static void curs_rows(win_T *wp) || (i < wp->w_lines_valid && (!wp->w_lines[i].wl_valid || wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) { - if (wp->w_cursor.lnum == wp->w_topline) - wp->w_cline_height = plines_win_nofill(wp, wp->w_cursor.lnum, - true) + wp->w_topfill; - else - wp->w_cline_height = plines_win(wp, wp->w_cursor.lnum, true); - wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum, - NULL, NULL, true, NULL); + wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL, + &wp->w_cline_folded, true); } else if (i > wp->w_lines_valid) { /* a line that is too long to fit on the last screen line */ wp->w_cline_height = 0; - wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum, - NULL, NULL, true, NULL); + wp->w_cline_folded = hasFoldingWin(wp, wp->w_cursor.lnum, NULL, + NULL, true, NULL); } else { wp->w_cline_height = wp->w_lines[i].wl_size; wp->w_cline_folded = wp->w_lines[i].wl_folded; @@ -646,12 +630,9 @@ static void validate_cheight(void) { check_cursor_moved(curwin); if (!(curwin->w_valid & VALID_CHEIGHT)) { - if (curwin->w_cursor.lnum == curwin->w_topline) - curwin->w_cline_height = plines_nofill(curwin->w_cursor.lnum) - + curwin->w_topfill; - else - curwin->w_cline_height = plines(curwin->w_cursor.lnum); - curwin->w_cline_folded = hasFolding(curwin->w_cursor.lnum, NULL, NULL); + curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, + NULL, &curwin->w_cline_folded, + true); curwin->w_valid |= VALID_CHEIGHT; } } -- cgit From c3ae5e13753e1b27324f167bdc7fab94a86ca294 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 3 Oct 2019 09:32:14 +0200 Subject: test/old: align with Vim #11096 --- src/nvim/testdir/test_normal.vim | 18 +++++++++--------- src/nvim/testdir/test_window_cmd.vim | 33 ++++++++++++++++----------------- 2 files changed, 25 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index b3e43640bb..8bc4228359 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2488,6 +2488,15 @@ func Test_normal_large_count() bwipe! endfunc +func Test_delete_until_paragraph() + new + normal grádv} + call assert_equal('á', getline(1)) + normal grád} + call assert_equal('', getline(1)) + bwipe! +endfunc + " Test for the gr (virtual replace) command " Test for the bug fixed by 7.4.387 func Test_gr_command() @@ -2553,15 +2562,6 @@ func Test_nv_hat_count() %bwipeout! endfunc -func Test_delete_until_paragraph() - new - normal grádv} - call assert_equal('á', getline(1)) - normal grád} - call assert_equal('', getline(1)) - bwipe! -endfunc - func Test_message_when_using_ctrl_c() " Make sure no buffers are changed. %bwipe! diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 43c1f06c44..aaa291f87d 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -69,18 +69,6 @@ function Test_window_cmd_wincmd_gf() augroup! test_window_cmd_wincmd_gf endfunc -func Test_next_split_all() - " This was causing an illegal memory access. - n x - norm axxx - split - split - s/x - s/x - all - bwipe! -endfunc - func Test_window_quit() e Xa split Xb @@ -502,6 +490,17 @@ func Test_window_newtab() %bw! endfunc +func Test_next_split_all() + " This was causing an illegal memory access. + n x + norm axxx + split + split + s/x + s/x + all + bwipe! +endfunc " Tests for adjusting window and contents func GetScreenStr(row) @@ -541,6 +540,11 @@ func Test_window_contents() call test_garbagecollect_now() endfunc +func Test_window_colon_command() + " This was reading invalid memory. + exe "norm! v\:\echo v:version" +endfunc + func Test_access_freed_mem() " This was accessing freed memory au * 0 vs xxx @@ -837,9 +841,4 @@ func Test_winnr() only | tabonly endfunc -func Test_window_colon_command() - " This was reading invalid memory. - exe "norm! v\:\echo v:version" -endfunc - " vim: shiftwidth=2 sts=2 expandtab -- cgit From a341eb608706e5e8ac691a7e8f4a9d314bafee20 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 26 Sep 2019 09:15:21 +0200 Subject: win_line: update `w_last_cursorline` always Vim patch 8.1.0856 (54d9ea6) caused a performance regression in Neovim, when `set conceallevel=1 nocursorline` was used, since then due to refactoring in 23c71d5 `w_last_cursorline` would never get updated anymore. Adds/uses `redrawdebug+=nodelta` for testing this. Fixes https://github.com/neovim/neovim/issues/11100. Closes https://github.com/neovim/neovim/pull/11101. --- src/nvim/option.c | 4 ++++ src/nvim/option_defs.h | 9 ++++++++- src/nvim/screen.c | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 3ccc67eb14..22f7b85133 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4306,6 +4306,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, if (value < 0) { errmsg = e_positive; } + } else if (pp == &p_wd) { + if (value < 0) { + errmsg = e_positive; + } } // Don't change the value and return early if validation failed. diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 108a3dde7c..67cb53ce02 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -518,11 +518,18 @@ EXTERN long p_pyx; // 'pyxversion' EXTERN char_u *p_rdb; // 'redrawdebug' EXTERN unsigned rdb_flags; # ifdef IN_OPTION_C -static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", NULL }; +static char *(p_rdb_values[]) = { + "compositor", + "nothrottle", + "invalid", + "nodelta", + NULL +}; # endif # define RDB_COMPOSITOR 0x001 # define RDB_NOTHROTTLE 0x002 # define RDB_INVALID 0x004 +# define RDB_NODELTA 0x008 EXTERN long p_rdt; // 'redrawtime' EXTERN int p_remap; // 'remap' diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 187c89b28c..488341c6a7 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -577,7 +577,7 @@ void conceal_check_cursor_line(void) /// Whether cursorline is drawn in a special way /// /// If true, both old and new cursorline will need -/// need to be redrawn when moving cursor within windows. +/// to be redrawn when moving cursor within windows. /// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch /// caused by scrolling. bool win_cursorline_standout(const win_T *wp) @@ -2406,10 +2406,10 @@ win_line ( filler_todo = filler_lines; // Cursor line highlighting for 'cursorline' in the current window. - if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { + if (lnum == wp->w_cursor.lnum) { // Do not show the cursor line when Visual mode is active, because it's // not clear what is selected then. - if (!(wp == curwin && VIsual_active)) { + if (wp->w_p_cul && !(wp == curwin && VIsual_active)) { int cul_attr = win_hl_attr(wp, HLF_CUL); HlAttrs ae = syn_attr2entry(cul_attr); @@ -4354,7 +4354,7 @@ static int grid_char_needs_redraw(ScreenGrid *grid, int off_from, int off_to, || (line_off2cells(linebuf_char, off_from, off_from + cols) > 1 && schar_cmp(linebuf_char[off_from + 1], grid->chars[off_to + 1]))) - || p_wd < 0)); + || rdb_flags & RDB_NODELTA)); } /// Move one buffered line to the window grid, but only the characters that -- cgit From b4ea09cc064034f43808662c40cc1fff14284432 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 5 Oct 2019 00:18:24 +0900 Subject: Fix potential deadlock #11151 ELOG may call os_getenv and os_setenv internally. In that case, a deadlock occurs. --- src/nvim/os/env.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 13853016d1..ae61e54993 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -55,6 +55,7 @@ const char *os_getenv(const char *name) return NULL; } uv_mutex_lock(&mutex); + int r = 0; if (pmap_has(cstr_t)(envmap, name) && !!(e = (char *)pmap_get(cstr_t)(envmap, name))) { if (e[0] != '\0') { @@ -67,7 +68,7 @@ const char *os_getenv(const char *name) pmap_del2(envmap, name); } e = xmalloc(size); - int r = uv_os_getenv(name, e, &size); + r = uv_os_getenv(name, e, &size); if (r == UV_ENOBUFS) { e = xrealloc(e, size); r = uv_os_getenv(name, e, &size); @@ -75,14 +76,15 @@ const char *os_getenv(const char *name) if (r != 0 || size == 0 || e[0] == '\0') { xfree(e); e = NULL; - if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) { - ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); - } goto end; } pmap_put(cstr_t)(envmap, xstrdup(name), e); end: + // Must do this before ELOG, log.c may call os_setenv. uv_mutex_unlock(&mutex); + if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) { + ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); + } return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e; } @@ -146,10 +148,11 @@ int os_setenv(const char *name, const char *value, int overwrite) // Destroy the old map item. Do this AFTER uv_os_setenv(), because `value` // could be a previous os_getenv() result. pmap_del2(envmap, name); + // Must do this before ELOG, log.c may call os_setenv. + uv_mutex_unlock(&mutex); if (r != 0) { ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r)); } - uv_mutex_unlock(&mutex); return r == 0 ? 0 : -1; } @@ -163,10 +166,11 @@ int os_unsetenv(const char *name) uv_mutex_lock(&mutex); pmap_del2(envmap, name); int r = uv_os_unsetenv(name); + // Must do this before ELOG, log.c may call os_setenv. + uv_mutex_unlock(&mutex); if (r != 0) { ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r)); } - uv_mutex_unlock(&mutex); return r == 0 ? 0 : -1; } -- cgit From 402afb08959c353a50e040dd0bdcc7cd7aa73041 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 4 Oct 2019 22:10:16 +0200 Subject: Makefile: use `$TMPDIR` below `src/nvim/testdir` (#11153) This makes it ignored/cleaned automatically. It was made absolute in 8821579ba, but to the root back then. --- src/nvim/testdir/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index d1a449c7cc..08353509af 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -11,7 +11,7 @@ ROOT := ../../.. export SHELL := sh export NVIM_PRG := $(NVIM_PRG) -export TMPDIR := $(abspath ../../../Xtest-tmpdir) +export TMPDIR := $(abspath Xtest-tmpdir) SCRIPTS_DEFAULT = \ test42.out \ -- cgit From 5581ffac740d4a75809c6395da4ab757b8d7e6c8 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 5 Oct 2019 11:01:37 -0400 Subject: vim-patch:8.1.2113: ":help expr-!~?" only works after searching Problem: ":help expr-!~?" only works after searching. Solution: Escape "~" after "expr-". (closes vim/vim#5015) https://github.com/vim/vim/commit/9ca250855b55f4d3292b010525c827dc6992cb61 --- src/nvim/ex_cmds.c | 14 +++++++++++--- src/nvim/testdir/test_help.vim | 6 ++++++ 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 16487ce447..d7ae522f32 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4739,11 +4739,19 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, if (STRNICMP(arg, "expr-", 5) == 0) { // When the string starting with "expr-" and containing '?' and matches - // the table, it is taken literally. Otherwise '?' is recognized as a - // wildcard. + // the table, it is taken literally (but ~ is escaped). Otherwise '?' + // is recognized as a wildcard. for (i = (int)ARRAY_SIZE(expr_table); --i >= 0; ) { if (STRCMP(arg + 5, expr_table[i]) == 0) { - STRCPY(d, arg); + for (int si = 0, di = 0; ; si++) { + if (arg[si] == '~') { + d[di++] = '\\'; + } + d[di++] = arg[si]; + if (arg[si] == NUL) { + break; + } + } break; } } diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim index ed3181564c..01fb9917e9 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -21,6 +21,12 @@ func Test_help_errors() bwipe! endfunc +func Test_help_expr() + help expr-!~? + call assert_equal('eval.txt', expand('%:t')) + close +endfunc + func Test_help_keyword() new set keywordprg=:help -- cgit From 1396cc9abb0dfcdbd9572706235aba59f7c3318a Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Sun, 6 Oct 2019 05:13:47 +0200 Subject: version.c: update [ci skip] #10981 vim-patch:8.0.0934: change to struts.h missing in patch vim-patch:8.0.1176: job_start() does not handle quote and backslash correctly vim-patch:8.0.1492: memory leak in balloon_split() vim-patch:8.0.1582: in the MS-Windows console mouse movement is not used vim-patch:8.0.1619: Win32 GUI: crash when winpty is not installed vim-patch:8.0.1624: options for term_dumpdiff() and term_dumpload() not implemented vim-patch:8.0.1665: when running a terminal from the GUI 'term' is not useful vim-patch:8.0.1666: % argument in ch_log() causes trouble vim-patch:8.0.1685: can't set ANSI colors of a terminal window vim-patch:8.0.1711: term_setsize() is not implemented yet vim-patch:8.0.1722: cannot specify a minimal size for a terminal window vim-patch:8.0.1725: terminal debugger doesn't handle command arguments vim-patch:8.0.1742: cannot get a list of all the jobs vim-patch:8.0.1798: MS-Windows: file considered read-only too often vim-patch:8.0.1835: print document name does not support multi-byte vim-patch:8.1.0080: can't see the breakpoint number in the terminal debugger vim-patch:8.1.0156: MS-Windows compiler warning vim-patch:8.1.0226: too many #ifdefs vim-patch:8.1.0722: cannot build without the virtualedit feature vim-patch:8.1.0745: compiler warnings for signed/unsigned string vim-patch:8.1.0752: one more compiler warning for signed/unsigned string vim-patch:8.1.2025: MS-Windows: Including shlguid.h causes problems for msys2 vim-patch:8.1.2027: MS-Windows: problem with ambiwidth characters vim-patch:8.1.2033: cannot build with tiny features vim-patch:8.1.2049: cannot build tiny version vim-patch:8.1.2061: MS-Windows GUI: ":sh" crashes when trying to use a terminal vim-patch:8.1.2075: get many log messages when waiting for a typed character vim-patch:8.1.2078: build error with +textprop but without +terminal vim-patch:8.1.2084: Amiga: cannot get the user name vim-patch:8.1.2086: missing a few changes for the renamed files vim-patch:8.1.2088: renamed libvterm mouse.c file not in distributed file list vim-patch:8.1.2090: not clear why channel log file ends vim-patch:8.1.2101: write_session_file() often defined but not used vim-patch:8.1.2102: can't build with GTK and FEAT_GUI_GNOME vim-patch:8.1.2112: build number for ConPTY is outdated The following `if_pyth` patch seems to be N/A. In `~/.local/`, python 2 and 3 have their own subfolders in `~/.local/include/` and `~/.local/lib/`. `PYTHONUSERBASE` is enough to make the user modules work (on my machine) for the legacy tests. vim-patch:8.0.1451: difficult to set the python home directories properly The following patch requires `set compatible` and unsupported `cpoptions`: vim-patch:8.1.1331: test 29 is old style --- src/nvim/version.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index b0619d6273..b6122f6463 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -106,7 +106,7 @@ static const int included_patches[] = { 1815, 1814, 1813, - // 1812, + 1812, 1811, 1810, 1809, @@ -142,13 +142,13 @@ static const int included_patches[] = { 1779, 1778, 1777, - // 1776, + 1776, 1775, // 1774, 1773, - // 1772, - // 1771, - // 1770, + 1772, + 1771, + 1770, // 1769, 1768, // 1767, @@ -164,7 +164,7 @@ static const int included_patches[] = { 1757, 1756, 1755, - // 1754, + 1754, 1753, 1752, 1751, @@ -185,7 +185,7 @@ static const int included_patches[] = { 1736, 1735, 1734, - // 1733, + 1733, // 1732, 1731, 1730, @@ -297,7 +297,7 @@ static const int included_patches[] = { // 1624, 1623, 1622, - // 1621, + 1621, 1620, // 1619, 1618, @@ -379,7 +379,7 @@ static const int included_patches[] = { 1542, 1541, // 1540, - // 1539, + 1539, // 1538, 1537, 1536, @@ -389,13 +389,13 @@ static const int included_patches[] = { 1532, // 1531, 1530, - // 1529, + 1529, 1528, 1527, 1526, // 1525, 1524, - // 1523, + 1523, // 1522, 1521, // 1520, @@ -470,7 +470,7 @@ static const int included_patches[] = { // 1451, 1450, // 1449, - // 1448, + 1448, 1447, 1446, 1445, @@ -552,7 +552,7 @@ static const int included_patches[] = { 1369, 1368, // 1367, - // 1366, + 1366, 1365, 1364, 1363, @@ -784,7 +784,7 @@ static const int included_patches[] = { 1137, 1136, 1135, - // 1134, + 1134, 1133, 1132, 1131, @@ -809,7 +809,7 @@ static const int included_patches[] = { 1112, 1111, 1110, - // 1109, + 1109, 1108, 1107, 1106, @@ -880,7 +880,7 @@ static const int included_patches[] = { 1041, 1040, 1039, - // 1038, + 1038, 1037, 1036, 1035, @@ -948,7 +948,7 @@ static const int included_patches[] = { 973, 972, 971, - // 970, + 970, 969, 968, 967, @@ -977,11 +977,11 @@ static const int included_patches[] = { 944, 943, 942, - // 941, + 941, 940, 939, 938, - // 937, + 937, 936, 935, // 934, @@ -1004,7 +1004,7 @@ static const int included_patches[] = { 917, 916, 915, - // 914, + 914, 913, 912, 911, @@ -1093,7 +1093,7 @@ static const int included_patches[] = { 828, 827, 826, - // 825, + 825, 824, 823, 822, -- cgit From fe074611cd5b3319a3f639f68289df6a718e64eb Mon Sep 17 00:00:00 2001 From: Jurica Bradarić Date: Sun, 6 Oct 2019 05:35:48 +0200 Subject: vim-patch:8.1.1371: cannot recover from a swap file #11081 Problem: Cannot recover from a swap file. Solution: Do not expand environment variables in the swap file name. Do not check the extension when we already know a file is a swap file. (Ken Takata, closes 4415, closes vim/vim#4369) https://github.com/vim/vim/commit/99499b1c05f85f83876b828eea3f6e14f0f407b4 --- src/nvim/buffer.c | 5 ++- src/nvim/ex_cmds.c | 4 +- src/nvim/ex_cmds2.c | 4 +- src/nvim/ex_docmd.c | 11 +++--- src/nvim/main.c | 16 ++++---- src/nvim/memline.c | 14 ++++--- src/nvim/path.c | 11 ++++-- src/nvim/path.h | 11 +++--- src/nvim/search.c | 3 +- src/nvim/spell.c | 10 +++-- src/nvim/spellfile.c | 5 ++- src/nvim/tag.c | 3 +- src/nvim/testdir/test_swap.vim | 84 ++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 141 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e5b80693a4..b81ffd09e1 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -929,7 +929,7 @@ void handle_swap_exists(bufref_T *old_curbuf) // User selected Recover at ATTENTION prompt. msg_scroll = true; - ml_recover(); + ml_recover(false); MSG_PUTS("\n"); // don't overwrite the last message cmdline_row = msg_row; do_modelines(0); @@ -4629,7 +4629,8 @@ do_arg_all( if (i < alist->al_ga.ga_len && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum || path_full_compare(alist_name(&AARGLIST(alist)[i]), - buf->b_ffname, true) & kEqualFiles)) { + buf->b_ffname, + true, true) & kEqualFiles)) { int weight = 1; if (old_curtab == curtab) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 16487ce447..a3a08a5884 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5023,7 +5023,7 @@ void fix_help_buffer(void) copy_option_part(&p, NameBuff, MAXPATHL, ","); char_u *const rt = (char_u *)vim_getenv("VIMRUNTIME"); if (rt != NULL - && path_full_compare(rt, NameBuff, false) != kEqualFiles) { + && path_full_compare(rt, NameBuff, false, true) != kEqualFiles) { int fcount; char_u **fnames; char_u *s; @@ -5233,7 +5233,7 @@ static void helptags_one(char_u *const dir, const char_u *const ext, ga_init(&ga, (int)sizeof(char_u *), 100); if (add_help_tags || path_full_compare((char_u *)"$VIMRUNTIME/doc", - dir, false) == kEqualFiles) { + dir, false, true) == kEqualFiles) { s = xmalloc(18 + STRLEN(tagfname)); sprintf((char *)s, "help-tags\t%s\t1\n", tagfname); GA_APPEND(char_u *, &ga, s); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 813d1a9b0b..87eae2dd4f 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1743,7 +1743,7 @@ static bool editing_arg_idx(win_T *win) && (win->w_buffer->b_ffname == NULL || !(path_full_compare( alist_name(&WARGLIST(win)[win->w_arg_idx]), - win->w_buffer->b_ffname, true) & kEqualFiles)))); + win->w_buffer->b_ffname, true, true) & kEqualFiles)))); } /// Check if window "win" is editing the w_arg_idx file in its argument list. @@ -1761,7 +1761,7 @@ void check_arg_idx(win_T *win) && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum || (win->w_buffer->b_ffname != NULL && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), - win->w_buffer->b_ffname, true) + win->w_buffer->b_ffname, true, true) & kEqualFiles)))) { arg_had_last = true; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 34b4c10d3e..a6042b0e8c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6708,17 +6708,18 @@ static void ex_preserve(exarg_T *eap) /// ":recover". static void ex_recover(exarg_T *eap) { - /* Set recoverymode right away to avoid the ATTENTION prompt. */ - recoverymode = TRUE; + // Set recoverymode right away to avoid the ATTENTION prompt. + recoverymode = true; if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0) | CCGD_MULTWIN | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD) && (*eap->arg == NUL - || setfname(curbuf, eap->arg, NULL, TRUE) == OK)) - ml_recover(); - recoverymode = FALSE; + || setfname(curbuf, eap->arg, NULL, true) == OK)) { + ml_recover(true); + } + recoverymode = false; } /* diff --git a/src/nvim/main.c b/src/nvim/main.c index be1f08bb46..ba15dcedad 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1460,12 +1460,13 @@ static void create_windows(mparm_T *parmp) } else parmp->window_count = 1; - if (recoverymode) { /* do recover */ - msg_scroll = TRUE; /* scroll message up */ - ml_recover(); - if (curbuf->b_ml.ml_mfp == NULL) /* failed */ + if (recoverymode) { // do recover + msg_scroll = true; // scroll message up + ml_recover(true); + if (curbuf->b_ml.ml_mfp == NULL) { // failed getout(1); - do_modelines(0); /* do modelines */ + } + do_modelines(0); // do modelines } else { // Open a buffer for windows that don't have one yet. // Commands in the vimrc might have loaded a file or split the window. @@ -1778,7 +1779,8 @@ static bool do_user_initialization(void) if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) { do_exrc = p_exrc; if (do_exrc) { - do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, false) + do_exrc = (path_full_compare((char_u *)VIMRC_FILE, user_vimrc, + false, true) != kEqualFiles); } xfree(user_vimrc); @@ -1805,7 +1807,7 @@ static bool do_user_initialization(void) do_exrc = p_exrc; if (do_exrc) { do_exrc = (path_full_compare((char_u *)VIMRC_FILE, (char_u *)vimrc, - false) != kEqualFiles); + false, true) != kEqualFiles); } xfree(vimrc); xfree(config_dirs); diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 15dd2767a2..f1d6ee064c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -738,10 +738,10 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) } -/* - * Try to recover curbuf from the .swp file. - */ -void ml_recover(void) +/// Try to recover curbuf from the .swp file. +/// @param checkext If true, check the extension and detect whether it is a +/// swap file. +void ml_recover(bool checkext) { buf_T *buf = NULL; memfile_T *mfp = NULL; @@ -785,7 +785,7 @@ void ml_recover(void) if (fname == NULL) /* When there is no file name */ fname = (char_u *)""; len = (int)STRLEN(fname); - if (len >= 4 + if (checkext && len >= 4 && STRNICMP(fname + len - 4, ".s", 2) == 0 && vim_strchr((char_u *)"abcdefghijklmnopqrstuvw", TOLOWER_ASC(fname[len - 2])) != NULL @@ -1375,7 +1375,9 @@ recover_names ( if (curbuf->b_ml.ml_mfp != NULL && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) { for (int i = 0; i < num_files; i++) { - if (path_full_compare(p, files[i], true) & kEqualFiles) { + // Do not expand wildcards, on Windows would try to expand + // "%tmp%" in "%tmp%file" + if (path_full_compare(p, files[i], true, false) & kEqualFiles) { // Remove the name from files[i]. Move further entries // down. When the array becomes empty free it here, since // FreeWild() won't be called below. diff --git a/src/nvim/path.c b/src/nvim/path.c index 1c787e3a1d..62d5d69d1a 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -51,9 +51,10 @@ /// expanded. /// @param s2 Second file name. /// @param checkname When both files don't exist, only compare their names. +/// @param expandenv Whether to expand environment variables in file names. /// @return Enum of type FileComparison. @see FileComparison. FileComparison path_full_compare(char_u *const s1, char_u *const s2, - const bool checkname) + const bool checkname, const bool expandenv) { assert(s1 && s2); char_u exp1[MAXPATHL]; @@ -61,7 +62,11 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2, char_u full2[MAXPATHL]; FileID file_id_1, file_id_2; - expand_env(s1, exp1, MAXPATHL); + if (expandenv) { + expand_env(s1, exp1, MAXPATHL); + } else { + xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1); + } bool id_ok_1 = os_fileid((char *)exp1, &file_id_1); bool id_ok_2 = os_fileid((char *)s2, &file_id_2); if (!id_ok_1 && !id_ok_2) { @@ -1203,7 +1208,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, } } else { // First expand environment variables, "~/" and "~user/". - if (has_env_var(p) || *p == '~') { + if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') { p = expand_env_save_opt(p, true); if (p == NULL) p = pat[i]; diff --git a/src/nvim/path.h b/src/nvim/path.h index 4e466d1b71..15abd19646 100644 --- a/src/nvim/path.h +++ b/src/nvim/path.h @@ -20,11 +20,12 @@ #define EW_KEEPDOLLAR 0x800 /* do not escape $, $var is expanded */ /* Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND * is used when executing commands and EW_SILENT for interactive expanding. */ -#define EW_ALLLINKS 0x1000 // also links not pointing to existing file -#define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check - // if executable is in $PATH -#define EW_DODOT 0x4000 // also files starting with a dot -#define EW_EMPTYOK 0x8000 // no matches is not an error +#define EW_ALLLINKS 0x1000 // also links not pointing to existing file +#define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check + // if executable is in $PATH +#define EW_DODOT 0x4000 // also files starting with a dot +#define EW_EMPTYOK 0x8000 // no matches is not an error +#define EW_NOTENV 0x10000 // do not expand environment variables /// Return value for the comparison of two files. Also @see path_full_compare. typedef enum file_comparison { diff --git a/src/nvim/search.c b/src/nvim/search.c index 7d1c19d68c..85c0d7eb48 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4458,7 +4458,8 @@ find_pattern_in_path( if (i == max_path_depth) { break; } - if (path_full_compare(new_fname, files[i].name, true) & kEqualFiles) { + if (path_full_compare(new_fname, files[i].name, + true, true) & kEqualFiles) { if (type != CHECK_PATH && action == ACTION_SHOW_ALL && files[i].matched) { msg_putchar('\n'); // cursor below last one */ diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 724a0332bc..ab40355a8a 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2031,7 +2031,8 @@ char_u *did_set_spelllang(win_T *wp) // Check if we loaded this language before. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare(lang, slang->sl_fname, false) == kEqualFiles) { + if (path_full_compare(lang, slang->sl_fname, false, true) + == kEqualFiles) { break; } } @@ -2076,7 +2077,7 @@ char_u *did_set_spelllang(win_T *wp) // Loop over the languages, there can be several files for "lang". for (slang = first_lang; slang != NULL; slang = slang->sl_next) { if (filename - ? path_full_compare(lang, slang->sl_fname, false) == kEqualFiles + ? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles : STRICMP(lang, slang->sl_name) == 0) { region_mask = REGION_ALL; if (!filename && region != NULL) { @@ -2129,7 +2130,7 @@ char_u *did_set_spelllang(win_T *wp) for (c = 0; c < ga.ga_len; ++c) { p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname; if (p != NULL - && path_full_compare(spf_name, p, false) == kEqualFiles) { + && path_full_compare(spf_name, p, false, true) == kEqualFiles) { break; } } @@ -2139,7 +2140,8 @@ char_u *did_set_spelllang(win_T *wp) // Check if it was loaded already. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare(spf_name, slang->sl_fname, false) == kEqualFiles) { + if (path_full_compare(spf_name, slang->sl_fname, false, true) + == kEqualFiles) { break; } } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 01daafa09e..eeec5be120 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1787,7 +1787,7 @@ spell_reload_one ( bool didit = false; for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare(fname, slang->sl_fname, false) == kEqualFiles) { + if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) { slang_clear(slang); if (spell_load_file(fname, NULL, slang, false) == NULL) // reloading failed, clear the language @@ -4719,7 +4719,8 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // of the code for the soundfolding stuff. // It might have been done already by spell_reload_one(). for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare(wfname, slang->sl_fname, false) == kEqualFiles) { + if (path_full_compare(wfname, slang->sl_fname, false, true) + == kEqualFiles) { break; } } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 91f3da1793..6fe3efbaae 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2666,7 +2666,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, *fname_end = NUL; } fullname = expand_tag_fname(fname, tag_fname, true); - retval = (path_full_compare(fullname, buf_ffname, true) & kEqualFiles); + retval = (path_full_compare(fullname, buf_ffname, true, true) + & kEqualFiles); xfree(fullname); *fname_end = c; } diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index 11eb324488..e072e9ed7f 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -221,3 +221,87 @@ func Test_swapfile_delete() augroup END augroup! test_swapfile_delete endfunc + +func Test_swap_recover() + autocmd! SwapExists + augroup test_swap_recover + autocmd! + autocmd SwapExists * let v:swapchoice = 'r' + augroup END + + + call mkdir('Xswap') + let $Xswap = 'foo' " Check for issue #4369. + set dir=Xswap// + " Create a valid swapfile by editing a file. + split Xswap/text + call setline(1, ['one', 'two', 'three']) + write " file is written, not modified + " read the swapfile as a Blob + let swapfile_name = swapname('%') + let swapfile_bytes = readfile(swapfile_name, 'B') + + " Close the file and recreate the swap file. + quit + call writefile(swapfile_bytes, swapfile_name) + " Edit the file again. This triggers recovery. + try + split Xswap/text + catch + " E308 should be caught, not E305. + call assert_exception('E308:') " Original file may have been changed + endtry + " The file should be recovered. + call assert_equal(['one', 'two', 'three'], getline(1, 3)) + quit! + + call delete('Xswap/text') + call delete(swapfile_name) + call delete('Xswap', 'd') + unlet $Xswap + set dir& + augroup test_swap_recover + autocmd! + augroup END + augroup! test_swap_recover +endfunc + +func Test_swap_recover_ext() + autocmd! SwapExists + augroup test_swap_recover_ext + autocmd! + autocmd SwapExists * let v:swapchoice = 'r' + augroup END + + + " Create a valid swapfile by editing a file with a special extension. + split Xtest.scr + call setline(1, ['one', 'two', 'three']) + write " file is written, not modified + write " write again to make sure the swapfile is created + " read the swapfile as a Blob + let swapfile_name = swapname('%') + let swapfile_bytes = readfile(swapfile_name, 'B') + + " Close and delete the file and recreate the swap file. + quit + call delete('Xtest.scr') + call writefile(swapfile_bytes, swapfile_name) + " Edit the file again. This triggers recovery. + try + split Xtest.scr + catch + " E308 should be caught, not E306. + call assert_exception('E308:') " Original file may have been changed + endtry + " The file should be recovered. + call assert_equal(['one', 'two', 'three'], getline(1, 3)) + quit! + + call delete('Xtest.scr') + call delete(swapfile_name) + augroup test_swap_recover_ext + autocmd! + augroup END + augroup! test_swap_recover_ext +endfunc -- cgit From b007e5d8820c613606bdc3afcb49d7eecc14ea0b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 5 Oct 2019 22:59:31 -0400 Subject: vim-patch:8.1.0059: displayed digraph for "ga" wrong with 'encoding' "cp1251" Problem: Displayed digraph for "ga" wrong with 'encoding' "cp1251". Solution: Convert from 'encoding' to "utf-8" if needed. (closes vim/vim#3015) https://github.com/vim/vim/commit/bc5020aa4d7ef4aea88395eff858f74fc881eab9 --- src/nvim/digraph.c | 3 ++- src/nvim/testdir/test_digraph.vim | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index f6ec350488..427b5537f3 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1450,8 +1450,9 @@ int do_digraph(int c) /// Find a digraph for "val". If found return the string to display it. /// If not found return NULL. -char_u *get_digraph_for_char(int val) +char_u *get_digraph_for_char(int val_arg) { + const int val = val_arg; digr_T *dp; static char_u r[3]; diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index 62a5da33df..5da05e85b5 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -465,4 +465,17 @@ func Test_show_digraph() bwipe! endfunc +func Test_show_digraph_cp1251() + throw 'skipped: Nvim supports ''utf8'' encoding only' + if !has('multi_byte') + return + endif + new + set encoding=cp1251 + call Put_Dig("='") + call assert_equal("\n<\xfa> <|z> 250, Hex fa, Oct 372, Digr ='", execute('ascii')) + set encoding=utf-8 + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From d1abd6513e95a41e41ad570038310087e97f3bb1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 6 Oct 2019 08:03:47 -0400 Subject: vim-patch:8.1.0586: :digraph output is not easy to read Problem: :digraph output is not easy to read. Solution: Add highlighting for :digraphs. (Marcin Szamotulski, closes vim/vim#3572) Also add section headers for :digraphs!. https://github.com/vim/vim/commit/eae8ae1b2b4e532b125077d9838b70d966891be3 --- src/nvim/digraph.c | 232 ++++++++++++++++++++++++++++++++++----------------- src/nvim/ex_cmds.lua | 2 +- src/nvim/ex_docmd.c | 7 +- src/nvim/message.c | 10 +-- 4 files changed, 167 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 427b5537f3..5a07137831 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -128,101 +128,154 @@ static digr_T digraphdefault[] = { 'P', 'M', 0x9e }, { 'A', 'C', 0x9f }, { 'N', 'S', 0xa0 }, +#define DG_START_LATIN 0xa1 { '!', 'I', 0xa1 }, + { '~', '!', 0xa1 }, // ¡ Vim 5.x compatible { 'C', 't', 0xa2 }, + { 'c', '|', 0xa2 }, // ¢ Vim 5.x compatible { 'P', 'd', 0xa3 }, + { '$', '$', 0xa3 }, // £ Vim 5.x compatible { 'C', 'u', 0xa4 }, + { 'o', 'x', 0xa4 }, // ¤ Vim 5.x compatible { 'Y', 'e', 0xa5 }, + { 'Y', '-', 0xa5 }, // ¥ Vim 5.x compatible { 'B', 'B', 0xa6 }, + { '|', '|', 0xa6 }, // ¦ Vim 5.x compatible { 'S', 'E', 0xa7 }, { '\'', ':', 0xa8 }, { 'C', 'o', 0xa9 }, + { 'c', 'O', 0xa9 }, // © Vim 5.x compatible { '-', 'a', 0xaa }, { '<', '<', 0xab }, { 'N', 'O', 0xac }, + { '-', ',', 0xac }, // ¬ Vim 5.x compatible { '-', '-', 0xad }, { 'R', 'g', 0xae }, { '\'', 'm', 0xaf }, + { '-', '=', 0xaf }, // ¯ Vim 5.x compatible { 'D', 'G', 0xb0 }, + { '~', 'o', 0xb0 }, // ° Vim 5.x compatible { '+', '-', 0xb1 }, { '2', 'S', 0xb2 }, + { '2', '2', 0xb2 }, // ² Vim 5.x compatible { '3', 'S', 0xb3 }, + { '3', '3', 0xb3 }, // ³ Vim 5.x compatible { '\'', '\'', 0xb4 }, { 'M', 'y', 0xb5 }, { 'P', 'I', 0xb6 }, + { 'p', 'p', 0xb6 }, // ¶ Vim 5.x compatible { '.', 'M', 0xb7 }, + { '~', '.', 0xb7 }, // · Vim 5.x compatible { '\'', ',', 0xb8 }, { '1', 'S', 0xb9 }, + { '1', '1', 0xb9 }, // ¹ Vim 5.x compatible { '-', 'o', 0xba }, { '>', '>', 0xbb }, { '1', '4', 0xbc }, { '1', '2', 0xbd }, { '3', '4', 0xbe }, { '?', 'I', 0xbf }, + { '~', '?', 0xbf }, // ¿ Vim 5.x compatible { 'A', '!', 0xc0 }, + { 'A', '`', 0xc0 }, // À Vim 5.x compatible { 'A', '\'', 0xc1 }, { 'A', '>', 0xc2 }, + { 'A', '^', 0xc2 }, // Â Vim 5.x compatible { 'A', '?', 0xc3 }, + { 'A', '~', 0xc3 }, // Ã Vim 5.x compatible { 'A', ':', 0xc4 }, + { 'A', '"', 0xc4 }, // Ä Vim 5.x compatible { 'A', 'A', 0xc5 }, + { 'A', '@', 0xc5 }, // Å Vim 5.x compatible { 'A', 'E', 0xc6 }, { 'C', ',', 0xc7 }, { 'E', '!', 0xc8 }, + { 'E', '`', 0xc8 }, // È Vim 5.x compatible { 'E', '\'', 0xc9 }, { 'E', '>', 0xca }, + { 'E', '^', 0xca }, // Ê Vim 5.x compatible { 'E', ':', 0xcb }, + { 'E', '"', 0xcb }, // Ë Vim 5.x compatible { 'I', '!', 0xcc }, + { 'I', '`', 0xcc }, // Ì Vim 5.x compatible { 'I', '\'', 0xcd }, { 'I', '>', 0xce }, + { 'I', '^', 0xce }, // Î Vim 5.x compatible { 'I', ':', 0xcf }, + { 'I', '"', 0xcf }, // Ï Vim 5.x compatible { 'D', '-', 0xd0 }, { 'N', '?', 0xd1 }, + { 'N', '~', 0xd1 }, // Ñ Vim 5.x compatible { 'O', '!', 0xd2 }, + { 'O', '`', 0xd2 }, // Ò Vim 5.x compatible { 'O', '\'', 0xd3 }, { 'O', '>', 0xd4 }, + { 'O', '^', 0xd4 }, // Ô Vim 5.x compatible { 'O', '?', 0xd5 }, + { 'O', '~', 0xd5 }, // Õ Vim 5.x compatible { 'O', ':', 0xd6 }, { '*', 'X', 0xd7 }, + { '/', '\\', 0xd7 }, // × Vim 5.x compatible { 'O', '/', 0xd8 }, { 'U', '!', 0xd9 }, + { 'U', '`', 0xd9 }, // Ù Vim 5.x compatible { 'U', '\'', 0xda }, { 'U', '>', 0xdb }, + { 'U', '^', 0xdb }, // Û Vim 5.x compatible { 'U', ':', 0xdc }, { 'Y', '\'', 0xdd }, { 'T', 'H', 0xde }, + { 'I', 'p', 0xde }, // Þ Vim 5.x compatible { 's', 's', 0xdf }, { 'a', '!', 0xe0 }, + { 'a', '`', 0xe0 }, // à Vim 5.x compatible { 'a', '\'', 0xe1 }, { 'a', '>', 0xe2 }, + { 'a', '^', 0xe2 }, // â Vim 5.x compatible { 'a', '?', 0xe3 }, + { 'a', '~', 0xe3 }, // ã Vim 5.x compatible { 'a', ':', 0xe4 }, + { 'a', '"', 0xe4 }, // ä Vim 5.x compatible { 'a', 'a', 0xe5 }, + { 'a', '@', 0xe5 }, // å Vim 5.x compatible { 'a', 'e', 0xe6 }, { 'c', ',', 0xe7 }, { 'e', '!', 0xe8 }, + { 'e', '`', 0xe8 }, // è Vim 5.x compatible { 'e', '\'', 0xe9 }, { 'e', '>', 0xea }, + { 'e', '^', 0xea }, // ê Vim 5.x compatible { 'e', ':', 0xeb }, + { 'e', '"', 0xeb }, // ë Vim 5.x compatible { 'i', '!', 0xec }, + { 'i', '`', 0xec }, // ì Vim 5.x compatible { 'i', '\'', 0xed }, { 'i', '>', 0xee }, + { 'i', '^', 0xee }, // î Vim 5.x compatible { 'i', ':', 0xef }, { 'd', '-', 0xf0 }, { 'n', '?', 0xf1 }, + { 'n', '~', 0xf1 }, // ñ Vim 5.x compatible { 'o', '!', 0xf2 }, + { 'o', '`', 0xf2 }, // ò Vim 5.x compatible { 'o', '\'', 0xf3 }, { 'o', '>', 0xf4 }, + { 'o', '^', 0xf4 }, // ô Vim 5.x compatible { 'o', '?', 0xf5 }, + { 'o', '~', 0xf5 }, // õ Vim 5.x compatible { 'o', ':', 0xf6 }, { '-', ':', 0xf7 }, { 'o', '/', 0xf8 }, { 'u', '!', 0xf9 }, + { 'u', '`', 0xf9 }, // ù Vim 5.x compatible { 'u', '\'', 0xfa }, { 'u', '>', 0xfb }, + { 'u', '^', 0xfb }, // û Vim 5.x compatible { 'u', ':', 0xfc }, { 'y', '\'', 0xfd }, { 't', 'h', 0xfe }, { 'y', ':', 0xff }, + { 'y', '"', 0xff }, // x XX Vim 5.x compatible { 'A', '-', 0x0100 }, { 'a', '-', 0x0101 }, @@ -397,6 +450,7 @@ static digr_T digraphdefault[] = { '\'', '0', 0x02da }, { '\'', ';', 0x02db }, { '\'', '"', 0x02dd }, +#define DG_START_GREEK 0x0386 { 'A', '%', 0x0386 }, { 'E', '%', 0x0388 }, { 'Y', '%', 0x0389 }, @@ -478,6 +532,7 @@ static digr_T digraphdefault[] = { 'p', '3', 0x03e1 }, { '\'', '%', 0x03f4 }, { 'j', '3', 0x03f5 }, +#define DG_START_CYRILLIC 0x0401 { 'I', 'O', 0x0401 }, { 'D', '%', 0x0402 }, { 'G', '%', 0x0403 }, @@ -582,6 +637,7 @@ static digr_T digraphdefault[] = { 'c', '3', 0x0481 }, { 'G', '3', 0x0490 }, { 'g', '3', 0x0491 }, +#define DG_START_HEBREW 0x05d0 { 'A', '+', 0x05d0 }, { 'B', '+', 0x05d1 }, { 'G', '+', 0x05d2 }, @@ -609,6 +665,7 @@ static digr_T digraphdefault[] = { 'R', '+', 0x05e8 }, { 'S', 'h', 0x05e9 }, { 'T', '+', 0x05ea }, +#define DG_START_ARABIC 0x060c { ',', '+', 0x060c }, { ';', '+', 0x061b }, { '?', '+', 0x061f }, @@ -671,6 +728,7 @@ static digr_T digraphdefault[] = { '7', 'a', 0x06f7 }, { '8', 'a', 0x06f8 }, { '9', 'a', 0x06f9 }, +#define DG_START_LATIN_EXTENDED 0x1e02 { 'B', '.', 0x1e02 }, { 'b', '.', 0x1e03 }, { 'B', '_', 0x1e06 }, @@ -722,7 +780,9 @@ static digr_T digraphdefault[] = { 'V', '?', 0x1e7c }, { 'v', '?', 0x1e7d }, { 'W', '!', 0x1e80 }, + { 'W', '`', 0x1e80 }, // extra alternative, easier to remember { 'w', '!', 0x1e81 }, + { 'w', '`', 0x1e81 }, // extra alternative, easier to remember { 'W', '\'', 0x1e82 }, { 'w', '\'', 0x1e83 }, { 'W', ':', 0x1e84 }, @@ -756,11 +816,14 @@ static digr_T digraphdefault[] = { 'U', '2', 0x1ee6 }, { 'u', '2', 0x1ee7 }, { 'Y', '!', 0x1ef2 }, + { 'Y', '`', 0x1ef2 }, // extra alternative, easier to remember { 'y', '!', 0x1ef3 }, + { 'y', '`', 0x1ef3 }, // extra alternative, easier to remember { 'Y', '2', 0x1ef6 }, { 'y', '2', 0x1ef7 }, { 'Y', '?', 0x1ef8 }, { 'y', '?', 0x1ef9 }, +#define DG_START_GREEK_EXTENDED 0x1f00 { ';', '\'', 0x1f00 }, { ',', '\'', 0x1f01 }, { ';', '!', 0x1f02 }, @@ -769,6 +832,7 @@ static digr_T digraphdefault[] = { '?', ',', 0x1f05 }, { '!', ':', 0x1f06 }, { '?', ':', 0x1f07 }, +#define DG_START_PUNCTUATION 0x2002 { '1', 'N', 0x2002 }, { '1', 'M', 0x2003 }, { '3', 'M', 0x2004 }, @@ -807,6 +871,7 @@ static digr_T digraphdefault[] = { ':', 'X', 0x203b }, { '\'', '-', 0x203e }, { '/', 'f', 0x2044 }, +#define DG_START_SUB_SUPER 0x2070 { '0', 'S', 0x2070 }, { '4', 'S', 0x2074 }, { '5', 'S', 0x2075 }, @@ -835,13 +900,15 @@ static digr_T digraphdefault[] = { '=', 's', 0x208c }, { '(', 's', 0x208d }, { ')', 's', 0x208e }, +#define DG_START_CURRENCY 0x20a4 { 'L', 'i', 0x20a4 }, { 'P', 't', 0x20a7 }, { 'W', '=', 0x20a9 }, - { '=', 'e', 0x20ac }, // euro - { 'E', 'u', 0x20ac }, // euro - { '=', 'R', 0x20bd }, // rouble - { '=', 'P', 0x20bd }, // rouble + { '=', 'e', 0x20ac }, // euro + { 'E', 'u', 0x20ac }, // euro + { '=', 'R', 0x20bd }, // rouble + { '=', 'P', 0x20bd }, // rouble +#define DG_START_OTHER1 0x2103 { 'o', 'C', 0x2103 }, { 'c', 'o', 0x2105 }, { 'o', 'F', 0x2109 }, @@ -864,6 +931,7 @@ static digr_T digraphdefault[] = { '3', '8', 0x215c }, { '5', '8', 0x215d }, { '7', '8', 0x215e }, +#define DG_START_ROMAN 0x2160 { '1', 'R', 0x2160 }, { '2', 'R', 0x2161 }, { '3', 'R', 0x2162 }, @@ -888,6 +956,7 @@ static digr_T digraphdefault[] = { 'a', 'r', 0x2179 }, { 'b', 'r', 0x217a }, { 'c', 'r', 0x217b }, +#define DG_START_ARROWS 0x2190 { '<', '-', 0x2190 }, { '-', '!', 0x2191 }, { '-', '>', 0x2192 }, @@ -897,6 +966,7 @@ static digr_T digraphdefault[] = { '<', '=', 0x21d0 }, { '=', '>', 0x21d2 }, { '=', '=', 0x21d4 }, +#define DG_START_MATH 0x2200 { 'F', 'A', 0x2200 }, { 'd', 'P', 0x2202 }, { 'T', 'E', 0x2203 }, @@ -954,6 +1024,7 @@ static digr_T digraphdefault[] = { '.', 'P', 0x22c5 }, { ':', '3', 0x22ee }, { '.', '3', 0x22ef }, +#define DG_START_TECHNICAL 0x2302 { 'E', 'h', 0x2302 }, { '<', '7', 0x2308 }, { '>', '7', 0x2309 }, @@ -966,6 +1037,7 @@ static digr_T digraphdefault[] = { 'I', 'l', 0x2321 }, { '<', '/', 0x2329 }, { '/', '>', 0x232a }, +#define DG_START_OTHER2 0x2423 { 'V', 's', 0x2423 }, { '1', 'h', 0x2440 }, { '3', 'h', 0x2441 }, @@ -984,6 +1056,7 @@ static digr_T digraphdefault[] = { '7', '.', 0x248e }, { '8', '.', 0x248f }, { '9', '.', 0x2490 }, +#define DG_START_DRAWING 0x2500 { 'h', 'h', 0x2500 }, { 'H', 'H', 0x2501 }, { 'v', 'v', 0x2502 }, @@ -1034,6 +1107,7 @@ static digr_T digraphdefault[] = { 'V', 'H', 0x254b }, { 'F', 'D', 0x2571 }, { 'B', 'D', 0x2572 }, +#define DG_START_BLOCK 0x2580 { 'T', 'B', 0x2580 }, { 'L', 'B', 0x2584 }, { 'F', 'B', 0x2588 }, @@ -1042,6 +1116,7 @@ static digr_T digraphdefault[] = { '.', 'S', 0x2591 }, { ':', 'S', 0x2592 }, { '?', 'S', 0x2593 }, +#define DG_START_SHAPES 0x25a0 { 'f', 'S', 0x25a0 }, { 'O', 'S', 0x25a1 }, { 'R', 'O', 0x25a2 }, @@ -1075,6 +1150,7 @@ static digr_T digraphdefault[] = { 'I', 'c', 0x25d9 }, { 'F', 'd', 0x25e2 }, { 'B', 'd', 0x25e3 }, +#define DG_START_SYMBOLS 0x2605 { '*', '2', 0x2605 }, { '*', '1', 0x2606 }, { '<', 'H', 0x261c }, @@ -1094,9 +1170,11 @@ static digr_T digraphdefault[] = { 'M', 'b', 0x266d }, { 'M', 'x', 0x266e }, { 'M', 'X', 0x266f }, +#define DG_START_DINGBATS 0x2713 { 'O', 'K', 0x2713 }, { 'X', 'X', 0x2717 }, { '-', 'X', 0x2720 }, +#define DG_START_CJK_SYMBOLS 0x3000 { 'I', 'S', 0x3000 }, { ',', '_', 0x3001 }, { '.', '_', 0x3002 }, @@ -1120,6 +1198,7 @@ static digr_T digraphdefault[] = { '(', 'I', 0x3016 }, { ')', 'I', 0x3017 }, { '-', '?', 0x301c }, +#define DG_START_HIRAGANA 0x3041 { 'A', '5', 0x3041 }, { 'a', '5', 0x3042 }, { 'I', '5', 0x3043 }, @@ -1208,6 +1287,7 @@ static digr_T digraphdefault[] = { '0', '5', 0x309c }, { '*', '5', 0x309d }, { '+', '5', 0x309e }, +#define DG_START_KATAKANA 0x30a1 { 'a', '6', 0x30a1 }, { 'A', '6', 0x30a2 }, { 'i', '6', 0x30a3 }, @@ -1302,6 +1382,7 @@ static digr_T digraphdefault[] = { '-', '6', 0x30fc }, { '*', '6', 0x30fd }, { '+', '6', 0x30fe }, +#define DG_START_BOPOMOFO 0x3105 { 'b', '4', 0x3105 }, { 'p', '4', 0x3106 }, { 'm', '4', 0x3107 }, @@ -1341,6 +1422,7 @@ static digr_T digraphdefault[] = { 'v', '4', 0x312a }, { 'n', 'G', 0x312b }, { 'g', 'n', 0x312c }, +#define DG_START_OTHER3 0x3220 { '1', 'c', 0x3220 }, { '2', 'c', 0x3221 }, { '3', 'c', 0x3222 }, @@ -1359,66 +1441,6 @@ static digr_T digraphdefault[] = { 'f', 't', 0xfb05 }, { 's', 't', 0xfb06 }, - // extra alternatives, easier to remember - { 'W', '`', 0x1e80 }, - { 'w', '`', 0x1e81 }, - { 'Y', '`', 0x1ef2 }, - { 'y', '`', 0x1ef3 }, - - // Vim 5.x compatible digraphs that don't conflict with the above - { '~', '!', 161 }, // ¡ - { 'c', '|', 162 }, // ¢ - { '$', '$', 163 }, // £ - { 'o', 'x', 164 }, // ¤ - currency symbol in ISO 8859-1 - { 'Y', '-', 165 }, // ¥ - { '|', '|', 166 }, // ¦ - { 'c', 'O', 169 }, // © - { '-', ',', 172 }, // ¬ - { '-', '=', 175 }, // ¯ - { '~', 'o', 176 }, // ° - { '2', '2', 178 }, // ² - { '3', '3', 179 }, // ³ - { 'p', 'p', 182 }, // ¶ - { '~', '.', 183 }, // · - { '1', '1', 185 }, // ¹ - { '~', '?', 191 }, // ¿ - { 'A', '`', 192 }, // À - { 'A', '^', 194 }, // Â - { 'A', '~', 195 }, // Ã - { 'A', '"', 196 }, // Ä - { 'A', '@', 197 }, // Å - { 'E', '`', 200 }, // È - { 'E', '^', 202 }, // Ê - { 'E', '"', 203 }, // Ë - { 'I', '`', 204 }, // Ì - { 'I', '^', 206 }, // Î - { 'I', '"', 207 }, // Ï - { 'N', '~', 209 }, // Ñ - { 'O', '`', 210 }, // Ò - { 'O', '^', 212 }, // Ô - { 'O', '~', 213 }, // Õ - { '/', '\\', 215 }, // × - multiplication symbol in ISO 8859-1 - { 'U', '`', 217 }, // Ù - { 'U', '^', 219 }, // Û - { 'I', 'p', 222 }, // Þ - { 'a', '`', 224 }, // à - { 'a', '^', 226 }, // â - { 'a', '~', 227 }, // ã - { 'a', '"', 228 }, // ä - { 'a', '@', 229 }, // å - { 'e', '`', 232 }, // è - { 'e', '^', 234 }, // ê - { 'e', '"', 235 }, // ë - { 'i', '`', 236 }, // ì - { 'i', '^', 238 }, // î - { 'n', '~', 241 }, // ñ - { 'o', '`', 242 }, // ò - { 'o', '^', 244 }, // ô - { 'o', '~', 245 }, // õ - { 'u', '`', 249 }, // ù - { 'u', '^', 251 }, // û - { 'y', '"', 255 }, // x XX - { NUL, NUL, NUL } }; @@ -1436,7 +1458,7 @@ int do_digraph(int c) backspaced = -1; } else if (p_dg) { if (backspaced >= 0) { - c = getdigraph(backspaced, c, FALSE); + c = getdigraph(backspaced, c, false); } backspaced = -1; @@ -1509,7 +1531,7 @@ int get_digraph(int cmdline) if (cc != ESC) { // ESC cancels CTRL-K - return getdigraph(c, cc, TRUE); + return getdigraph(c, cc, true); } } return NUL; @@ -1521,9 +1543,9 @@ int get_digraph(int cmdline) /// @param char2 /// @param meta_char /// -/// @return If no match, return "char2". If "meta_char" is TRUE and "char1" +/// @return If no match, return "char2". If "meta_char" is true and "char1" // is a space, return "char2" | 0x80. -static int getexactdigraph(int char1, int char2, int meta_char) +static int getexactdigraph(int char1, int char2, bool meta_char) { int retval = 0; @@ -1573,7 +1595,7 @@ static int getexactdigraph(int char1, int char2, int meta_char) /// @param meta_char /// /// @return The digraph. -int getdigraph(int char1, int char2, int meta_char) +int getdigraph(int char1, int char2, bool meta_char) { int retval; @@ -1643,9 +1665,20 @@ void putdigraph(char_u *str) } } -void listdigraphs(void) +static void digraph_header(const char *msg) + FUNC_ATTR_NONNULL_ALL +{ + if (msg_col > 0) { + msg_putchar('\n'); + } + msg_outtrans_attr((const char_u *)msg, HL_ATTR(HLF_CM)); + msg_putchar('\n'); +} + +void listdigraphs(bool use_headers) { digr_T *dp; + result_T previous = 0; msg_putchar('\n'); @@ -1657,25 +1690,63 @@ void listdigraphs(void) // May need to convert the result to 'encoding'. tmp.char1 = dp->char1; tmp.char2 = dp->char2; - tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE); + tmp.result = getexactdigraph(tmp.char1, tmp.char2, false); if ((tmp.result != 0) && (tmp.result != tmp.char2)) { - printdigraph(&tmp); + printdigraph(&tmp, use_headers ? &previous : NULL); } dp++; fast_breakcheck(); } dp = (digr_T *)user_digraphs.ga_data; - for (int i = 0; i < user_digraphs.ga_len && !got_int; ++i) { - printdigraph(dp); + for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) { + if (previous >= 0 && use_headers) { + digraph_header(_("Custom")); + } + previous = -1; + printdigraph(dp, NULL); fast_breakcheck(); dp++; } } -static void printdigraph(digr_T *dp) +struct dg_header_entry { + int dg_start; + const char *dg_header; +} header_table[] = { + { DG_START_LATIN, N_("Latin supplement") }, + { DG_START_GREEK, N_("Greek and Coptic") }, + { DG_START_CYRILLIC, N_("Cyrillic") }, + { DG_START_HEBREW, N_("Hebrew") }, + { DG_START_ARABIC, N_("Arabic") }, + { DG_START_LATIN_EXTENDED, N_("Latin extended") }, + { DG_START_GREEK_EXTENDED, N_("Greek extended") }, + { DG_START_PUNCTUATION, N_("Punctuation") }, + { DG_START_SUB_SUPER, N_("Super- and subscripts") }, + { DG_START_CURRENCY, N_("Currency") }, + { DG_START_OTHER1, N_("Other") }, + { DG_START_ROMAN, N_("Roman numbers") }, + { DG_START_ARROWS, N_("Arrows") }, + { DG_START_MATH, N_("Mathematical operators") }, + { DG_START_TECHNICAL, N_("Technical") }, + { DG_START_OTHER2, N_("Other") }, + { DG_START_DRAWING, N_("Box drawing") }, + { DG_START_BLOCK, N_("Block elements") }, + { DG_START_SHAPES, N_("Geometric shapes") }, + { DG_START_SYMBOLS, N_("Symbols") }, + { DG_START_DINGBATS, N_("Dingbats") }, + { DG_START_CJK_SYMBOLS, N_("CJK symbols and punctuation") }, + { DG_START_HIRAGANA, N_("Hiragana") }, + { DG_START_KATAKANA, N_("Katakana") }, + { DG_START_BOPOMOFO, N_("Bopomofo") }, + { DG_START_OTHER3, N_("Other") }, + { 0xfffffff, NULL }, +}; + +static void printdigraph(const digr_T *dp, result_T *previous) + FUNC_ATTR_NONNULL_ARG(1) { char_u buf[30]; char_u *p; @@ -1685,6 +1756,17 @@ static void printdigraph(digr_T *dp) list_width = 13; if (dp->result != 0) { + if (previous != NULL) { + for (int i = 0; header_table[i].dg_header != NULL; i++) { + if (*previous < header_table[i].dg_start + && dp->result >= header_table[i].dg_start + && dp->result < header_table[i + 1].dg_start) { + digraph_header(_(header_table[i].dg_header)); + break; + } + } + *previous = dp->result; + } if (msg_col > Columns - list_width) { msg_putchar('\n'); } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index a709acd4ef..6317ec77ff 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -758,7 +758,7 @@ return { }, { command='digraphs', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), addr_type=ADDR_LINES, func='ex_digraphs', }, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 34b4c10d3e..29b63c3f99 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -10010,10 +10010,11 @@ static void ex_setfiletype(exarg_T *eap) static void ex_digraphs(exarg_T *eap) { - if (*eap->arg != NUL) + if (*eap->arg != NUL) { putdigraph(eap->arg); - else - listdigraphs(); + } else { + listdigraphs(eap->forceit); + } } static void ex_set(exarg_T *eap) diff --git a/src/nvim/message.c b/src/nvim/message.c index 30e906cd5f..b518664f32 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -238,7 +238,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int) if (next_spec != NULL) { // Printing all char that are before the char found by strpbrk - msg_outtrans_len_attr((char_u *)s, next_spec - s, attr); + msg_outtrans_len_attr((const char_u *)s, next_spec - s, attr); if (*next_spec != TAB) { msg_clr_eos(); @@ -1384,12 +1384,12 @@ int msg_outtrans(char_u *str) return msg_outtrans_attr(str, 0); } -int msg_outtrans_attr(char_u *str, int attr) +int msg_outtrans_attr(const char_u *str, int attr) { return msg_outtrans_len_attr(str, (int)STRLEN(str), attr); } -int msg_outtrans_len(char_u *str, int len) +int msg_outtrans_len(const char_u *str, int len) { return msg_outtrans_len_attr(str, len, 0); } @@ -1402,7 +1402,7 @@ char_u *msg_outtrans_one(char_u *p, int attr) { int l; - if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) { + if ((l = utfc_ptr2len(p)) > 1) { msg_outtrans_len_attr(p, l, attr); return p + l; } @@ -1410,7 +1410,7 @@ char_u *msg_outtrans_one(char_u *p, int attr) return p + 1; } -int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) +int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) { int retval = 0; const char *str = (const char *)msgstr; -- cgit From c8fe2a8d23978b1faf9f03af476dddbccf0c76f7 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 6 Oct 2019 23:35:52 +0200 Subject: test/old: add test_fnamemodify.vim (#11168) Moved to a new-style test in vim/vim@610cc1b9b (v7.4.1652). Ref: https://github.com/neovim/neovim/pull/11165#issuecomment-538785588 --- src/nvim/testdir/test_fnamemodify.vim | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/nvim/testdir/test_fnamemodify.vim (limited to 'src') diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim new file mode 100644 index 0000000000..63f273677d --- /dev/null +++ b/src/nvim/testdir/test_fnamemodify.vim @@ -0,0 +1,47 @@ +" Test filename modifiers. + +func Test_fnamemodify() + let save_home = $HOME + let save_shell = &shell + let $HOME = fnamemodify('.', ':p:h:h') + set shell=sh + + call assert_equal('/', fnamemodify('.', ':p')[-1:]) + call assert_equal('r', fnamemodify('.', ':p:h')[-1:]) + call assert_equal('t', fnamemodify('test.out', ':p')[-1:]) + call assert_equal('test.out', fnamemodify('test.out', ':.')) + call assert_equal('a', fnamemodify('../testdir/a', ':.')) + call assert_equal('~/testdir/test.out', fnamemodify('test.out', ':~')) + call assert_equal('~/testdir/a', fnamemodify('../testdir/a', ':~')) + call assert_equal('a', fnamemodify('../testdir/a', ':t')) + call assert_equal('', fnamemodify('.', ':p:t')) + call assert_equal('test.out', fnamemodify('test.out', ':p:t')) + call assert_equal('out', fnamemodify('test.out', ':p:e')) + call assert_equal('out', fnamemodify('test.out', ':p:t:e')) + call assert_equal('abc.fb2.tar', fnamemodify('abc.fb2.tar.gz', ':r')) + call assert_equal('abc.fb2', fnamemodify('abc.fb2.tar.gz', ':r:r')) + call assert_equal('abc', fnamemodify('abc.fb2.tar.gz', ':r:r:r')) + call assert_equal('testdir/abc.fb2', substitute(fnamemodify('abc.fb2.tar.gz', ':p:r:r'), '.*\(testdir/.*\)', '\1', '')) + call assert_equal('gz', fnamemodify('abc.fb2.tar.gz', ':e')) + call assert_equal('tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e')) + call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e')) + call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e')) + call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r')) + + call assert_equal('''abc def''', fnamemodify('abc def', ':S')) + call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S')) + call assert_equal('''abc"%"def''', fnamemodify('abc"%"def', ':S')) + call assert_equal('''abc''\'''' ''\''''def''', fnamemodify('abc'' ''def', ':S')) + call assert_equal('''abc''\''''%''\''''def''', fnamemodify('abc''%''def', ':S')) + sp test_alot.vim + call assert_equal(expand('%:r:S'), shellescape(expand('%:r'))) + call assert_equal('test_alot,''test_alot'',test_alot.vim', join([expand('%:r'), expand('%:r:S'), expand('%')], ',')) + quit + + call assert_equal("'abc\ndef'", fnamemodify("abc\ndef", ':S')) + set shell=tcsh + call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S')) + + let $HOME = save_home + let &shell = save_shell +endfunc -- cgit From 09232958ff9c7d4737701160549c8d64a0f92856 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 6 Oct 2019 20:52:30 -0400 Subject: vim-patch:8.1.2120: some MB_ macros are more complicated than necessary Problem: Some MB_ macros are more complicated than necessary. (Dominique Pelle) Solution: Simplify the macros. Expand inline. https://github.com/vim/vim/commit/1614a14901558ca091329315d14a7d5e1b53aa47 --- src/nvim/diff.c | 2 +- src/nvim/eval.c | 6 +++--- src/nvim/ex_getln.c | 2 +- src/nvim/file_search.c | 4 ++-- src/nvim/getchar.c | 12 ++++++------ src/nvim/macros.h | 2 -- src/nvim/ops.c | 2 +- src/nvim/path.c | 24 +++++++++++++----------- src/nvim/regexp_nfa.c | 2 +- src/nvim/screen.c | 2 +- src/nvim/search.c | 18 +++++++++--------- src/nvim/spell.c | 30 +++++++++++++++--------------- 12 files changed, 53 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/nvim/diff.c b/src/nvim/diff.c index db3ef7ac47..34cac5a098 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -724,7 +724,7 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) c = PTR2CHAR(s); c = enc_utf8 ? utf_fold(c) : TOLOWER_LOC(c); - orig_len = MB_PTR2LEN(s); + orig_len = utfc_ptr2len(s); if (utf_char2bytes(c, cbuf) != orig_len) { // TODO(Bram): handle byte length difference memmove(ptr + len, s, orig_len); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e3e5bb9a90..925b64d42c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -15502,7 +15502,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else set_last_csearch(PTR2CHAR(csearch), - csearch, MB_PTR2LEN(csearch)); + csearch, utfc_ptr2len(csearch)); } di = tv_dict_find(d, S_LEN("forward")); @@ -24051,8 +24051,8 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, /* Skip empty match except for first match. */ if (regmatch.startp[0] == regmatch.endp[0]) { if (zero_width == regmatch.startp[0]) { - /* avoid getting stuck on a match with an empty string */ - int i = MB_PTR2LEN(tail); + // avoid getting stuck on a match with an empty string + int i = utfc_ptr2len(tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); ga.ga_len += i; tail += i; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 3f7bc45c2e..d0af8a0fdf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2320,7 +2320,7 @@ redraw: } while (++vcol % 8); p++; } else { - len = MB_PTR2LEN(p); + len = utfc_ptr2len(p); msg_outtrans_len(p, len); vcol += ptr2cells(p); p += len; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index ad6a481bc5..47272df2f0 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1102,8 +1102,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) prev2 = prev1; prev1 = c1; - i += MB_PTR2LEN(s1 + i); - j += MB_PTR2LEN(s2 + j); + i += utfc_ptr2len(s1 + i); + j += utfc_ptr2len(s2 + j); } return s1[i] == s2[j]; } diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index af642a8e11..1f82df3241 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1833,13 +1833,13 @@ static int vgetorpeek(int advance) char_u *p1 = mp->m_keys; char_u *p2 = (char_u *)mb_unescape((const char **)&p1); - if (has_mbyte && p2 != NULL && MB_BYTE2LEN(c1) > MB_PTR2LEN(p2)) + if (p2 != NULL && MB_BYTE2LEN(c1) > utfc_ptr2len(p2)) { mlen = 0; - /* - * Check an entry whether it matches. - * - Full match: mlen == keylen - * - Partly match: mlen == typebuf.tb_len - */ + } + + // Check an entry whether it matches. + // - Full match: mlen == keylen + // - Partly match: mlen == typebuf.tb_len keylen = mp->m_keylen; if (mlen == keylen || (mlen == typebuf.tb_len diff --git a/src/nvim/macros.h b/src/nvim/macros.h index d11507fa6a..f6c8c0a4a0 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -110,8 +110,6 @@ // MB_COPY_CHAR(f, t): copy one char from "f" to "t" and advance the pointers. // PTR2CHAR(): get character from pointer. -// Get the length of the character p points to, including composing chars. -# define MB_PTR2LEN(p) mb_ptr2len(p) // Advance multi-byte pointer, skip over composing chars. # define MB_PTR_ADV(p) (p += mb_ptr2len((char_u *)p)) // Advance multi-byte pointer, do not skip over composing chars. diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 4f0c1b5cb9..6f515151d6 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4641,7 +4641,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) if (visual) { while (ptr[col] != NUL && length > 0 && !ascii_isdigit(ptr[col]) && !(doalp && ASCII_ISALPHA(ptr[col]))) { - int mb_len = MB_PTR2LEN(ptr + col); + int mb_len = utfc_ptr2len(ptr + col); col += mb_len; length -= mb_len; diff --git a/src/nvim/path.c b/src/nvim/path.c index 62d5d69d1a..a53870acb8 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -338,9 +338,9 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { break; } - len -= (size_t)MB_PTR2LEN((const char_u *)p1); - p1 += MB_PTR2LEN((const char_u *)p1); - p2 += MB_PTR2LEN((const char_u *)p2); + len -= (size_t)utfc_ptr2len((const char_u *)p1); + p1 += utfc_ptr2len((const char_u *)p1); + p2 += utfc_ptr2len((const char_u *)p2); } return c1 - c2; #else @@ -1910,16 +1910,16 @@ int pathcmp(const char *p, const char *q, int maxlen) : c1 - c2; // no match } - i += MB_PTR2LEN((char_u *)p + i); - j += MB_PTR2LEN((char_u *)q + j); + i += utfc_ptr2len((char_u *)p + i); + j += utfc_ptr2len((char_u *)q + j); } if (s == NULL) { // "i" or "j" ran into "maxlen" return 0; } c1 = PTR2CHAR((char_u *)s + i); - c2 = PTR2CHAR((char_u *)s + i + MB_PTR2LEN((char_u *)s + i)); - /* ignore a trailing slash, but not "//" or ":/" */ + c2 = PTR2CHAR((char_u *)s + i + utfc_ptr2len((char_u *)s + i)); + // ignore a trailing slash, but not "//" or ":/" if (c2 == NUL && i > 0 && !after_pathsep((char *)s, (char *)s + i) @@ -1928,10 +1928,12 @@ int pathcmp(const char *p, const char *q, int maxlen) #else && c1 == '/' #endif - ) - return 0; /* match with trailing slash */ - if (s == q) - return -1; /* no match */ + ) { + return 0; // match with trailing slash + } + if (s == q) { + return -1; // no match + } return 1; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 63640e723e..f33c61d39f 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4943,7 +4943,7 @@ static int skip_to_start(int c, colnr_T *colp) */ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) { -#define PTR2LEN(x) enc_utf8 ? utf_ptr2len(x) : MB_PTR2LEN(x) +#define PTR2LEN(x) utf_ptr2len(x) colnr_T col = startcol; int regstart_len = PTR2LEN(regline + startcol); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 488341c6a7..1ce0b5217e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2994,7 +2994,7 @@ win_line ( if (shl->startcol != MAXCOL && v >= (long)shl->startcol && v < (long)shl->endcol) { - int tmp_col = v + MB_PTR2LEN(ptr); + int tmp_col = v + utfc_ptr2len(ptr); if (shl->endcol < tmp_col) { shl->endcol = tmp_col; diff --git a/src/nvim/search.c b/src/nvim/search.c index 85c0d7eb48..1f382d31c5 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -681,7 +681,7 @@ int searchit( } if (matchcol == matchpos.col && ptr[matchcol] != NUL) { - matchcol += MB_PTR2LEN(ptr + matchcol); + matchcol += utfc_ptr2len(ptr + matchcol); } if (matchcol == 0 && (options & SEARCH_START)) { @@ -1755,7 +1755,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) find_mps_values(&initc, &findc, &backwards, FALSE); if (findc) break; - pos.col += MB_PTR2LEN(linep + pos.col); + pos.col += utfc_ptr2len(linep + pos.col); } if (!findc) { /* no brace in the line, maybe use " #if" then */ @@ -2234,14 +2234,14 @@ showmatch( for (p = curbuf->b_p_mps; *p != NUL; ++p) { if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri)) break; - p += MB_PTR2LEN(p) + 1; - if (PTR2CHAR(p) == c - && !(curwin->w_p_rl ^ p_ri) - ) + p += utfc_ptr2len(p) + 1; + if (PTR2CHAR(p) == c && !(curwin->w_p_rl ^ p_ri)) { break; - p += MB_PTR2LEN(p); - if (*p == NUL) + } + p += utfc_ptr2len(p); + if (*p == NUL) { return; + } } if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep @@ -4845,7 +4845,7 @@ exit_matched: && action == ACTION_EXPAND && !(compl_cont_status & CONT_SOL) && *startp != NUL - && *(p = startp + MB_PTR2LEN(startp)) != NUL) + && *(p = startp + utfc_ptr2len(startp)) != NUL) goto search_line; } line_breakcheck(); diff --git a/src/nvim/spell.c b/src/nvim/spell.c index ab40355a8a..503657c427 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2577,7 +2577,7 @@ static bool spell_iswordp(char_u *p, win_T *wp) int c; if (has_mbyte) { - l = MB_PTR2LEN(p); + l = utfc_ptr2len(p); s = p; if (l == 1) { // be quick for ASCII @@ -4118,7 +4118,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - l = MB_PTR2LEN(fword + sp->ts_fidx); + l = utfc_ptr2len(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, @@ -4264,7 +4264,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = sp->ts_fcharstart - + MB_PTR2LEN(fword + sp->ts_fcharstart); + + utfc_ptr2len(fword + sp->ts_fcharstart); // For changing a composing character adjust // the score from SCORE_SUBST to @@ -4363,7 +4363,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // a bit illogical for soundfold tree but it does give better // results. c = utf_ptr2char(fword + sp->ts_fidx); - stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); + stack[depth].ts_fidx += utfc_ptr2len(fword + sp->ts_fidx); if (utf_iscomposing(c)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; } else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) { @@ -4533,9 +4533,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNSWAP: // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; - n = MB_PTR2LEN(p); + n = utfc_ptr2len(p); c = utf_ptr2char(p + n); - memmove(p + MB_PTR2LEN(p + n), p, n); + memmove(p + utfc_ptr2len(p + n), p, n); utf_char2bytes(c, p); FALLTHROUGH; @@ -4589,11 +4589,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNSWAP3: // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; - n = MB_PTR2LEN(p); + n = utfc_ptr2len(p); c2 = utf_ptr2char(p + n); - fl = MB_PTR2LEN(p + n); + fl = utfc_ptr2len(p + n); c = utf_ptr2char(p + n + fl); - tl = MB_PTR2LEN(p + n + fl); + tl = utfc_ptr2len(p + n + fl); memmove(p + fl + tl, p, n); utf_char2bytes(c, p); utf_char2bytes(c2, p + tl); @@ -4637,10 +4637,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNROT3L: // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; - n = MB_PTR2LEN(p); - n += MB_PTR2LEN(p + n); + n = utfc_ptr2len(p); + n += utfc_ptr2len(p + n); c = utf_ptr2char(p + n); - tl = MB_PTR2LEN(p + n); + tl = utfc_ptr2len(p + n); memmove(p + tl, p, n); utf_char2bytes(c, p); @@ -4675,9 +4675,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo ROT3R: "312" -> "123" p = fword + sp->ts_fidx; c = utf_ptr2char(p); - tl = MB_PTR2LEN(p); - n = MB_PTR2LEN(p + tl); - n += MB_PTR2LEN(p + tl + n); + tl = utfc_ptr2len(p); + n = utfc_ptr2len(p + tl); + n += utfc_ptr2len(p + tl + n); memmove(p, p + tl, n); utf_char2bytes(c, p + n); -- cgit From 97cdfdcde24c6a804f879b6464512008db4b5cef Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 6 Oct 2019 21:22:02 -0400 Subject: Remove dead code --- src/nvim/diff.c | 2 +- src/nvim/spell.c | 46 ++++++++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 34cac5a098..31552929dc 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -723,7 +723,7 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) char_u cbuf[MB_MAXBYTES + 1]; c = PTR2CHAR(s); - c = enc_utf8 ? utf_fold(c) : TOLOWER_LOC(c); + c = utf_fold(c); orig_len = utfc_ptr2len(s); if (utf_char2bytes(c, cbuf) != orig_len) { // TODO(Bram): handle byte length difference diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 503657c427..a3c1746383 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2570,36 +2570,33 @@ void init_spell_chartab(void) /// Thus this only works properly when past the first character of the word. /// /// @param wp Buffer used. -static bool spell_iswordp(char_u *p, win_T *wp) +static bool spell_iswordp(const char_u *p, const win_T *wp) + FUNC_ATTR_NONNULL_ALL { - char_u *s; - int l; int c; - if (has_mbyte) { - l = utfc_ptr2len(p); - s = p; - if (l == 1) { - // be quick for ASCII - if (wp->w_s->b_spell_ismw[*p]) - s = p + 1; // skip a mid-word character - } else { - c = utf_ptr2char(p); - if (c < 256 ? wp->w_s->b_spell_ismw[c] - : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) { - s = p + l; - } + const int l = utfc_ptr2len(p); + const char_u *s = p; + if (l == 1) { + // be quick for ASCII + if (wp->w_s->b_spell_ismw[*p]) { + s = p + 1; // skip a mid-word character } - - c = utf_ptr2char(s); - if (c > 255) { - return spell_mb_isword_class(mb_get_class(s), wp); + } else { + c = utf_ptr2char(p); + if (c < 256 + ? wp->w_s->b_spell_ismw[c] + : (wp->w_s->b_spell_ismw_mb != NULL + && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) { + s = p + l; } - return spelltab.st_isw[c]; } - return spelltab.st_isw[wp->w_s->b_spell_ismw[*p] ? p[1] : p[0]]; + c = utf_ptr2char(s); + if (c > 255) { + return spell_mb_isword_class(mb_get_class(s), wp); + } + return spelltab.st_isw[c]; } // Returns true if "p" points to a word character. @@ -2617,7 +2614,8 @@ bool spell_iswordp_nmw(const char_u *p, win_T *wp) // Only for characters above 255. // Unicode subscript and superscript are not considered word characters. // See also utf_class() in mbyte.c. -static bool spell_mb_isword_class(int cl, win_T *wp) +static bool spell_mb_isword_class(int cl, const win_T *wp) + FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (wp->w_s->b_cjk) // East Asian characters are not considered word characters. -- cgit From b1ada8ec2159fbc69b58cc40eb62a4e76edd8d45 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sun, 22 Sep 2019 21:47:24 +0200 Subject: vim-patch:8.1.1354: getting a list of text lines is clumsy Problem: Getting a list of text lines is clumsy. Solution: Add the =<< assignment. (Yegappan Lakshmanan, closes vim/vim#4386) https://github.com/vim/vim/commit/f5842c5a533346c4ff41ff666e465c85f1de35d5 --- src/nvim/eval.c | 92 +++++++++++++++++++++++++++++++++++++++++++ src/nvim/testdir/test_let.vim | 55 ++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 925b64d42c..9b12ca1e12 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1504,6 +1504,87 @@ void ex_const(exarg_T *eap) ex_let_const(eap, true); } +// Get a list of lines from a HERE document. The here document is a list of +// lines surrounded by a marker. +// cmd << {marker} +// {line1} +// {line2} +// .... +// {marker} +// +// The {marker} is a string. If the optional 'trim' word is supplied before the +// marker, then the leading indentation before the lines (matching the +// indentation in the 'cmd' line) is stripped. +// Returns a List with {lines} or NULL. +static list_T * +heredoc_get(exarg_T *eap, char_u *cmd) +{ + char_u *marker; + char_u *p; + int indent_len = 0; + + if (eap->getline == NULL) { + EMSG(_("E991: cannot use =<< here")); + return NULL; + } + + // Check for the optional 'trim' word before the marker + cmd = skipwhite(cmd); + if (STRNCMP(cmd, "trim", 4) == 0 + && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { + cmd = skipwhite(cmd + 4); + + // Trim the indentation from all the lines in the here document + // The amount of indentation trimmed is the same as the indentation of + // the :let command line. + p = *eap->cmdlinep; + while (ascii_iswhite(*p)) { + p++; + indent_len++; + } + } + + // The marker is the next word. Default marker is "." + if (*cmd != NUL && *cmd != '"') { + marker = skipwhite(cmd); + p = skiptowhite(marker); + if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { + EMSG(_(e_trailing)); + return NULL; + } + *p = NUL; + } else { + marker = (char_u *)"."; + } + + list_T *l = tv_list_alloc(0); + for (;;) { + int i = 0; + + char_u *theline = eap->getline(NUL, eap->cookie, 0); + if (theline != NULL && indent_len > 0) { + // trim the indent matching the first line + if (STRNCMP(theline, *eap->cmdlinep, indent_len) == 0) { + i = indent_len; + } + } + + if (theline == NULL) { + EMSG2(_("E990: Missing end marker '%s'"), marker); + break; + } + if (STRCMP(marker, theline + i) == 0) { + xfree(theline); + break; + } + + tv_list_append_string(l, (char *)(theline + i), -1); + xfree(theline); + } + + return l; +} + // ":let" list all variable values // ":let var1 var2" list variable values // ":let var = expr" assignment command. @@ -1560,6 +1641,17 @@ static void ex_let_const(exarg_T *eap, const bool is_const) list_vim_vars(&first); } eap->nextcmd = check_nextcmd(arg); + } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { + // HERE document + list_T *l = heredoc_get(eap, expr + 3); + if (l != NULL) { + tv_list_set_ret(&rettv, l); + op[0] = '='; + op[1] = NUL; + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); + tv_clear(&rettv); + } } else { op[0] = '='; op[1] = NUL; diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 8a6f1bc320..9887fb531e 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -140,3 +140,58 @@ func Test_let_varg_fail() call assert_fails('call s:set_varg7(1)', 'E742:') call s:set_varg8([0]) endfunction + + +" Test for the setting a variable using the heredoc syntax +func Test_let_heredoc() + let var1 =<< END +Some sample text + Text with indent + !@#$%^&*()-+_={}|[]\~`:";'<>?,./ +END + + call assert_equal(["Some sample text", "\tText with indent", " !@#$%^&*()-+_={}|[]\\~`:\";'<>?,./"], var1) + + let var2 =<< +Editor +. + call assert_equal(['Editor'], var2) + + let var3 =< Date: Mon, 23 Sep 2019 19:46:45 +0200 Subject: vim-patch:8.1.1356: some text in heredoc assignment ends the text Problem: Some text in heredoc assignment ends the text. (Ozaki Kiichi) Solution: Recognize "let v =<<" and skip until the end. https://github.com/vim/vim/commit/8471e57026714c5a0faf89288ceef5231fb88d4f --- src/nvim/eval.c | 34 ++++++++++++++++--- src/nvim/testdir/test_let.vim | 79 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9b12ca1e12..6d706939a1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21269,6 +21269,7 @@ void ex_function(exarg_T *eap) int indent; int nesting; char_u *skip_until = NULL; + char_u *trimmed = NULL; dictitem_T *v; funcdict_T fudi; static int func_nr = 0; /* number for nameless function */ @@ -21572,10 +21573,14 @@ void ex_function(exarg_T *eap) sourcing_lnum_off = 0; if (skip_until != NULL) { - /* between ":append" and "." and between ":python < Date: Mon, 23 Sep 2019 22:12:02 +0200 Subject: vim-patch:8.1.1362: code and data in tests can be hard to read Problem: Code and data in tests can be hard to read. Solution: Use the new heredoc style. (Yegappan Lakshmanan, closes vim/vim#4400) https://github.com/vim/vim/commit/c79745a82faeb5a6058e915ca49a4c69fa60ea01 --- src/nvim/testdir/test_autocmd.vim | 102 ++++---- src/nvim/testdir/test_cindent.vim | 60 +++-- src/nvim/testdir/test_exit.vim | 66 ++--- src/nvim/testdir/test_fold.vim | 23 +- src/nvim/testdir/test_goto.vim | 406 ++++++++++++++++--------------- src/nvim/testdir/test_mksession_utf8.vim | 57 ++--- src/nvim/testdir/test_normal.vim | 181 ++++++++++---- src/nvim/testdir/test_profile.vim | 266 ++++++++++---------- src/nvim/testdir/test_quickfix.vim | 252 +++++++++---------- src/nvim/testdir/test_startup.vim | 150 ++++++------ 10 files changed, 859 insertions(+), 704 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 4dd48beef4..275b053bc9 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -425,18 +425,20 @@ func Test_autocmd_bufwipe_in_SessLoadPost() set noswapfile mksession! - let content = ['set nocp noswapfile', - \ 'let v:swapchoice="e"', - \ 'augroup test_autocmd_sessionload', - \ 'autocmd!', - \ 'autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"', - \ 'augroup END', - \ '', - \ 'func WriteErrors()', - \ ' call writefile([execute("messages")], "Xerrors")', - \ 'endfunc', - \ 'au VimLeave * call WriteErrors()', - \ ] + let content =<< trim [CODE] + set nocp noswapfile + let v:swapchoice="e" + augroup test_autocmd_sessionload + autocmd! + autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!" + augroup END + + func WriteErrors() + call writefile([execute("messages")], "Xerrors") + endfunc + au VimLeave * call WriteErrors() + [CODE] + call writefile(content, 'Xvimrc') call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq') let errors = join(readfile('Xerrors')) @@ -454,27 +456,29 @@ func Test_autocmd_bufwipe_in_SessLoadPost2() set noswapfile mksession! - let content = ['set nocp noswapfile', - \ 'function! DeleteInactiveBufs()', - \ ' tabfirst', - \ ' let tabblist = []', - \ ' for i in range(1, tabpagenr(''$''))', - \ ' call extend(tabblist, tabpagebuflist(i))', - \ ' endfor', - \ ' for b in range(1, bufnr(''$''))', - \ ' if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')', - \ ' exec ''bwipeout '' . b', - \ ' endif', - \ ' endfor', - \ ' echomsg "SessionLoadPost DONE"', - \ 'endfunction', - \ 'au SessionLoadPost * call DeleteInactiveBufs()', - \ '', - \ 'func WriteErrors()', - \ ' call writefile([execute("messages")], "Xerrors")', - \ 'endfunc', - \ 'au VimLeave * call WriteErrors()', - \ ] + let content =<< trim [CODE] + set nocp noswapfile + function! DeleteInactiveBufs() + tabfirst + let tabblist = [] + for i in range(1, tabpagenr(''$'')) + call extend(tabblist, tabpagebuflist(i)) + endfor + for b in range(1, bufnr(''$'')) + if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'') + exec ''bwipeout '' . b + endif + endfor + echomsg "SessionLoadPost DONE" + endfunction + au SessionLoadPost * call DeleteInactiveBufs() + + func WriteErrors() + call writefile([execute("messages")], "Xerrors") + endfunc + au VimLeave * call WriteErrors() + [CODE] + call writefile(content, 'Xvimrc') call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq') let errors = join(readfile('Xerrors')) @@ -936,21 +940,23 @@ func Test_bufunload_all() call writefile(['Test file Xxx1'], 'Xxx1')" call writefile(['Test file Xxx2'], 'Xxx2')" - let content = [ - \ "func UnloadAllBufs()", - \ " let i = 1", - \ " while i <= bufnr('$')", - \ " if i != bufnr('%') && bufloaded(i)", - \ " exe i . 'bunload'", - \ " endif", - \ " let i += 1", - \ " endwhile", - \ "endfunc", - \ "au BufUnload * call UnloadAllBufs()", - \ "au VimLeave * call writefile(['Test Finished'], 'Xout')", - \ "edit Xxx1", - \ "split Xxx2", - \ "q"] + let content =<< trim [CODE] + func UnloadAllBufs() + let i = 1 + while i <= bufnr('$') + if i != bufnr('%') && bufloaded(i) + exe i . 'bunload' + endif + let i += 1 + endwhile + endfunc + au BufUnload * call UnloadAllBufs() + au VimLeave * call writefile(['Test Finished'], 'Xout') + edit Xxx1 + split Xxx2 + q + [CODE] + call writefile(content, 'Xtest') call delete('Xout') diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 7c2c5e341c..f979e354ba 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -18,25 +18,25 @@ endfunc func Test_cino_extern_c() " Test for cino-E - let without_ind = [ - \ '#ifdef __cplusplus', - \ 'extern "C" {', - \ '#endif', - \ 'int func_a(void);', - \ '#ifdef __cplusplus', - \ '}', - \ '#endif' - \ ] + let without_ind =<< trim [CODE] + #ifdef __cplusplus + extern "C" { + #endif + int func_a(void); + #ifdef __cplusplus + } + #endif + [CODE] - let with_ind = [ - \ '#ifdef __cplusplus', - \ 'extern "C" {', - \ '#endif', - \ "\tint func_a(void);", - \ '#ifdef __cplusplus', - \ '}', - \ '#endif' - \ ] + let with_ind =<< trim [CODE] + #ifdef __cplusplus + extern "C" { + #endif + int func_a(void); + #ifdef __cplusplus + } + #endif + [CODE] new setlocal cindent cinoptions=E0 call setline(1, without_ind) @@ -89,16 +89,32 @@ func Test_cindent_expr() return v:lnum == 1 ? shiftwidth() : 0 endfunc setl expandtab sw=8 indentkeys+=; indentexpr=MyIndentFunction() - call setline(1, ['var_a = something()', 'b = something()']) + let testinput =<< trim [CODE] + var_a = something() + b = something() + [CODE] + call setline(1, testinput) call cursor(1, 1) call feedkeys("^\j$A;\", 'tnix') - call assert_equal([' var_a = something();', 'b = something();'], getline(1, '$')) + let expected =<< trim [CODE] + var_a = something(); + b = something(); + [CODE] + call assert_equal(expected, getline(1, '$')) %d - call setline(1, [' var_a = something()', ' b = something()']) + let testinput =<< trim [CODE] + var_a = something() + b = something() + [CODE] + call setline(1, testinput) call cursor(1, 1) call feedkeys("^\j$A;\", 'tnix') - call assert_equal([' var_a = something();', ' b = something()'], getline(1, '$')) + let expected =<< trim [CODE] + var_a = something(); + b = something() + [CODE] + call assert_equal(expected, getline(1, '$')) bw! endfunc diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index 8f02fd29e3..3797626abf 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -3,52 +3,56 @@ source shared.vim func Test_exiting() - let after = [ - \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")', - \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")', - \ 'quit', - \ ] + let after =<< trim [CODE] + au QuitPre * call writefile(["QuitPre"], "Xtestout") + au ExitPre * call writefile(["ExitPre"], "Xtestout", "a") + quit + [CODE] + if RunVim([], after, '') call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout')) endif call delete('Xtestout') - let after = [ - \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")', - \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")', - \ 'help', - \ 'wincmd w', - \ 'quit', - \ ] + let after =<< trim [CODE] + au QuitPre * call writefile(["QuitPre"], "Xtestout") + au ExitPre * call writefile(["ExitPre"], "Xtestout", "a") + help + wincmd w + quit + [CODE] + if RunVim([], after, '') call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout')) endif call delete('Xtestout') - let after = [ - \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")', - \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")', - \ 'split', - \ 'new', - \ 'qall', - \ ] + let after =<< trim [CODE] + au QuitPre * call writefile(["QuitPre"], "Xtestout") + au ExitPre * call writefile(["ExitPre"], "Xtestout", "a") + split + new + qall + [CODE] + if RunVim([], after, '') call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout')) endif call delete('Xtestout') - let after = [ - \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")', - \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")', - \ 'augroup nasty', - \ ' au ExitPre * split', - \ 'augroup END', - \ 'quit', - \ 'augroup nasty', - \ ' au! ExitPre', - \ 'augroup END', - \ 'quit', - \ ] + let after =<< trim [CODE] + au QuitPre * call writefile(["QuitPre"], "Xtestout", "a") + au ExitPre * call writefile(["ExitPre"], "Xtestout", "a") + augroup nasty + au ExitPre * split + augroup END + quit + augroup nasty + au! ExitPre + augroup END + quit + [CODE] + if RunVim([], after, '') call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'], \ readfile('Xtestout')) diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 03723b3cb5..324f3f8cf2 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -520,17 +520,18 @@ func Test_fold_create_marker_in_C() set fdm=marker fdl=9 set filetype=c - let content = [ - \ '/*', - \ ' * comment', - \ ' * ', - \ ' *', - \ ' */', - \ 'int f(int* p) {', - \ ' *p = 3;', - \ ' return 0;', - \ '}' - \] + let content =<< trim [CODE] + /* + * comment + * + * + */ + int f(int* p) { + *p = 3; + return 0; + } + [CODE] + for c in range(len(content) - 1) bw! call append(0, content) diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index c0235b1707..f04a5a7e3d 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -15,262 +15,283 @@ func XTest_goto_decl(cmd, lines, line, col) endfunc func Test_gD() - let lines = [ - \ 'int x;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int x; + + int func(void) + { + return x; + } + [CODE] + call XTest_goto_decl('gD', lines, 1, 5) endfunc func Test_gD_too() - let lines = [ - \ 'Filename x;', - \ '', - \ 'int Filename', - \ 'int func() {', - \ ' Filename x;', - \ ' return x;', - \ ] + let lines =<< trim [CODE] + Filename x; + + int Filename + int func() { + Filename x; + return x; + [CODE] + call XTest_goto_decl('gD', lines, 1, 10) endfunc func Test_gD_comment() - let lines = [ - \ '/* int x; */', - \ 'int x;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + /* int x; */ + int x; + + int func(void) + { + return x; + } + [CODE] + call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gD_inline_comment() - let lines = [ - \ 'int y /* , x */;', - \ 'int x;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int y /* , x */; + int x; + + int func(void) + { + return x; + } + [CODE] + call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gD_string() - let lines = [ - \ 'char *s[] = "x";', - \ 'int x = 1;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + char *s[] = "x"; + int x = 1; + + int func(void) + { + return x; + } + [CODE] + call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gD_string_same_line() - let lines = [ - \ 'char *s[] = "x", int x = 1;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + char *s[] = "x", int x = 1; + + int func(void) + { + return x; + } + [CODE] + call XTest_goto_decl('gD', lines, 1, 22) endfunc func Test_gD_char() - let lines = [ - \ "char c = 'x';", - \ 'int x = 1;', - \ '', - \ 'int func(void)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + char c = 'x'; + int x = 1; + + int func(void) + { + return x; + } + [CODE] + call XTest_goto_decl('gD', lines, 2, 5) endfunc func Test_gd() - let lines = [ - \ 'int x;', - \ '', - \ 'int func(int x)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int x; + + int func(int x) + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 3, 14) endfunc func Test_gd_not_local() - let lines = [ - \ 'int func1(void)', - \ '{', - \ ' return x;', - \ '}', - \ '', - \ 'int func2(int x)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func1(void) + { + return x; + } + + int func2(int x) + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 3, 10) endfunc func Test_gd_kr_style() - let lines = [ - \ 'int func(x)', - \ ' int x;', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(x) + int x; + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 2, 7) endfunc func Test_gd_missing_braces() - let lines = [ - \ 'def func1(a)', - \ ' a + 1', - \ 'end', - \ '', - \ 'a = 1', - \ '', - \ 'def func2()', - \ ' return a', - \ 'end', - \ ] + let lines =<< trim [CODE] + def func1(a) + a + 1 + end + + a = 1 + + def func2() + return a + end + [CODE] + call XTest_goto_decl('gd', lines, 1, 11) endfunc func Test_gd_comment() - let lines = [ - \ 'int func(void)', - \ '{', - \ ' /* int x; */', - \ ' int x;', - \ ' return x;', - \ '}', - \] + let lines =<< trim [CODE] + int func(void) + { + /* int x; */ + int x; + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 4, 7) endfunc func Test_gd_comment_in_string() - let lines = [ - \ 'int func(void)', - \ '{', - \ ' char *s ="//"; int x;', - \ ' int x;', - \ ' return x;', - \ '}', - \] + let lines =<< trim [CODE] + int func(void) + { + char *s ="//"; int x; + int x; + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 3, 22) endfunc func Test_gd_string_in_comment() set comments= - let lines = [ - \ 'int func(void)', - \ '{', - \ ' /* " */ int x;', - \ ' int x;', - \ ' return x;', - \ '}', - \] + let lines =<< trim [CODE] + int func(void) + { + /* " */ int x; + int x; + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 3, 15) set comments& endfunc func Test_gd_inline_comment() - let lines = [ - \ 'int func(/* x is an int */ int x)', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(/* x is an int */ int x) + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 1, 32) endfunc func Test_gd_inline_comment_only() - let lines = [ - \ 'int func(void) /* one lonely x */', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(void) /* one lonely x */ + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 3, 10) endfunc func Test_gd_inline_comment_body() - let lines = [ - \ 'int func(void)', - \ '{', - \ ' int y /* , x */;', - \ '', - \ ' for (/* int x = 0 */; y < 2; y++);', - \ '', - \ ' int x = 0;', - \ '', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(void) + { + int y /* , x */; + + for (/* int x = 0 */; y < 2; y++); + + int x = 0; + + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 7, 7) endfunc func Test_gd_trailing_multiline_comment() - let lines = [ - \ 'int func(int x) /* x is an int */', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(int x) /* x is an int */ + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 1, 14) endfunc func Test_gd_trailing_comment() - let lines = [ - \ 'int func(int x) // x is an int', - \ '{', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(int x) // x is an int + { + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 1, 14) endfunc func Test_gd_string() - let lines = [ - \ 'int func(void)', - \ '{', - \ ' char *s = "x";', - \ ' int x = 1;', - \ '', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(void) + { + char *s = "x"; + int x = 1; + + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 4, 7) endfunc func Test_gd_string_only() - let lines = [ - \ 'int func(void)', - \ '{', - \ ' char *s = "x";', - \ '', - \ ' return x;', - \ '}', - \ ] + let lines =<< trim [CODE] + int func(void) + { + char *s = "x"; + + return x; + } + [CODE] + call XTest_goto_decl('gd', lines, 5, 10) endfunc @@ -289,24 +310,25 @@ func Test_cursorline_keep_col() endfunc func Test_gd_local_block() - let lines = [ - \ ' int main()', - \ '{', - \ ' char *a = "NOT NULL";', - \ ' if(a)', - \ ' {', - \ ' char *b = a;', - \ ' printf("%s\n", b);', - \ ' }', - \ ' else', - \ ' {', - \ ' char *b = "NULL";', - \ ' return b;', - \ ' }', - \ '', - \ ' return 0;', - \ '}', - \ ] + let lines =<< trim [CODE] + int main() + { + char *a = "NOT NULL"; + if(a) + { + char *b = a; + printf("%s\n", b); + } + else + { + char *b = "NULL"; + return b; + } + + return 0; + } + [CODE] + call XTest_goto_decl('1gd', lines, 11, 11) endfunc diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim index 67af3a9ca2..36f07512a8 100644 --- a/src/nvim/testdir/test_mksession_utf8.vim +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -65,34 +65,35 @@ func Test_mksession_utf8() call wincol() mksession! test_mks.out let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"') - let expected = [ - \ 'normal! 016|', - \ 'normal! 016|', - \ 'normal! 016|', - \ 'normal! 08|', - \ 'normal! 08|', - \ 'normal! 016|', - \ 'normal! 016|', - \ 'normal! 016|', - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|", - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|", - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|", - \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'", - \ " normal! 08|", - \ " exe 'normal! ' . s:c . '|zs' . 8 . '|'", - \ " normal! 08|", - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|", - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|", - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|", - \ " exe 'normal! ' . s:c . '|zs' . 16 . '|'", - \ " normal! 016|" - \ ] + let expected =<< trim [DATA] + normal! 016| + normal! 016| + normal! 016| + normal! 08| + normal! 08| + normal! 016| + normal! 016| + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 8 . '|' + normal! 08| + exe 'normal! ' . s:c . '|zs' . 8 . '|' + normal! 08| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + [DATA] + call assert_equal(expected, li) tabclose! diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 8bc4228359..b967f84626 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1563,52 +1563,94 @@ endfunc fun! Test_normal29_brace() " basic test for { and } movements - let text= ['A paragraph begins after each empty line, and also at each of a set of', - \ 'paragraph macros, specified by the pairs of characters in the ''paragraphs''', - \ 'option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to', - \ 'the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in', - \ 'the first column). A section boundary is also a paragraph boundary.', - \ 'Note that a blank line (only containing white space) is NOT a paragraph', - \ 'boundary.', - \ '', - \ '', - \ 'Also note that this does not include a ''{'' or ''}'' in the first column. When', - \ 'the ''{'' flag is in ''cpoptions'' then ''{'' in the first column is used as a', - \ 'paragraph boundary |posix|.', - \ '{', - \ 'This is no paragraph', - \ 'unless the ''{'' is set', - \ 'in ''cpoptions''', - \ '}', - \ '.IP', - \ 'The nroff macros IP separates a paragraph', - \ 'That means, it must be a ''.''', - \ 'followed by IP', - \ '.LPIt does not matter, if afterwards some', - \ 'more characters follow.', - \ '.SHAlso section boundaries from the nroff', - \ 'macros terminate a paragraph. That means', - \ 'a character like this:', - \ '.NH', - \ 'End of text here'] + let text =<< trim [DATA] + A paragraph begins after each empty line, and also at each of a set of + paragraph macros, specified by the pairs of characters in the 'paragraphs' + option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to + the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in + the first column). A section boundary is also a paragraph boundary. + Note that a blank line (only containing white space) is NOT a paragraph + boundary. + + + Also note that this does not include a '{' or '}' in the first column. When + the '{' flag is in 'cpoptions' then '{' in the first column is used as a + paragraph boundary |posix|. + { + This is no paragraph + unless the '{' is set + in 'cpoptions' + } + .IP + The nroff macros IP separates a paragraph + That means, it must be a '.' + followed by IP + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + .NH + End of text here + [DATA] + new call append(0, text) 1 norm! 0d2} - call assert_equal(['.IP', - \ 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', 'followed by IP', - \ '.LPIt does not matter, if afterwards some', 'more characters follow.', '.SHAlso section boundaries from the nroff', - \ 'macros terminate a paragraph. That means', 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) + + let expected =<< trim [DATA] + .IP + The nroff macros IP separates a paragraph + That means, it must be a '.' + followed by IP + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + .NH + End of text here + + [DATA] + call assert_equal(expected, getline(1, '$')) + norm! 0d} - call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', - \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', - \ 'a character like this:', '.NH', 'End of text here', ''], getline(1, '$')) + + let expected =<< trim [DATA] + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + .NH + End of text here + + [DATA] + call assert_equal(expected, getline(1, '$')) + $ norm! d{ - call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', - \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', 'a character like this:', ''], getline(1, '$')) + + let expected =<< trim [DATA] + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + + [DATA] + call assert_equal(expected, getline(1, '$')) + norm! d{ - call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', ''], getline(1,'$')) + + let expected =<< trim [DATA] + .LPIt does not matter, if afterwards some + more characters follow. + + [DATA] + call assert_equal(expected, getline(1, '$')) + " Test with { in cpooptions %d call append(0, text) @@ -1616,21 +1658,62 @@ fun! Test_normal29_brace() " set cpo+={ " 1 " norm! 0d2} - " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', - " \ '.IP', 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', - " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.', - " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', - " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) + " let expected =<< trim [DATA] + " { + " This is no paragraph + " unless the '{' is set + " in 'cpoptions' + " } + " .IP + " The nroff macros IP separates a paragraph + " That means, it must be a '.' + " followed by IP + " .LPIt does not matter, if afterwards some + " more characters follow. + " .SHAlso section boundaries from the nroff + " macros terminate a paragraph. That means + " a character like this: + " .NH + " End of text here + " + " [DATA] + " call assert_equal(expected, getline(1, '$')) + " " $ " norm! d} - " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', - " \ '.IP', 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', - " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.', - " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', - " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) + " let expected =<< trim [DATA] + " { + " This is no paragraph + " unless the '{' is set + " in 'cpoptions' + " } + " .IP + " The nroff macros IP separates a paragraph + " That means, it must be a '.' + " followed by IP + " .LPIt does not matter, if afterwards some + " more characters follow. + " .SHAlso section boundaries from the nroff + " macros terminate a paragraph. That means + " a character like this: + " .NH + " End of text here + " + " [DATA] + " call assert_equal(expected, getline(1, '$')) + " " norm! gg} " norm! d5} - " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', ''], getline(1,'$')) + " + " let expected =<< trim [DATA] + " { + " This is no paragraph + " unless the '{' is set + " in 'cpoptions' + " } + + " [DATA] + " call assert_equal(expected, getline(1, '$')) " clean up set cpo-={ diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index b677ac3704..4ab20a9c77 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -6,34 +6,34 @@ endif source screendump.vim func Test_profile_func() - let lines = [ - \ 'profile start Xprofile_func.log', - \ 'profile func Foo*"', - \ "func! Foo1()", - \ "endfunc", - \ "func! Foo2()", - \ " let l:count = 100", - \ " while l:count > 0", - \ " let l:count = l:count - 1", - \ " endwhile", - \ "endfunc", - \ "func! Foo3()", - \ "endfunc", - \ "func! Bar()", - \ "endfunc", - \ "call Foo1()", - \ "call Foo1()", - \ "profile pause", - \ "call Foo1()", - \ "profile continue", - \ "call Foo2()", - \ "call Foo3()", - \ "call Bar()", - \ "if !v:profiling", - \ " delfunc Foo2", - \ "endif", - \ "delfunc Foo3", - \ ] + let lines =<< trim [CODE] + profile start Xprofile_func.log + profile func Foo* + func! Foo1() + endfunc + func! Foo2() + let l:count = 100 + while l:count > 0 + let l:count = l:count - 1 + endwhile + endfunc + func! Foo3() + endfunc + func! Bar() + endfunc + call Foo1() + call Foo1() + profile pause + call Foo1() + profile continue + call Foo2() + call Foo3() + call Bar() + if !v:profiling + delfunc Foo2 + endif + delfunc Foo3 + [CODE] call writefile(lines, 'Xprofile_func.vim') call system(v:progpath @@ -88,38 +88,38 @@ func Test_profile_func() endfunc func Test_profile_func_with_ifelse() - let lines = [ - \ "func! Foo1()", - \ " if 1", - \ " let x = 0", - \ " elseif 1", - \ " let x = 1", - \ " else", - \ " let x = 2", - \ " endif", - \ "endfunc", - \ "func! Foo2()", - \ " if 0", - \ " let x = 0", - \ " elseif 1", - \ " let x = 1", - \ " else", - \ " let x = 2", - \ " endif", - \ "endfunc", - \ "func! Foo3()", - \ " if 0", - \ " let x = 0", - \ " elseif 0", - \ " let x = 1", - \ " else", - \ " let x = 2", - \ " endif", - \ "endfunc", - \ "call Foo1()", - \ "call Foo2()", - \ "call Foo3()", - \ ] + let lines =<< trim [CODE] + func! Foo1() + if 1 + let x = 0 + elseif 1 + let x = 1 + else + let x = 2 + endif + endfunc + func! Foo2() + if 0 + let x = 0 + elseif 1 + let x = 1 + else + let x = 2 + endif + endfunc + func! Foo3() + if 0 + let x = 0 + elseif 0 + let x = 1 + else + let x = 2 + endif + endfunc + call Foo1() + call Foo2() + call Foo3() + [CODE] call writefile(lines, 'Xprofile_func.vim') call system(v:progpath @@ -198,41 +198,41 @@ func Test_profile_func_with_ifelse() endfunc func Test_profile_func_with_trycatch() - let lines = [ - \ "func! Foo1()", - \ " try", - \ " let x = 0", - \ " catch", - \ " let x = 1", - \ " finally", - \ " let x = 2", - \ " endtry", - \ "endfunc", - \ "func! Foo2()", - \ " try", - \ " throw 0", - \ " catch", - \ " let x = 1", - \ " finally", - \ " let x = 2", - \ " endtry", - \ "endfunc", - \ "func! Foo3()", - \ " try", - \ " throw 0", - \ " catch", - \ " throw 1", - \ " finally", - \ " let x = 2", - \ " endtry", - \ "endfunc", - \ "call Foo1()", - \ "call Foo2()", - \ "try", - \ " call Foo3()", - \ "catch", - \ "endtry", - \ ] + let lines =<< trim [CODE] + func! Foo1() + try + let x = 0 + catch + let x = 1 + finally + let x = 2 + endtry + endfunc + func! Foo2() + try + throw 0 + catch + let x = 1 + finally + let x = 2 + endtry + endfunc + func! Foo3() + try + throw 0 + catch + throw 1 + finally + let x = 2 + endtry + endfunc + call Foo1() + call Foo2() + try + call Foo3() + catch + endtry + [CODE] call writefile(lines, 'Xprofile_func.vim') call system(v:progpath @@ -311,15 +311,15 @@ func Test_profile_func_with_trycatch() endfunc func Test_profile_file() - let lines = [ - \ 'func! Foo()', - \ 'endfunc', - \ 'for i in range(10)', - \ ' " a comment', - \ ' call Foo()', - \ 'endfor', - \ 'call Foo()', - \ ] + let lines =<< trim [CODE] + func! Foo() + endfunc + for i in range(10) + " a comment + call Foo() + endfor + call Foo() + [CODE] call writefile(lines, 'Xprofile_file.vim') call system(v:progpath @@ -450,26 +450,27 @@ func Test_profile_truncate_mbyte() endfunc func Test_profdel_func() - let lines = [ - \ 'profile start Xprofile_file.log', - \ 'func! Foo1()', - \ 'endfunc', - \ 'func! Foo2()', - \ 'endfunc', - \ 'func! Foo3()', - \ 'endfunc', - \ '', - \ 'profile func Foo1', - \ 'profile func Foo2', - \ 'call Foo1()', - \ 'call Foo2()', - \ '', - \ 'profile func Foo3', - \ 'profdel func Foo2', - \ 'profdel func Foo3', - \ 'call Foo1()', - \ 'call Foo2()', - \ 'call Foo3()' ] + let lines =<< trim [CODE] + profile start Xprofile_file.log + func! Foo1() + endfunc + func! Foo2() + endfunc + func! Foo3() + endfunc + + profile func Foo1 + profile func Foo2 + call Foo1() + call Foo2() + + profile func Foo3 + profdel func Foo2 + profdel func Foo3 + call Foo1() + call Foo2() + call Foo3() + [CODE] call writefile(lines, 'Xprofile_file.vim') call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') call assert_equal(0, v:shell_error) @@ -496,14 +497,15 @@ endfunc func Test_profdel_star() " Foo() is invoked once before and once after 'profdel *'. " So profiling should report it only once. - let lines = [ - \ 'profile start Xprofile_file.log', - \ 'func! Foo()', - \ 'endfunc', - \ 'profile func Foo', - \ 'call Foo()', - \ 'profdel *', - \ 'call Foo()' ] + let lines =<< trim [CODE] + profile start Xprofile_file.log + func! Foo() + endfunc + profile func Foo + call Foo() + profdel * + call Foo() + [CODE] call writefile(lines, 'Xprofile_file.vim') call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') call assert_equal(0, v:shell_error) diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 597be0aa89..b9a22aff51 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -775,68 +775,68 @@ func Test_efm1() return endif - let l = [ - \ '"Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set.', - \ '"Xtestfile", line 6 col 19; this is an error', - \ 'gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c', - \ 'Xtestfile:9: parse error before `asd''', - \ 'make: *** [vim] Error 1', - \ 'in file "Xtestfile" linenr 10: there is an error', - \ '', - \ '2 returned', - \ '"Xtestfile", line 11 col 1; this is an error', - \ '"Xtestfile", line 12 col 2; this is another error', - \ '"Xtestfile", line 14:10; this is an error in column 10', - \ '=Xtestfile=, line 15:10; this is another error, but in vcol 10 this time', - \ '"Xtestfile", linenr 16: yet another problem', - \ 'Error in "Xtestfile" at line 17:', - \ 'x should be a dot', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17', - \ ' ^', - \ 'Error in "Xtestfile" at line 18:', - \ 'x should be a dot', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18', - \ '.............^', - \ 'Error in "Xtestfile" at line 19:', - \ 'x should be a dot', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19', - \ '--------------^', - \ 'Error in "Xtestfile" at line 20:', - \ 'x should be a dot', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20', - \ ' ^', - \ '', - \ 'Does anyone know what is the problem and how to correction it?', - \ '"Xtestfile", line 21 col 9: What is the title of the quickfix window?', - \ '"Xtestfile", line 22 col 9: What is the title of the quickfix window?' - \ ] + let l =<< trim [DATA] + "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set. + "Xtestfile", line 6 col 19; this is an error + gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c + Xtestfile:9: parse error before `asd' + make: *** [vim] Error 1 + in file "Xtestfile" linenr 10: there is an error + + 2 returned + "Xtestfile", line 11 col 1; this is an error + "Xtestfile", line 12 col 2; this is another error + "Xtestfile", line 14:10; this is an error in column 10 + =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time + "Xtestfile", linenr 16: yet another problem + Error in "Xtestfile" at line 17: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 + ^ + Error in "Xtestfile" at line 18: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 + .............^ + Error in "Xtestfile" at line 19: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 + --------------^ + Error in "Xtestfile" at line 20: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 + ^ + + Does anyone know what is the problem and how to correction it? + "Xtestfile", line 21 col 9: What is the title of the quickfix window? + "Xtestfile", line 22 col 9: What is the title of the quickfix window? + [DATA] call writefile(l, 'Xerrorfile1') call writefile(l[:-2], 'Xerrorfile2') - let m = [ - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21', - \ ' xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22' - \ ] + let m =<< trim [DATA] + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22 + [DATA] call writefile(m, 'Xtestfile') let save_efm = &efm @@ -895,20 +895,21 @@ func s:dir_stack_tests(cchar) let save_efm=&efm set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' - let lines = ["Entering dir 'dir1/a'", - \ 'habits2.txt:1:Nine Healthy Habits', - \ "Entering dir 'b'", - \ 'habits3.txt:2:0 Hours of television', - \ 'habits2.txt:7:5 Small meals', - \ "Entering dir 'dir1/c'", - \ 'habits4.txt:3:1 Hour of exercise', - \ "Leaving dir 'dir1/c'", - \ "Leaving dir 'dir1/a'", - \ 'habits1.txt:4:2 Liters of water', - \ "Entering dir 'dir2'", - \ 'habits5.txt:5:3 Cups of hot green tea', - \ "Leaving dir 'dir2'" - \] + let lines =<< trim [DATA] + Entering dir 'dir1/a' + habits2.txt:1:Nine Healthy Habits + Entering dir 'b' + habits3.txt:2:0 Hours of television + habits2.txt:7:5 Small meals + Entering dir 'dir1/c' + habits4.txt:3:1 Hour of exercise + Leaving dir 'dir1/c' + Leaving dir 'dir1/a' + habits1.txt:4:2 Liters of water + Entering dir 'dir2' + habits5.txt:5:3 Cups of hot green tea + Leaving dir 'dir2 + [DATA] Xexpr "" for l in lines @@ -942,18 +943,20 @@ func Test_efm_dirstack() call mkdir('dir1/c') call mkdir('dir2') - let lines = ["Nine Healthy Habits", - \ "0 Hours of television", - \ "1 Hour of exercise", - \ "2 Liters of water", - \ "3 Cups of hot green tea", - \ "4 Short mental breaks", - \ "5 Small meals", - \ "6 AM wake up time", - \ "7 Minutes of laughter", - \ "8 Hours of sleep (at least)", - \ "9 PM end of the day and off to bed" - \ ] + let lines =<< trim [DATA] + Nine Healthy Habits, + 0 Hours of television, + 1 Hour of exercise, + 2 Liters of water, + 3 Cups of hot green tea, + 4 Short mental breaks, + 5 Small meals, + 6 AM wake up time, + 7 Minutes of laughter, + 8 Hours of sleep (at least), + 9 PM end of the day and off to bed + [DATA] + call writefile(lines, 'habits1.txt') call writefile(lines, 'dir1/a/habits2.txt') call writefile(lines, 'dir1/a/b/habits3.txt') @@ -1049,21 +1052,22 @@ func Test_efm2() call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) " Test for %P, %Q and %t format specifiers - let lines=["[Xtestfile1]", - \ "(1,17) error: ';' missing", - \ "(21,2) warning: variable 'z' not defined", - \ "(67,3) error: end of file found before string ended", - \ "--", - \ "", - \ "[Xtestfile2]", - \ "--", - \ "", - \ "[Xtestfile3]", - \ "NEW compiler v1.1", - \ "(2,2) warning: variable 'x' not defined", - \ "(67,3) warning: 's' already defined", - \ "--" - \] + let lines =<< trim [DATA] + [Xtestfile1] + (1,17) error: ';' missing + (21,2) warning: variable 'z' not defined + (67,3) error: end of file found before string ended + -- + + [Xtestfile2] + -- + + [Xtestfile3] + NEW compiler v1.1 + (2,2) warning: variable 'x' not defined + (67,3) warning: 's' already defined + - + [DATA] set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r " To exercise the push/pop file functionality in quickfix, the test files " need to be created. @@ -1085,11 +1089,13 @@ func Test_efm2() call delete('Xtestfile3') " Tests for %E, %C and %Z format specifiers - let lines = ["Error 275", - \ "line 42", - \ "column 3", - \ "' ' expected after '--'" - \] + let lines =<< trim [DATA] + Error 275 + line 42 + column 3 + ' ' expected after '--' + [DATA] + set efm=%EError\ %n,%Cline\ %l,%Ccolumn\ %c,%Z%m cgetexpr lines let l = getqflist() @@ -1100,9 +1106,11 @@ func Test_efm2() call assert_equal("\n' ' expected after '--'", l[0].text) " Test for %> - let lines = ["Error in line 147 of foo.c:", - \"unknown variable 'i'" - \] + let lines =<< trim [DATA] + Error in line 147 of foo.c: + unknown variable 'i' + [DATA] + set efm=unknown\ variable\ %m,%E%>Error\ in\ line\ %l\ of\ %f:,%Z%m cgetexpr lines let l = getqflist() @@ -1111,21 +1119,21 @@ func Test_efm2() call assert_equal("\nunknown variable 'i'", l[0].text) " Test for %A, %C and other formats - let lines = [ - \"==============================================================", - \"FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)", - \"--------------------------------------------------------------", - \"Traceback (most recent call last):", - \' File "unittests/dbfacadeTest.py", line 89, in testFoo', - \" self.assertEquals(34, dtid)", - \' File "/usr/lib/python2.2/unittest.py", line 286, in', - \" failUnlessEqual", - \" raise self.failureException, \\", - \"AssertionError: 34 != 33", - \"", - \"--------------------------------------------------------------", - \"Ran 27 tests in 0.063s" - \] + let lines =<< trim [DATA] + ============================================================== + FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest) + -------------------------------------------------------------- + Traceback (most recent call last): + File "unittests/dbfacadeTest.py", line 89, in testFoo + self.assertEquals(34, dtid) + File "/usr/lib/python2.2/unittest.py", line 286, in + failUnlessEqual + raise self.failureException, \\ + AssertionError: 34 != 33 + + -------------------------------------------------------------- + Ran 27 tests in 0.063s + [DATA] set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m cgetexpr lines let l = getqflist() diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 1e70f28a00..a38625cca8 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -20,25 +20,27 @@ func Test_after_comes_later() if !has('packages') return endif - let before = [ - \ 'set nocp viminfo+=nviminfo', - \ 'set guioptions+=M', - \ 'let $HOME = "/does/not/exist"', - \ 'set loadplugins', - \ 'set rtp=Xhere,Xafter,Xanother', - \ 'set packpath=Xhere,Xafter', - \ 'set nomore', - \ 'let g:sequence = ""', - \ ] - let after = [ - \ 'redir! > Xtestout', - \ 'scriptnames', - \ 'redir END', - \ 'redir! > Xsequence', - \ 'echo g:sequence', - \ 'redir END', - \ 'quit', - \ ] + let before =<< trim [CODE] + set nocp viminfo+=nviminfo + set guioptions+=M + let $HOME = "/does/not/exist" + set loadplugins + set rtp=Xhere,Xafter,Xanother + set packpath=Xhere,Xafter + set nomore + let g:sequence = "" + [CODE] + + let after =<< trim [CODE] + redir! > Xtestout + scriptnames + redir END + redir! > Xsequence + echo g:sequence + redir END + quit + [CODE] + call mkdir('Xhere/plugin', 'p') call writefile(['let g:sequence .= "here "'], 'Xhere/plugin/here.vim') call mkdir('Xanother/plugin', 'p') @@ -77,15 +79,16 @@ func Test_pack_in_rtp_when_plugins_run() if !has('packages') return endif - let before = [ - \ 'set nocp viminfo+=nviminfo', - \ 'set guioptions+=M', - \ 'let $HOME = "/does/not/exist"', - \ 'set loadplugins', - \ 'set rtp=Xhere', - \ 'set packpath=Xhere', - \ 'set nomore', - \ ] + let before =<< trim [CODE] + set nocp viminfo+=nviminfo + set guioptions+=M + let $HOME = "/does/not/exist" + set loadplugins + set rtp=Xhere + set packpath=Xhere + set nomore + [CODE] + let after = [ \ 'quit', \ ] @@ -133,11 +136,12 @@ endfunc func Test_compatible_args() throw "skipped: Nvim is always 'nocompatible'" - let after = [ - \ 'call writefile([string(&compatible)], "Xtestout")', - \ 'set viminfo+=nviminfo', - \ 'quit', - \ ] + let after =<< trim [CODE] + call writefile([string(&compatible)], "Xtestout") + set viminfo+=nviminfo + quit + [CODE] + if RunVim([], after, '-C') let lines = readfile('Xtestout') call assert_equal('1', lines[0]) @@ -154,14 +158,15 @@ endfunc " Test the -o[N] and -O[N] arguments to open N windows split " horizontally or vertically. func Test_o_arg() - let after = [ - \ 'call writefile([winnr("$"), - \ winheight(1), winheight(2), &lines, - \ winwidth(1), winwidth(2), &columns, - \ bufname(winbufnr(1)), bufname(winbufnr(2))], - \ "Xtestout")', - \ 'qall', - \ ] + let after =<< trim [CODE] + call writefile([winnr("$"), + \ winheight(1), winheight(2), &lines, + \ winwidth(1), winwidth(2), &columns, + \ bufname(winbufnr(1)), bufname(winbufnr(2))], + \ "Xtestout") + qall + [CODE] + if RunVim([], after, '-o2') " Open 2 windows split horizontally. Expect: " - 2 windows @@ -230,10 +235,11 @@ endfunc " Test the -p[N] argument to open N tabpages. func Test_p_arg() - let after = [ - \ 'call writefile(split(execute("tabs"), "\n"), "Xtestout")', - \ 'qall', - \ ] + let after =<< trim [CODE] + call writefile(split(execute("tabs"), "\n"), "Xtestout") + qall + [CODE] + if RunVim([], after, '-p2') let lines = readfile('Xtestout') call assert_equal(4, len(lines)) @@ -290,10 +296,11 @@ endfunc " -M resets 'modifiable' and 'write' " -R sets 'readonly' func Test_m_M_R() - let after = [ - \ 'call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout")', - \ 'qall', - \ ] + let after =<< trim [CODE] + call writefile([&write, &modifiable, &readonly, &updatecount], "Xtestout") + qall + [CODE] + if RunVim([], after, '') let lines = readfile('Xtestout') call assert_equal(['1', '1', '0', '200'], lines) @@ -316,10 +323,11 @@ endfunc " Test the -A, -F and -H arguments (Arabic, Farsi and Hebrew modes). func Test_A_F_H_arg() - let after = [ - \ 'call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout")', - \ 'qall', - \ ] + let after =<< trim [CODE] + call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout") + qall + [CODE] + " Use silent Ex mode to avoid the hit-Enter prompt for the warning that " 'encoding' is not utf-8. if has('arabic') && &encoding == 'utf-8' && RunVim([], after, '-e -s -A') @@ -423,10 +431,11 @@ func Test_invalid_args() endfunc func Test_file_args() - let after = [ - \ 'call writefile(argv(), "Xtestout")', - \ 'qall', - \ ] + let after =<< trim [CODE] + call writefile(argv(), "Xtestout") + qall + [CODE] + if RunVim([], after, '') let lines = readfile('Xtestout') call assert_equal(0, len(lines)) @@ -487,10 +496,11 @@ func Test_startuptime() endfunc func Test_read_stdin() - let after = [ - \ 'write Xtestout', - \ 'quit!', - \ ] + let after =<< trim [CODE] + write Xtestout + quit! + [CODE] + if RunVimPiped([], after, '-', 'echo something | ') let lines = readfile('Xtestout') " MS-Windows adds a space after the word @@ -540,20 +550,22 @@ endfunc func Test_zzz_startinsert() " Test :startinsert call writefile(['123456'], 'Xtestout') - let after = [ - \ ':startinsert', - \ 'call feedkeys("foobar\:wq\","t")' - \ ] + let after =<< trim [CODE] + :startinsert + call feedkeys("foobar\:wq\","t") + [CODE] + if RunVim([], after, 'Xtestout') let lines = readfile('Xtestout') call assert_equal(['foobar123456'], lines) endif " Test :startinsert! call writefile(['123456'], 'Xtestout') - let after = [ - \ ':startinsert!', - \ 'call feedkeys("foobar\:wq\","t")' - \ ] + let after =<< trim [CODE] + :startinsert! + call feedkeys("foobar\:wq\","t") + [CODE] + if RunVim([], after, 'Xtestout') let lines = readfile('Xtestout') call assert_equal(['123456foobar'], lines) -- cgit From 0586a4b512b2495d32f20c46946d35a0d403bd52 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Mon, 23 Sep 2019 21:19:26 +0200 Subject: vim-patch:8.1.1588: in :let-heredoc line continuation is recognized Problem: In :let-heredoc line continuation is recognized. Solution: Do not consume line continuation. (Ozaki Kiichi, closes vim/vim#4580) https://github.com/vim/vim/commit/e96a2498f9a2d3e93ac07431f6d4afd77f30afdf --- src/nvim/digraph.c | 2 +- src/nvim/eval.c | 13 ++++++++----- src/nvim/ex_cmds.c | 4 ++-- src/nvim/ex_cmds2.c | 6 +++--- src/nvim/ex_cmds_defs.h | 2 +- src/nvim/ex_docmd.c | 52 ++++++++++++++++++++++++++----------------------- src/nvim/ex_getln.c | 23 ++++++++++++---------- src/nvim/fileio.c | 10 ++++------ src/nvim/getchar.c | 2 +- src/nvim/normal.c | 2 +- src/nvim/ops.c | 10 ++++++---- 11 files changed, 68 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 5a07137831..65d95ff158 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1887,7 +1887,7 @@ void ex_loadkeymap(exarg_T *eap) // Get each line of the sourced file, break at the end. for (;;) { - line = eap->getline(0, eap->cookie, 0); + line = eap->getline(0, eap->cookie, 0, true); if (line == NULL) { break; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6d706939a1..c18d687cc1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1561,7 +1561,7 @@ heredoc_get(exarg_T *eap, char_u *cmd) for (;;) { int i = 0; - char_u *theline = eap->getline(NUL, eap->cookie, 0); + char_u *theline = eap->getline(NUL, eap->cookie, 0, false); if (theline != NULL && indent_len > 0) { // trim the indent matching the first line if (STRNCMP(theline, *eap->cmdlinep, indent_len) == 0) { @@ -8734,7 +8734,7 @@ typedef struct { const listitem_T *li; } GetListLineCookie; -static char_u *get_list_line(int c, void *cookie, int indent) +static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) { GetListLineCookie *const p = (GetListLineCookie *)cookie; @@ -21279,6 +21279,7 @@ void ex_function(exarg_T *eap) hashitem_T *hi; int sourcing_lnum_off; bool show_block = false; + bool do_concat = true; /* * ":function" without argument: list functions. @@ -21548,9 +21549,9 @@ void ex_function(exarg_T *eap) } else { xfree(line_to_free); if (eap->getline == NULL) { - theline = getcmdline(':', 0L, indent); + theline = getcmdline(':', 0L, indent, do_concat); } else { - theline = eap->getline(':', eap->cookie, indent); + theline = eap->getline(':', eap->cookie, indent, do_concat); } line_to_free = theline; } @@ -21580,6 +21581,7 @@ void ex_function(exarg_T *eap) if (STRCMP(p, skip_until) == 0) { XFREE_CLEAR(skip_until); XFREE_CLEAR(trimmed); + do_concat = true; } } } else { @@ -21699,6 +21701,7 @@ void ex_function(exarg_T *eap) } else { skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); } + do_concat = false; } } @@ -23522,7 +23525,7 @@ char_u *get_return_cmd(void *rettv) * Called by do_cmdline() to get the next line. * Returns allocated string, or NULL for end of function. */ -char_u *get_func_line(int c, void *cookie, int indent) +char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; ufunc_T *fp = fcp->func; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index b2d7ded6be..00de7a3d66 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2767,7 +2767,7 @@ void ex_append(exarg_T *eap) State = CMDLINE; theline = eap->getline( eap->cstack->cs_looplevel > 0 ? -1 : - NUL, eap->cookie, indent); + NUL, eap->cookie, indent, true); State = save_State; } lines_left = Rows - 1; @@ -3630,7 +3630,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, for (; i <= (long)ec; ++i) msg_putchar('^'); - resp = getexmodeline('?', NULL, 0); + resp = getexmodeline('?', NULL, 0, true); if (resp != NULL) { typed = *resp; xfree(resp); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 87eae2dd4f..272c81e29b 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3218,7 +3218,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc) cookie.conv.vc_type = CONV_NONE; // no conversion // Read the first line so we can check for a UTF-8 BOM. - firstline = getsourceline(0, (void *)&cookie, 0); + firstline = getsourceline(0, (void *)&cookie, 0, true); if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef && firstline[1] == 0xbb && firstline[2] == 0xbf) { // Found BOM; setup conversion, skip over BOM and recode the line. @@ -3381,7 +3381,7 @@ void free_scriptnames(void) /// /// @return pointer to the line in allocated memory, or NULL for end-of-file or /// some error. -char_u *getsourceline(int c, void *cookie, int indent) +char_u *getsourceline(int c, void *cookie, int indent, bool do_concat) { struct source_cookie *sp = (struct source_cookie *)cookie; char_u *line; @@ -3412,7 +3412,7 @@ char_u *getsourceline(int c, void *cookie, int indent) // Only concatenate lines starting with a \ when 'cpoptions' doesn't // contain the 'C' flag. - if (line != NULL && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { + if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { // compensate for the one line read-ahead sourcing_lnum--; diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 42ba1060e9..3a9fd01dd9 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -88,7 +88,7 @@ typedef struct exarg exarg_T; typedef void (*ex_func_T)(exarg_T *eap); -typedef char_u *(*LineGetter)(int, void *, int); +typedef char_u *(*LineGetter)(int, void *, int, bool); /// Structure for command definition. typedef struct cmdname { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 8d67ab5348..0da2cd67d6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -117,11 +117,11 @@ typedef struct { * reads more lines that may come from the while/for loop. */ struct loop_cookie { - garray_T *lines_gap; /* growarray with line info */ - int current_line; /* last read line from growarray */ - int repeating; /* TRUE when looping a second time */ - /* When "repeating" is FALSE use "getline" and "cookie" to get lines */ - char_u *(*getline)(int, void *, int); + garray_T *lines_gap; // growarray with line info + int current_line; // last read line from growarray + int repeating; // TRUE when looping a second time + // When "repeating" is FALSE use "getline" and "cookie" to get lines + char_u *(*getline)(int, void *, int, bool); void *cookie; }; @@ -313,8 +313,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, struct msglist **saved_msg_list = NULL; struct msglist *private_msg_list; - /* "fgetline" and "cookie" passed to do_one_cmd() */ - char_u *(*cmd_getline)(int, void *, int); + // "fgetline" and "cookie" passed to do_one_cmd() + char_u *(*cmd_getline)(int, void *, int, bool); void *cmd_cookie; struct loop_cookie cmd_loop_cookie; void *real_cookie; @@ -507,17 +507,20 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * Need to set msg_didout for the first line after an ":if", * otherwise the ":if" will be overwritten. */ - if (count == 1 && getline_equal(fgetline, cookie, getexline)) - msg_didout = TRUE; - if (fgetline == NULL || (next_cmdline = fgetline(':', cookie, - cstack.cs_idx < - 0 ? 0 : (cstack.cs_idx + 1) * 2 - )) == NULL) { - /* Don't call wait_return for aborted command line. The NULL - * returned for the end of a sourced file or executed function - * doesn't do this. */ - if (KeyTyped && !(flags & DOCMD_REPEAT)) - need_wait_return = FALSE; + if (count == 1 && getline_equal(fgetline, cookie, getexline)) { + msg_didout = true; + } + if (fgetline == NULL + || (next_cmdline = fgetline(':', cookie, + cstack.cs_idx < + 0 ? 0 : (cstack.cs_idx + 1) * 2, + true)) == NULL) { + // Don't call wait_return for aborted command line. The NULL + // returned for the end of a sourced file or executed function + // doesn't do this. + if (KeyTyped && !(flags & DOCMD_REPEAT)) { + need_wait_return = false; + } retval = FAIL; break; } @@ -951,7 +954,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, /* * Obtain a line when inside a ":while" or ":for" loop. */ -static char_u *get_loop_line(int c, void *cookie, int indent) +static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat) { struct loop_cookie *cp = (struct loop_cookie *)cookie; wcmd_T *wp; @@ -961,11 +964,12 @@ static char_u *get_loop_line(int c, void *cookie, int indent) if (cp->repeating) return NULL; /* trying to read past ":endwhile"/":endfor" */ - /* First time inside the ":while"/":for": get line normally. */ - if (cp->getline == NULL) - line = getcmdline(c, 0L, indent); - else - line = cp->getline(c, cp->cookie, indent); + // First time inside the ":while"/":for": get line normally. + if (cp->getline == NULL) { + line = getcmdline(c, 0L, indent, do_concat); + } else { + line = cp->getline(c, cp->cookie, indent, do_concat); + } if (line != NULL) { store_loop_line(cp->lines_gap, line); ++cp->current_line; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index d0af8a0fdf..5235b9e648 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1991,7 +1991,8 @@ char_u * getcmdline ( int firstc, long count, // only used for incremental search - int indent // indent for inside conditionals + int indent, // indent for inside conditionals + bool do_concat // unused ) { // Be prepared for situations where cmdline can be invoked recursively. @@ -2167,17 +2168,18 @@ static void correct_screencol(int idx, int cells, int *col) * Get an Ex command line for the ":" command. */ char_u * -getexline ( - int c, /* normally ':', NUL for ":append" */ +getexline( + int c, // normally ':', NUL for ":append" void *cookie, - int indent /* indent for inside conditionals */ + int indent, // indent for inside conditionals + bool do_concat ) { /* When executing a register, remove ':' that's in front of each line. */ if (exec_from_reg && vpeekc() == ':') (void)vgetc(); - return getcmdline(c, 1L, indent); + return getcmdline(c, 1L, indent, do_concat); } /* @@ -2187,11 +2189,12 @@ getexline ( * Returns a string in allocated memory or NULL. */ char_u * -getexmodeline ( - int promptc, /* normally ':', NUL for ":append" and '?' for - :s prompt */ +getexmodeline( + int promptc, // normally ':', NUL for ":append" and '?' + // for :s prompt void *cookie, - int indent /* indent for inside conditionals */ + int indent, // indent for inside conditionals + bool do_concat ) { garray_T line_ga; @@ -6308,7 +6311,7 @@ char *script_get(exarg_T *const eap, size_t *const lenp) for (;;) { char *const theline = (char *)eap->getline( eap->cstack->cs_looplevel > 0 ? -1 : - NUL, eap->cookie, 0); + NUL, eap->cookie, 0, true); if (theline == NULL || strcmp(end_pattern, theline) == 0) { xfree(theline); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 0394639a16..58e6b2ae92 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -7100,12 +7100,10 @@ auto_next_pat( } } -/* - * Get next autocommand command. - * Called by do_cmdline() to get the next line for ":if". - * Returns allocated string, or NULL for end of autocommands. - */ -char_u *getnextac(int c, void *cookie, int indent) +/// Get next autocommand command. +/// Called by do_cmdline() to get the next line for ":if". +/// @return allocated string, or NULL for end of autocommands. +char_u *getnextac(int c, void *cookie, int indent, bool do_concat) { AutoPatCmd *acp = (AutoPatCmd *)cookie; char_u *retval; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 1f82df3241..399f0671b4 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -4419,7 +4419,7 @@ mapblock_T *get_maphash(int index, buf_T *buf) } /// Get command argument for key -char_u * getcmdkeycmd(int promptc, void *cookie, int indent) +char_u * getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) { garray_T line_ga; int c1 = -1, c2; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e32b738c7e..e0dc9d4f23 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -5343,7 +5343,7 @@ static void nv_search(cmdarg_T *cap) // When using 'incsearch' the cursor may be moved to set a different search // start position. - cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0); + cap->searchbuf = getcmdline(cap->cmdchar, cap->count1, 0, true); if (cap->searchbuf == NULL) { clearop(oap); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 6f515151d6..0d27365d2b 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -671,13 +671,15 @@ int get_expr_register(void) { char_u *new_line; - new_line = getcmdline('=', 0L, 0); - if (new_line == NULL) + new_line = getcmdline('=', 0L, 0, true); + if (new_line == NULL) { return NUL; - if (*new_line == NUL) /* use previous line */ + } + if (*new_line == NUL) { // use previous line xfree(new_line); - else + } else { set_expr_line(new_line); + } return '='; } -- cgit From b772b86d2ba256a2c03ab701d00b322cf52560e0 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 10 Oct 2019 22:08:27 +0200 Subject: Remove "highbright bold" conversion. Fixes #11190 When using TUI host terminal should take care of this (regardless if 'termguicolors' is active or not). For GUI the behavior doesn't make sense (GUI should display bold attr as bold always). --- src/nvim/terminal.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 8fcc8bf0a5..7609006906 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -220,8 +220,6 @@ Terminal *terminal_open(TerminalOptions opts) rv->sb_size = (size_t)curbuf->b_p_scbk; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); - vterm_state_set_bold_highbright(state, true); - // Configure the color palette. Try to get the color from: // // - b:terminal_color_{NUM} -- cgit From 5f60861f5a7c7c588e1d638f734897bc5dc291cc Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 26 Sep 2019 23:04:59 +0100 Subject: fnamemodify: fix handling of :r after :e #11165 - Test fnamemodify() - Test handling of `expand("%:e:e:r")`. - Fix :e:e:r on filenames with insufficiently many extensions During `fnamemodify()`, ensuring that we don't go before the filename's tail is insufficient in cases where we've already handled a ":e" modifier, for example: ``` "path/to/this.file.ext" :e:e:r:r ^ ^-------- *fnamep +------------- tail ``` This means for a ":r", we'll go before `*fnamep`, and outside the bounds of the filename. This is both incorrect and causes neovim to exit with an allocation error. We exit because we attempt to calculate `s - *fnamep` (line 23948). Since `s` is before `*fnamep`, we caluclate a negative length, which ends up being interpreted as an amount to allocate, causing neovim to exit with ENOMEM (`memory.c:xmalloc`). We must instead ensure we don't go before `*fnamep` nor `tail`. The check for `tail` is still relevant, for example: ``` "path/to/this.file.ext" :r:r:r ^ ^------------- tail +--------------------- *fnamep ``` Here we don't want to go before `tail`. close #11165 --- src/nvim/eval.c | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7d641f1295..a899dbcd3a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -24051,22 +24051,47 @@ repeat: * - for second :e: before the current fname * - otherwise: The last '.' */ - if (src[*usedlen + 1] == 'e' && *fnamep > tail) + const bool is_second_e = *fnamep > tail; + if (src[*usedlen + 1] == 'e' && is_second_e) { s = *fnamep - 2; - else + } else { s = *fnamep + *fnamelen - 1; - for (; s > tail; --s) - if (s[0] == '.') + } + + for (; s > tail; s--) { + if (s[0] == '.') { break; - if (src[*usedlen + 1] == 'e') { /* :e */ - if (s > tail) { - *fnamelen += (size_t)(*fnamep - (s + 1)); - *fnamep = s + 1; - } else if (*fnamep <= tail) + } + } + if (src[*usedlen + 1] == 'e') { + if (s > tail || (0 && is_second_e && s == tail)) { + // we stopped at a '.' (so anchor to &'.' + 1) + char_u *newstart = s + 1; + size_t distance_stepped_back = *fnamep - newstart; + *fnamelen += distance_stepped_back; + *fnamep = newstart; + } else if (*fnamep <= tail) { *fnamelen = 0; - } else { /* :r */ - if (s > tail) /* remove one extension */ + } + } else { + // :r - Remove one extension + // + // Ensure that `s` doesn't go before `*fnamep`, + // since then we're taking too many roots: + // + // "path/to/this.file.ext" :e:e:r:r + // ^ ^-------- *fnamep + // +------------- tail + // + // Also ensure `s` doesn't go before `tail`, + // since then we're taking too many roots again: + // + // "path/to/this.file.ext" :r:r:r + // ^ ^------------- tail + // +--------------------- *fnamep + if (s > MAX(tail, *fnamep)) { *fnamelen = (size_t)(s - *fnamep); + } } *usedlen += 2; } -- cgit From 401398bc4b07301e8f7ad6c288dbc799624b1a2d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 10 Oct 2019 22:13:23 -0700 Subject: vim-patch:8.1.2125: fnamemodify() fails when repeating :e Problem: Fnamemodify() fails when repeating :e. Solution: Do not go before the tail. (Rob Pilling, closes vim/vim#5024) https://github.com/vim/vim/commit/b189295b72030f00c45c30d3daecf85d457221f8 --- src/nvim/testdir/test_fnamemodify.vim | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim index 63f273677d..116d23ba88 100644 --- a/src/nvim/testdir/test_fnamemodify.vim +++ b/src/nvim/testdir/test_fnamemodify.vim @@ -45,3 +45,31 @@ func Test_fnamemodify() let $HOME = save_home let &shell = save_shell endfunc + +func Test_fnamemodify_er() + call assert_equal("with", fnamemodify("path/to/file.with.extensions", ':e:e:r:r')) + + call assert_equal('c', fnamemodify('a.c', ':e')) + call assert_equal('c', fnamemodify('a.c', ':e:e')) + call assert_equal('c', fnamemodify('a.c', ':e:e:r')) + call assert_equal('c', fnamemodify('a.c', ':e:e:r:r')) + + call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r')) + call assert_equal('rb', fnamemodify('a.spec.rb', ':e:r')) + call assert_equal('spec.rb', fnamemodify('a.spec.rb', ':e:e')) + call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r')) + call assert_equal('spec', fnamemodify('a.spec.rb', ':e:e:r:r')) + call assert_equal('spec', fnamemodify('a.b.spec.rb', ':e:e:r')) + call assert_equal('b.spec', fnamemodify('a.b.spec.rb', ':e:e:e:r')) + call assert_equal('b', fnamemodify('a.b.spec.rb', ':e:e:e:r:r')) + + call assert_equal('spec', fnamemodify('a.b.spec.rb', ':r:e')) + call assert_equal('b', fnamemodify('a.b.spec.rb', ':r:r:e')) + + call assert_equal('c', fnamemodify('a.b.c.d.e', ':r:r:e')) + call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e')) + + " :e never includes the whole filename, so "a.b":e:e:e --> "b" + call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e')) + call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e')) +endfunc -- cgit From 9af0fe529d2d91640e4d3388ab9f28159553f14c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 11 Oct 2019 19:17:11 +0200 Subject: recovery mode (-r/-L): use headless_mode (#11187) Fixes https://github.com/neovim/neovim/issues/11181. --- src/nvim/main.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index ba15dcedad..e0a1e60fc0 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -957,6 +957,7 @@ static void command_line_scan(mparm_T *parmp) case 'r': // "-r" recovery mode case 'L': { // "-L" recovery mode recoverymode = 1; + headless_mode = true; break; } case 's': { -- cgit From dd49a130ff0cd7a51cec3a7bae1ecda3708f8eb2 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 11 Oct 2019 18:32:15 +0100 Subject: vim-patch:8.1.1099: the do_tag() function is too long Problem: The do_tag() function is too long. Solution: Factor parts out to separate functions. Move simplify_filename() to a file where it fits better. (Andy Massimino, closes vim/vim#4195) https://github.com/vim/vim/commit/b4a6020ac6a0638167013f1e45ff440ddc8a1671 --- src/nvim/tag.c | 800 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 427 insertions(+), 373 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 6fe3efbaae..880c467d30 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -131,13 +131,13 @@ static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; * * for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise */ -int -do_tag ( - char_u *tag, /* tag (pattern) to jump to */ +int +do_tag( + char_u *tag, // tag (pattern) to jump to int type, int count, - int forceit, /* :ta with ! */ - int verbose /* print "tag not found" message */ + int forceit, // :ta with ! + int verbose // print "tag not found" message ) { taggy_T *tagstack = curwin->w_tagstack; @@ -148,28 +148,19 @@ do_tag ( int oldtagstackidx = tagstackidx; int prevtagstackidx = tagstackidx; int prev_num_matches; - int new_tag = FALSE; - int other_name; - int i, j, k; - int idx; + int new_tag = false; + int i; int ic; - char_u *p; - char_u *name; - int no_regexp = FALSE; + int no_regexp = false; int error_cur_match = 0; - char_u *command_end; - int save_pos = FALSE; + int save_pos = false; fmark_T saved_fmark; - int taglen; - int jumped_to_tag = FALSE; - tagptrs_T tagp, tagp2; + int jumped_to_tag = false; int new_num_matches; char_u **new_matches; - int attr; int use_tagstack; - int skip_msg = FALSE; - char_u *buf_ffname = curbuf->b_ffname; /* name to use for - priority computation */ + int skip_msg = false; + char_u *buf_ffname = curbuf->b_ffname; // name for priority computation /* remember the matches for the last used tag */ static int num_matches = 0; @@ -183,13 +174,13 @@ do_tag ( FreeWild(num_matches, matches); cs_free_tags(); num_matches = 0; - return FALSE; + return false; } #endif if (type == DT_HELP) { type = DT_TAG; - no_regexp = TRUE; + no_regexp = true; } prev_num_matches = num_matches; @@ -209,10 +200,11 @@ do_tag ( ptag_entry.tagname = vim_strsave(tag); } } else { - if (g_do_tagpreview != 0) - use_tagstack = FALSE; - else - use_tagstack = TRUE; + if (g_do_tagpreview != 0) { + use_tagstack = false; + } else { + use_tagstack = true; + } /* new pattern, add to the tag stack */ if (*tag != NUL @@ -253,15 +245,15 @@ do_tag ( curwin->w_tagstacklen = tagstacklen; - save_pos = TRUE; /* save the cursor position below */ + save_pos = true; // save the cursor position below } - new_tag = TRUE; + new_tag = true; } else { if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : - tagstacklen == 0) { - /* empty stack */ + g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + tagstacklen == 0) { + // empty stack EMSG(_(e_tagstack)); goto end_do_tag; } @@ -271,7 +263,7 @@ do_tag ( if ((tagstackidx -= count) < 0) { EMSG(_(bottommsg)); if (tagstackidx + count == 0) { - /* We did [num]^T from the bottom of the stack */ + // We did [num]^T from the bottom of the stack tagstackidx = 0; goto end_do_tag; } @@ -279,7 +271,7 @@ do_tag ( * way to the bottom now. */ tagstackidx = 0; - } else if (tagstackidx >= tagstacklen) { /* count == 0? */ + } else if (tagstackidx >= tagstacklen) { // count == 0? EMSG(_(topmsg)); goto end_do_tag; } @@ -293,8 +285,8 @@ do_tag ( * file was changed) keep original position in tag stack. */ if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum, - GETF_SETMARK, forceit) == FAIL) { - tagstackidx = oldtagstackidx; /* back to old posn */ + GETF_SETMARK, forceit) == FAIL) { + tagstackidx = oldtagstackidx; // back to old posn goto end_do_tag; } /* A BufReadPost autocommand may jump to the '" mark, but @@ -305,12 +297,12 @@ do_tag ( curwin->w_cursor.lnum = saved_fmark.mark.lnum; } curwin->w_cursor.col = saved_fmark.mark.col; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; check_cursor(); if ((fdo_flags & FDO_TAG) && old_KeyTyped) foldOpenCursor(); - /* remove the old list of matches */ + // remove the old list of matches FreeWild(num_matches, matches); cs_free_tags(); num_matches = 0; @@ -325,8 +317,8 @@ do_tag ( cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { - /* ":tag" (no argument): go to newer pattern */ - save_pos = TRUE; /* save the cursor position below */ + // ":tag" (no argument): go to newer pattern + save_pos = true; // save the cursor position below if ((tagstackidx += count - 1) >= tagstacklen) { /* * Beyond the last one, just give an error message and @@ -335,8 +327,8 @@ do_tag ( */ tagstackidx = tagstacklen - 1; EMSG(_(topmsg)); - save_pos = FALSE; - } else if (tagstackidx < 0) { /* must have been count == 0 */ + save_pos = false; + } else if (tagstackidx < 0) { // must have been count == 0 EMSG(_(bottommsg)); tagstackidx = 0; goto end_do_tag; @@ -344,9 +336,9 @@ do_tag ( cur_match = tagstack[tagstackidx].cur_match; cur_fnum = tagstack[tagstackidx].cur_fnum; } - new_tag = TRUE; - } else { /* go to other matching tag */ - /* Save index for when selection is cancelled. */ + new_tag = true; + } else { // go to other matching tag + // Save index for when selection is cancelled. prevtagstackidx = tagstackidx; if (g_do_tagpreview != 0) { @@ -371,7 +363,7 @@ do_tag ( cur_match = MAXCOL - 1; else if (cur_match < 0) { EMSG(_("E425: Cannot go before first matching tag")); - skip_msg = TRUE; + skip_msg = true; cur_match = 0; cur_fnum = curbuf->b_fnum; } @@ -418,15 +410,17 @@ do_tag ( * Repeat searching for tags, when a file has not been found. */ for (;; ) { - /* - * When desired match not found yet, try to find it (and others). - */ - if (use_tagstack) + int other_name; + char_u *name; + + // When desired match not found yet, try to find it (and others). + if (use_tagstack) { name = tagstack[tagstackidx].tagname; - else if (g_do_tagpreview != 0) + } else if (g_do_tagpreview != 0) { name = ptag_entry.tagname; - else + } else { name = tag; + } other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0); if (new_tag || (cur_match >= num_matches && max_num_matches != MAXCOL) @@ -446,7 +440,7 @@ do_tag ( max_num_matches = cur_match + 1; } - /* when the argument starts with '/', use it as a regexp */ + // when the argument starts with '/', use it as a regexp if (!no_regexp && *name == '/') { flags = TAG_REGEXP; ++name; @@ -467,17 +461,21 @@ do_tag ( * to the start. Avoids that the order changes when using * ":tnext" and jumping to another file. */ if (!new_tag && !other_name) { - /* Find the position of each old match in the new list. Need - * to use parse_match() to find the tag line. */ - idx = 0; - for (j = 0; j < num_matches; ++j) { + int j, k; + int idx = 0; + tagptrs_T tagp, tagp2; + + // Find the position of each old match in the new list. Need + // to use parse_match() to find the tag line. + for (j = 0; j < num_matches; j++) { parse_match(matches[j], &tagp); for (i = idx; i < new_num_matches; ++i) { parse_match(new_matches[i], &tagp2); if (STRCMP(tagp.tagname, tagp2.tagname) == 0) { - p = new_matches[i]; - for (k = i; k > idx; --k) + char_u *p = new_matches[i]; + for (k = i; k > idx; k--) { new_matches[k] = new_matches[k - 1]; + } new_matches[idx++] = p; break; } @@ -504,304 +502,27 @@ do_tag ( // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; } else if (type == DT_SELECT || (type == DT_JUMP && num_matches > 1)) { - // List all the matching tags. - // Assume that the first match indicates how long the tags can - // be, and align the file names to that. - parse_match(matches[0], &tagp); - taglen = (int)(tagp.tagname_end - tagp.tagname + 2); - if (taglen < 18) - taglen = 18; - if (taglen > Columns - 25) - taglen = MAXCOL; - if (msg_col == 0) - msg_didout = FALSE; /* overwrite previous message */ - msg_start(); - MSG_PUTS_ATTR(_(" # pri kind tag"), HL_ATTR(HLF_T)); - msg_clr_eos(); - taglen_advance(taglen); - MSG_PUTS_ATTR(_("file\n"), HL_ATTR(HLF_T)); - - for (i = 0; i < num_matches && !got_int; i++) { - parse_match(matches[i], &tagp); - if (!new_tag && ((g_do_tagpreview != 0 && i == ptag_entry.cur_match) - || (use_tagstack - && i == tagstack[tagstackidx].cur_match))) { - *IObuff = '>'; - } else { - *IObuff = ' '; - } - vim_snprintf((char *)IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, - mt_names[matches[i][0] & MT_MASK]); - msg_puts((const char *)IObuff); - if (tagp.tagkind != NULL) { - msg_outtrans_len(tagp.tagkind, - (int)(tagp.tagkind_end - tagp.tagkind)); - } - msg_advance(13); - msg_outtrans_len_attr(tagp.tagname, - (int)(tagp.tagname_end - tagp.tagname), - HL_ATTR(HLF_T)); - msg_putchar(' '); - taglen_advance(taglen); - - /* Find out the actual file name. If it is long, truncate - * it and put "..." in the middle */ - p = tag_full_fname(&tagp); - msg_puts_long_attr(p, HL_ATTR(HLF_D)); - xfree(p); - - if (msg_col > 0) - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - - /* print any extra fields */ - command_end = tagp.command_end; - if (command_end != NULL) { - p = command_end + 3; - while (*p && *p != '\r' && *p != '\n') { - while (*p == TAB) - ++p; - - /* skip "file:" without a value (static tag) */ - if (STRNCMP(p, "file:", 5) == 0 - && ascii_isspace(p[5])) { - p += 5; - continue; - } - /* skip "kind:" and "" */ - if (p == tagp.tagkind - || (p + 5 == tagp.tagkind - && STRNCMP(p, "kind:", 5) == 0)) { - p = tagp.tagkind_end; - continue; - } - // print all other extra fields - attr = HL_ATTR(HLF_CM); - while (*p && *p != '\r' && *p != '\n') { - if (msg_col + ptr2cells(p) >= Columns) { - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - } - p = msg_outtrans_one(p, attr); - if (*p == TAB) { - msg_puts_attr(" ", attr); - break; - } - if (*p == ':') - attr = 0; - } - } - if (msg_col > 15) { - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - } - } else { - for (p = tagp.command; - *p && *p != '\r' && *p != '\n'; ++p) - ; - command_end = p; - } - - /* - * Put the info (in several lines) at column 15. - * Don't display "/^" and "?^". - */ - p = tagp.command; - if (*p == '/' || *p == '?') { - ++p; - if (*p == '^') - ++p; - } - /* Remove leading whitespace from pattern */ - while (p != command_end && ascii_isspace(*p)) - ++p; - - while (p != command_end) { - if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) - msg_putchar('\n'); - if (got_int) - break; - msg_advance(15); - - // Skip backslash used for escaping a command char or a backslash. - if (*p == '\\' && (*(p + 1) == *tagp.command - || *(p + 1) == '\\')) { - ++p; - } - - if (*p == TAB) { - msg_putchar(' '); - ++p; - } else - p = msg_outtrans_one(p, 0); - - /* don't display the "$/;\"" and "$?;\"" */ - if (p == command_end - 2 && *p == '$' - && *(p + 1) == *tagp.command) - break; - /* don't display matching '/' or '?' */ - if (p == command_end - 1 && *p == *tagp.command - && (*p == '/' || *p == '?')) - break; - } - if (msg_col) - msg_putchar('\n'); - os_breakcheck(); - } - if (got_int) { - got_int = false; // only stop the listing - } + print_tag_list(new_tag, use_tagstack, num_matches, matches); ask_for_selection = true; } else if (type == DT_LTAG) { - list_T *list; - char_u tag_name[128 + 1]; - char_u *fname; - char_u *cmd; - - /* - * Add the matching tags to the location list for the current - * window. - */ - - fname = xmalloc(MAXPATHL + 1); - cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(num_matches); - - for (i = 0; i < num_matches; ++i) { - int len, cmd_len; - long lnum; - dict_T *dict; - - parse_match(matches[i], &tagp); - - /* Save the tag name */ - len = (int)(tagp.tagname_end - tagp.tagname); - if (len > 128) - len = 128; - STRLCPY(tag_name, tagp.tagname, len + 1); - - /* Save the tag file name */ - p = tag_full_fname(&tagp); - STRLCPY(fname, p, MAXPATHL + 1); - xfree(p); - - /* - * Get the line number or the search pattern used to locate - * the tag. - */ - lnum = 0; - if (isdigit(*tagp.command)) - /* Line number is used to locate the tag */ - lnum = atol((char *)tagp.command); - else { - char_u *cmd_start, *cmd_end; - - /* Search pattern is used to locate the tag */ - - /* Locate the end of the command */ - cmd_start = tagp.command; - cmd_end = tagp.command_end; - if (cmd_end == NULL) { - for (p = tagp.command; - *p && *p != '\r' && *p != '\n'; ++p) - ; - cmd_end = p; - } - - /* - * Now, cmd_end points to the character after the - * command. Adjust it to point to the last - * character of the command. - */ - cmd_end--; - - /* - * Skip the '/' and '?' characters at the - * beginning and end of the search pattern. - */ - if (*cmd_start == '/' || *cmd_start == '?') - cmd_start++; - - if (*cmd_end == '/' || *cmd_end == '?') - cmd_end--; - - len = 0; - cmd[0] = NUL; - - /* - * If "^" is present in the tag search pattern, then - * copy it first. - */ - if (*cmd_start == '^') { - STRCPY(cmd, "^"); - cmd_start++; - len++; - } - - /* - * Precede the tag pattern with \V to make it very - * nomagic. - */ - STRCAT(cmd, "\\V"); - len += 2; - - cmd_len = (int)(cmd_end - cmd_start + 1); - if (cmd_len > (CMDBUFFSIZE - 5)) - cmd_len = CMDBUFFSIZE - 5; - STRNCAT(cmd, cmd_start, cmd_len); - len += cmd_len; - - if (cmd[len - 1] == '$') { - /* - * Replace '$' at the end of the search pattern - * with '\$' - */ - cmd[len - 1] = '\\'; - cmd[len] = '$'; - len++; - } - - cmd[len] = NUL; - } - - dict = tv_dict_alloc(); - tv_list_append_dict(list, dict); - - tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); - tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); - tv_dict_add_nr(dict, S_LEN("lnum"), lnum); - if (lnum == 0) { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); - } + if (add_llist_tags(tag, num_matches, matches) == FAIL) { + goto end_do_tag; } - vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); - set_errorlist(curwin, list, ' ', IObuff, NULL); - - tv_list_free(list); - xfree(fname); - xfree(cmd); - - cur_match = 0; /* Jump to the first tag */ + cur_match = 0; // Jump to the first tag } if (ask_for_selection) { // Ask to select a tag from the list. i = prompt_for_number(NULL); if (i <= 0 || i > num_matches || got_int) { - /* no valid choice: don't change anything */ + // no valid choice: don't change anything if (use_tagstack) { tagstack[tagstackidx].fmark = saved_fmark; tagstackidx = prevtagstackidx; } cs_free_tags(); - jumped_to_tag = TRUE; + jumped_to_tag = true; break; } cur_match = i - 1; @@ -817,7 +538,7 @@ do_tag ( EMSG(_("E427: There is only one matching tag")); else EMSG(_("E428: Cannot go beyond last matching tag")); - skip_msg = TRUE; + skip_msg = true; } cur_match = num_matches - 1; } @@ -843,13 +564,14 @@ do_tag ( && type != DT_CSCOPE && (num_matches > 1 || ic) && !skip_msg) { - /* Give an indication of the number of matching tags */ - sprintf((char *)IObuff, _("tag %d of %d%s"), - cur_match + 1, - num_matches, - max_num_matches != MAXCOL ? _(" or more") : ""); - if (ic) + // Give an indication of the number of matching tags + snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"), + cur_match + 1, + num_matches, + max_num_matches != MAXCOL ? _(" or more") : ""); + if (ic) { STRCAT(IObuff, _(" Using tag with different case!")); + } if ((num_matches > prev_num_matches || new_tag) && num_matches > 1) { if (ic) { @@ -867,7 +589,7 @@ do_tag ( } } - /* Let the SwapExists event know what tag we are jumping to. */ + // Let the SwapExists event know what tag we are jumping to. vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name); set_vim_var_string(VV_SWAPCOMMAND, (char *) IObuff, -1); @@ -879,7 +601,7 @@ do_tag ( set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); if (i == NOTAGFILE) { - /* File not found: try again with another matching tag */ + // File not found: try again with another matching tag if ((type == DT_PREV && cur_match > 0) || ((type == DT_TAG || type == DT_NEXT || type == DT_FIRST) @@ -902,22 +624,354 @@ do_tag ( * tagstackidx is still valid. */ if (use_tagstack && tagstackidx > curwin->w_tagstacklen) tagstackidx = curwin->w_tagstackidx; - jumped_to_tag = TRUE; + jumped_to_tag = true; } } break; } end_do_tag: - /* Only store the new index when using the tagstack and it's valid. */ - if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) + // Only store the new index when using the tagstack and it's valid. + if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) { curwin->w_tagstackidx = tagstackidx; + } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time return jumped_to_tag; } +// +// List all the matching tags. +// +static void +print_tag_list( + int new_tag, + int use_tagstack, + int num_matches, + char_u **matches) +{ + taggy_T *tagstack = curwin->w_tagstack; + int tagstackidx = curwin->w_tagstackidx; + int i; + char_u *p; + char_u *command_end; + tagptrs_T tagp; + int taglen; + int attr; + + // Assume that the first match indicates how long the tags can + // be, and align the file names to that. + parse_match(matches[0], &tagp); + taglen = (int)(tagp.tagname_end - tagp.tagname + 2); + if (taglen < 18) { + taglen = 18; + } + if (taglen > Columns - 25) { + taglen = MAXCOL; + } + if (msg_col == 0) { + msg_didout = false; // overwrite previous message + } + msg_start(); + msg_puts_attr(_(" # pri kind tag"), HL_ATTR(HLF_T)); + msg_clr_eos(); + taglen_advance(taglen); + msg_puts_attr(_("file\n"), HL_ATTR(HLF_T)); + + for (i = 0; i < num_matches && !got_int; i++) { + parse_match(matches[i], &tagp); + if (!new_tag && ( + (g_do_tagpreview != 0 + && i == ptag_entry.cur_match) + || (use_tagstack + && i == tagstack[tagstackidx].cur_match))) { + *IObuff = '>'; + } else { + *IObuff = ' '; + } + vim_snprintf((char *)IObuff + 1, IOSIZE - 1, + "%2d %s ", i + 1, + mt_names[matches[i][0] & MT_MASK]); + msg_puts((char *)IObuff); + if (tagp.tagkind != NULL) { + msg_outtrans_len(tagp.tagkind, + (int)(tagp.tagkind_end - tagp.tagkind)); + } + msg_advance(13); + msg_outtrans_len_attr(tagp.tagname, + (int)(tagp.tagname_end - tagp.tagname), + HL_ATTR(HLF_T)); + msg_putchar(' '); + taglen_advance(taglen); + + // Find out the actual file name. If it is long, truncate + // it and put "..." in the middle + p = tag_full_fname(&tagp); + if (p != NULL) { + msg_outtrans_attr(p, HL_ATTR(HLF_D)); + XFREE_CLEAR(p); + } + if (msg_col > 0) { + msg_putchar('\n'); + } + if (got_int) { + break; + } + msg_advance(15); + + // print any extra fields + command_end = tagp.command_end; + if (command_end != NULL) { + p = command_end + 3; + while (*p && *p != '\r' && *p != '\n') { + while (*p == TAB) { + p++; + } + + // skip "file:" without a value (static tag) + if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) { + p += 5; + continue; + } + // skip "kind:" and "" + if (p == tagp.tagkind + || (p + 5 == tagp.tagkind + && STRNCMP(p, "kind:", 5) == 0)) { + p = tagp.tagkind_end; + continue; + } + // print all other extra fields + attr = HL_ATTR(HLF_CM); + while (*p && *p != '\r' && *p != '\n') { + if (msg_col + ptr2cells(p) >= Columns) { + msg_putchar('\n'); + if (got_int) { + break; + } + msg_advance(15); + } + p = msg_outtrans_one(p, attr); + if (*p == TAB) { + msg_puts_attr(" ", attr); + break; + } + if (*p == ':') { + attr = 0; + } + } + } + if (msg_col > 15) { + msg_putchar('\n'); + if (got_int) { + break; + } + msg_advance(15); + } + } else { + for (p = tagp.command; + *p && *p != '\r' && *p != '\n'; + p++) { + } + command_end = p; + } + + // Put the info (in several lines) at column 15. + // Don't display "/^" and "?^". + p = tagp.command; + if (*p == '/' || *p == '?') { + p++; + if (*p == '^') { + p++; + } + } + // Remove leading whitespace from pattern + while (p != command_end && ascii_isspace(*p)) { + p++; + } + + while (p != command_end) { + if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) { + msg_putchar('\n'); + } + if (got_int) { + break; + } + msg_advance(15); + + // skip backslash used for escaping a command char or + // a backslash + if (*p == '\\' && (*(p + 1) == *tagp.command + || *(p + 1) == '\\')) { + p++; + } + + if (*p == TAB) { + msg_putchar(' '); + p++; + } else { + p = msg_outtrans_one(p, 0); + } + + // don't display the "$/;\"" and "$?;\"" + if (p == command_end - 2 && *p == '$' + && *(p + 1) == *tagp.command) { + break; + } + // don't display matching '/' or '?' + if (p == command_end - 1 && *p == *tagp.command + && (*p == '/' || *p == '?')) { + break; + } + } + if (msg_col) { + msg_putchar('\n'); + } + os_breakcheck(); + } + if (got_int) { + got_int = false; // only stop the listing + } +} + +// +// Add the matching tags to the location list for the current +// window. +// +static int +add_llist_tags( + char_u *tag, + int num_matches, + char_u **matches) +{ + list_T *list; + char_u tag_name[128 + 1]; + char_u *fname; + char_u *cmd; + int i; + char_u *p; + tagptrs_T tagp; + + fname = xmalloc(MAXPATHL + 1); + cmd = xmalloc(CMDBUFFSIZE + 1); + list = tv_list_alloc(0); + + for (i = 0; i < num_matches; i++) { + int len, cmd_len; + long lnum; + dict_T *dict; + + parse_match(matches[i], &tagp); + + // Save the tag name + len = (int)(tagp.tagname_end - tagp.tagname); + if (len > 128) { + len = 128; + } + xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len); + tag_name[len] = NUL; + + // Save the tag file name + p = tag_full_fname(&tagp); + if (p == NULL) { + continue; + } + xstrlcpy((char *)fname, (const char *)p, MAXPATHL); + XFREE_CLEAR(p); + + // Get the line number or the search pattern used to locate + // the tag. + lnum = 0; + if (isdigit(*tagp.command)) { + // Line number is used to locate the tag + lnum = atol((char *)tagp.command); + } else { + char_u *cmd_start, *cmd_end; + + // Search pattern is used to locate the tag + + // Locate the end of the command + cmd_start = tagp.command; + cmd_end = tagp.command_end; + if (cmd_end == NULL) { + for (p = tagp.command; + *p && *p != '\r' && *p != '\n'; p++) { + } + cmd_end = p; + } + + // Now, cmd_end points to the character after the + // command. Adjust it to point to the last + // character of the command. + cmd_end--; + + // Skip the '/' and '?' characters at the + // beginning and end of the search pattern. + if (*cmd_start == '/' || *cmd_start == '?') { + cmd_start++; + } + + if (*cmd_end == '/' || *cmd_end == '?') { + cmd_end--; + } + + len = 0; + cmd[0] = NUL; + + // If "^" is present in the tag search pattern, then + // copy it first. + if (*cmd_start == '^') { + STRCPY(cmd, "^"); + cmd_start++; + len++; + } + + // Precede the tag pattern with \V to make it very + // nomagic. + STRCAT(cmd, "\\V"); + len += 2; + + cmd_len = (int)(cmd_end - cmd_start + 1); + if (cmd_len > (CMDBUFFSIZE - 5)) { + cmd_len = CMDBUFFSIZE - 5; + } + xstrlcat((char *)cmd, (char *)cmd_start, cmd_len); + len += cmd_len; + + if (cmd[len - 1] == '$') { + // Replace '$' at the end of the search pattern + // with '\$' + cmd[len - 1] = '\\'; + cmd[len] = '$'; + len++; + } + + cmd[len] = NUL; + } + + if ((dict = tv_dict_alloc()) == NULL) { + continue; + } + tv_list_append_dict(list, dict); + + tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); + tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); + tv_dict_add_nr(dict, S_LEN("lnum"), lnum); + if (lnum == 0) { + tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); + } + } + + vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); + set_errorlist(curwin, list, ' ', IObuff, NULL); + + tv_list_free(list); + XFREE_CLEAR(fname); + XFREE_CLEAR(cmd); + + return OK; +} + /* * Free cached tags. */ @@ -1055,11 +1109,11 @@ static void prepare_pats(pat_T *pats, int has_re) * TAG_KEEP_LANG keep language * TAG_CSCOPE use cscope results for tags */ -int -find_tags ( - char_u *pat, /* pattern to search for */ - int *num_matches, /* return: number of matches found */ - char_u ***matchesp, /* return: array of matches found */ +int +find_tags( + char_u *pat, // pattern to search for + int *num_matches, // return: number of matches found + char_u ***matchesp, // return: array of matches found int flags, int mincount, /* MAXCOL: find all matches other: minimal number of matches */ @@ -1999,11 +2053,11 @@ void free_tag_stuff(void) * * Return FAIL if no more tag file names, OK otherwise. */ -int -get_tagfname ( - tagname_T *tnp, /* holds status info */ - int first, /* TRUE when first file name is wanted */ - char_u *buf /* pointer to buffer of MAXPATHL chars */ +int +get_tagfname( + tagname_T *tnp, // holds status info + int first, // TRUE when first file name is wanted + char_u *buf // pointer to buffer of MAXPATHL chars ) { char_u *fname = NULL; @@ -2128,9 +2182,9 @@ void tagname_free(tagname_T *tnp) * * Return FAIL if there is a format error in this line, OK otherwise. */ -static int -parse_tag_line ( - char_u *lbuf, /* line to be parsed */ +static int +parse_tag_line( + char_u *lbuf, // line to be parsed tagptrs_T *tagp ) { @@ -2211,10 +2265,10 @@ static size_t matching_line_len(const char_u *const lbuf) * * Return OK or FAIL. */ -static int -parse_match ( - char_u *lbuf, /* input: matching line */ - tagptrs_T *tagp /* output: pointers into the line */ +static int +parse_match( + char_u *lbuf, // input: matching line + tagptrs_T *tagp // output: pointers into the line ) { int retval; @@ -2768,8 +2822,8 @@ expand_tags ( * Add a tag field to the dictionary "dict". * Return OK or FAIL. */ -static int -add_tag_field ( +static int +add_tag_field( dict_T *dict, const char *field_name, const char_u *start, // start of the value -- cgit From 6c012b0624935b93e92a0b12d86d49ef695210ba Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sat, 12 Oct 2019 09:48:48 +0200 Subject: vim-patch:8.1.1585: :let-heredoc does not trim enough Problem: :let-heredoc does not trim enough. Solution: Trim indent from the contents based on the indent of the first line. Use let-heredoc in more tests. https://github.com/vim/vim/commit/e7eb92708ec2092a2fc11e78703b5dcf83844412 --- src/nvim/eval.c | 52 ++++-- src/nvim/testdir/test_cindent.vim | 56 +++--- src/nvim/testdir/test_debugger.vim | 56 +++--- src/nvim/testdir/test_goto.vim | 288 +++++++++++++++---------------- src/nvim/testdir/test_let.vim | 10 +- src/nvim/testdir/test_mksession_utf8.vim | 36 ++-- src/nvim/testdir/test_normal.vim | 180 +++++++++---------- src/nvim/testdir/test_popup.vim | 11 +- src/nvim/testdir/test_profile.vim | 12 +- src/nvim/testdir/test_quickfix.vim | 152 ++++++++-------- 10 files changed, 445 insertions(+), 408 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a899dbcd3a..bedfe2d6d0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1521,7 +1521,9 @@ heredoc_get(exarg_T *eap, char_u *cmd) { char_u *marker; char_u *p; - int indent_len = 0; + int marker_indent_len = 0; + int text_indent_len = 0; + char_u *text_indent = NULL; if (eap->getline == NULL) { EMSG(_("E991: cannot use =<< here")); @@ -1534,14 +1536,16 @@ heredoc_get(exarg_T *eap, char_u *cmd) && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { cmd = skipwhite(cmd + 4); - // Trim the indentation from all the lines in the here document + // Trim the indentation from all the lines in the here document. // The amount of indentation trimmed is the same as the indentation of - // the :let command line. + // the first line after the :let command line. To find the end marker + // the indent of the :let command line is trimmed. p = *eap->cmdlinep; while (ascii_iswhite(*p)) { p++; - indent_len++; + marker_indent_len++; } + text_indent_len = -1; } // The marker is the next word. Default marker is "." @@ -1559,28 +1563,48 @@ heredoc_get(exarg_T *eap, char_u *cmd) list_T *l = tv_list_alloc(0); for (;;) { - int i = 0; + int mi = 0; + int ti = 0; char_u *theline = eap->getline(NUL, eap->cookie, 0, false); - if (theline != NULL && indent_len > 0) { - // trim the indent matching the first line - if (STRNCMP(theline, *eap->cmdlinep, indent_len) == 0) { - i = indent_len; - } - } - if (theline == NULL) { EMSG2(_("E990: Missing end marker '%s'"), marker); break; } - if (STRCMP(marker, theline + i) == 0) { + + // with "trim": skip the indent matching the :let line to find the + // marker + if (marker_indent_len > 0 + && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) { + mi = marker_indent_len; + } + if (STRCMP(marker, theline + mi) == 0) { xfree(theline); break; } + if (text_indent_len == -1 && *theline != NUL) { + // set the text indent from the first line. + p = theline; + text_indent_len = 0; + while (ascii_iswhite(*p)) { + p++; + text_indent_len++; + } + text_indent = vim_strnsave(theline, text_indent_len); + } + // with "trim": skip the indent matching the first line + if (text_indent != NULL) { + for (ti = 0; ti < text_indent_len; ti++) { + if (theline[ti] != text_indent[ti]) { + break; + } + } + } - tv_list_append_string(l, (char *)(theline + i), -1); + tv_list_append_string(l, (char *)(theline + ti), -1); xfree(theline); } + xfree(text_indent); return l; } diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index f979e354ba..d9795d9335 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -19,23 +19,23 @@ func Test_cino_extern_c() " Test for cino-E let without_ind =<< trim [CODE] - #ifdef __cplusplus - extern "C" { - #endif - int func_a(void); - #ifdef __cplusplus - } - #endif + #ifdef __cplusplus + extern "C" { + #endif + int func_a(void); + #ifdef __cplusplus + } + #endif [CODE] let with_ind =<< trim [CODE] - #ifdef __cplusplus - extern "C" { - #endif - int func_a(void); - #ifdef __cplusplus - } - #endif + #ifdef __cplusplus + extern "C" { + #endif + int func_a(void); + #ifdef __cplusplus + } + #endif [CODE] new setlocal cindent cinoptions=E0 @@ -90,30 +90,30 @@ func Test_cindent_expr() endfunc setl expandtab sw=8 indentkeys+=; indentexpr=MyIndentFunction() let testinput =<< trim [CODE] - var_a = something() - b = something() + var_a = something() + b = something() [CODE] call setline(1, testinput) call cursor(1, 1) call feedkeys("^\j$A;\", 'tnix') - let expected =<< trim [CODE] - var_a = something(); - b = something(); - [CODE] + let expected =<< [CODE] + var_a = something(); +b = something(); +[CODE] call assert_equal(expected, getline(1, '$')) %d - let testinput =<< trim [CODE] - var_a = something() - b = something() - [CODE] + let testinput =<< [CODE] + var_a = something() + b = something() +[CODE] call setline(1, testinput) call cursor(1, 1) call feedkeys("^\j$A;\", 'tnix') - let expected =<< trim [CODE] - var_a = something(); - b = something() - [CODE] + let expected =<< [CODE] + var_a = something(); + b = something() +[CODE] call assert_equal(expected, getline(1, '$')) bw! endfunc diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index 3ef460b4fe..130bcf8910 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -26,27 +26,29 @@ func Test_Debugger() endif " Create a Vim script with some functions - call writefile([ - \ 'func Foo()', - \ ' let var1 = 1', - \ ' let var2 = Bar(var1) + 9', - \ ' return var2', - \ 'endfunc', - \ 'func Bar(var)', - \ ' let var1 = 2 + a:var', - \ ' let var2 = Bazz(var1) + 4', - \ ' return var2', - \ 'endfunc', - \ 'func Bazz(var)', - \ ' try', - \ ' let var1 = 3 + a:var', - \ ' let var3 = "another var"', - \ ' let var3 = "value2"', - \ ' catch', - \ ' let var4 = "exception"', - \ ' endtry', - \ ' return var1', - \ 'endfunc'], 'Xtest.vim') + let lines =<< trim END + func Foo() + let var1 = 1 + let var2 = Bar(var1) + 9 + return var2 + endfunc + func Bar(var) + let var1 = 2 + a:var + let var2 = Bazz(var1) + 4 + return var2 + endfunc + func Bazz(var) + try + let var1 = 3 + a:var + let var3 = "another var" + let var3 = "value2" + catch + let var4 = "exception" + endtry + return var1 + endfunc + END + call writefile(lines, 'Xtest.vim') " Start Vim in a terminal let buf = RunVimInTerminal('-S Xtest.vim', {}) @@ -294,11 +296,13 @@ func Test_Debugger() " Tests for :breakadd file and :breakadd here " Breakpoints should be set before sourcing the file - call writefile([ - \ 'let var1 = 10', - \ 'let var2 = 20', - \ 'let var3 = 30', - \ 'let var4 = 40'], 'Xtest.vim') + let lines =<< trim END + let var1 = 10 + let var2 = 20 + let var3 = 30 + let var4 = 40 + END + call writefile(lines, 'Xtest.vim') " Start Vim in a terminal let buf = RunVimInTerminal('Xtest.vim', {}) diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index f04a5a7e3d..19513b315a 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -16,12 +16,12 @@ endfunc func Test_gD() let lines =<< trim [CODE] - int x; - - int func(void) - { - return x; - } + int x; + + int func(void) + { + return x; + } [CODE] call XTest_goto_decl('gD', lines, 1, 5) @@ -29,12 +29,12 @@ endfunc func Test_gD_too() let lines =<< trim [CODE] - Filename x; - - int Filename - int func() { Filename x; - return x; + + int Filename + int func() { + Filename x; + return x; [CODE] call XTest_goto_decl('gD', lines, 1, 10) @@ -42,13 +42,13 @@ endfunc func Test_gD_comment() let lines =<< trim [CODE] - /* int x; */ - int x; - - int func(void) - { - return x; - } + /* int x; */ + int x; + + int func(void) + { + return x; + } [CODE] call XTest_goto_decl('gD', lines, 2, 5) @@ -56,13 +56,13 @@ endfunc func Test_gD_inline_comment() let lines =<< trim [CODE] - int y /* , x */; - int x; - - int func(void) - { - return x; - } + int y /* , x */; + int x; + + int func(void) + { + return x; + } [CODE] call XTest_goto_decl('gD', lines, 2, 5) @@ -70,13 +70,13 @@ endfunc func Test_gD_string() let lines =<< trim [CODE] - char *s[] = "x"; - int x = 1; - - int func(void) - { - return x; - } + char *s[] = "x"; + int x = 1; + + int func(void) + { + return x; + } [CODE] call XTest_goto_decl('gD', lines, 2, 5) @@ -84,12 +84,12 @@ endfunc func Test_gD_string_same_line() let lines =<< trim [CODE] - char *s[] = "x", int x = 1; - - int func(void) - { - return x; - } + char *s[] = "x", int x = 1; + + int func(void) + { + return x; + } [CODE] call XTest_goto_decl('gD', lines, 1, 22) @@ -97,13 +97,13 @@ endfunc func Test_gD_char() let lines =<< trim [CODE] - char c = 'x'; - int x = 1; - - int func(void) - { - return x; - } + char c = 'x'; + int x = 1; + + int func(void) + { + return x; + } [CODE] call XTest_goto_decl('gD', lines, 2, 5) @@ -111,12 +111,12 @@ endfunc func Test_gd() let lines =<< trim [CODE] - int x; - - int func(int x) - { - return x; - } + int x; + + int func(int x) + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 3, 14) @@ -124,15 +124,15 @@ endfunc func Test_gd_not_local() let lines =<< trim [CODE] - int func1(void) - { - return x; - } - - int func2(int x) - { - return x; - } + int func1(void) + { + return x; + } + + int func2(int x) + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 3, 10) @@ -140,11 +140,11 @@ endfunc func Test_gd_kr_style() let lines =<< trim [CODE] - int func(x) - int x; - { - return x; - } + int func(x) + int x; + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 2, 7) @@ -152,15 +152,15 @@ endfunc func Test_gd_missing_braces() let lines =<< trim [CODE] - def func1(a) - a + 1 - end - - a = 1 - - def func2() - return a - end + def func1(a) + a + 1 + end + + a = 1 + + def func2() + return a + end [CODE] call XTest_goto_decl('gd', lines, 1, 11) @@ -168,12 +168,12 @@ endfunc func Test_gd_comment() let lines =<< trim [CODE] - int func(void) - { - /* int x; */ - int x; - return x; - } + int func(void) + { + /* int x; */ + int x; + return x; + } [CODE] call XTest_goto_decl('gd', lines, 4, 7) @@ -181,12 +181,12 @@ endfunc func Test_gd_comment_in_string() let lines =<< trim [CODE] - int func(void) - { - char *s ="//"; int x; - int x; - return x; - } + int func(void) + { + char *s ="//"; int x; + int x; + return x; + } [CODE] call XTest_goto_decl('gd', lines, 3, 22) @@ -195,12 +195,12 @@ endfunc func Test_gd_string_in_comment() set comments= let lines =<< trim [CODE] - int func(void) - { - /* " */ int x; - int x; - return x; - } + int func(void) + { + /* " */ int x; + int x; + return x; + } [CODE] call XTest_goto_decl('gd', lines, 3, 15) @@ -209,10 +209,10 @@ endfunc func Test_gd_inline_comment() let lines =<< trim [CODE] - int func(/* x is an int */ int x) - { - return x; - } + int func(/* x is an int */ int x) + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 1, 32) @@ -220,10 +220,10 @@ endfunc func Test_gd_inline_comment_only() let lines =<< trim [CODE] - int func(void) /* one lonely x */ - { - return x; - } + int func(void) /* one lonely x */ + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 3, 10) @@ -231,16 +231,16 @@ endfunc func Test_gd_inline_comment_body() let lines =<< trim [CODE] - int func(void) - { - int y /* , x */; - - for (/* int x = 0 */; y < 2; y++); - - int x = 0; - - return x; - } + int func(void) + { + int y /* , x */; + + for (/* int x = 0 */; y < 2; y++); + + int x = 0; + + return x; + } [CODE] call XTest_goto_decl('gd', lines, 7, 7) @@ -248,10 +248,10 @@ endfunc func Test_gd_trailing_multiline_comment() let lines =<< trim [CODE] - int func(int x) /* x is an int */ - { - return x; - } + int func(int x) /* x is an int */ + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 1, 14) @@ -259,10 +259,10 @@ endfunc func Test_gd_trailing_comment() let lines =<< trim [CODE] - int func(int x) // x is an int - { - return x; - } + int func(int x) // x is an int + { + return x; + } [CODE] call XTest_goto_decl('gd', lines, 1, 14) @@ -270,13 +270,13 @@ endfunc func Test_gd_string() let lines =<< trim [CODE] - int func(void) - { - char *s = "x"; - int x = 1; - - return x; - } + int func(void) + { + char *s = "x"; + int x = 1; + + return x; + } [CODE] call XTest_goto_decl('gd', lines, 4, 7) @@ -284,12 +284,12 @@ endfunc func Test_gd_string_only() let lines =<< trim [CODE] - int func(void) - { - char *s = "x"; - - return x; - } + int func(void) + { + char *s = "x"; + + return x; + } [CODE] call XTest_goto_decl('gd', lines, 5, 10) @@ -312,21 +312,21 @@ endfunc func Test_gd_local_block() let lines =<< trim [CODE] int main() - { - char *a = "NOT NULL"; - if(a) - { - char *b = a; - printf("%s\n", b); - } - else { - char *b = "NULL"; - return b; + char *a = "NOT NULL"; + if(a) + { + char *b = a; + printf("%s\n", b); + } + else + { + char *b = "NULL"; + return b; + } + + return 0; } - - return 0; - } [CODE] call XTest_goto_decl('1gd', lines, 11, 11) diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 43f35e2b9d..d5100b5a82 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -199,10 +199,18 @@ END END call assert_equal(['Line1', ' Line2', "\tLine3", ' END'], var1) + let var1 =<< trim !!! + Line1 + line2 + Line3 + !!! + !!! + call assert_equal(['Line1', ' line2', "\tLine3", '!!!',], var1) + let var1 =<< trim Line1 . - call assert_equal([' Line1'], var1) + call assert_equal(['Line1'], var1) " ignore "endfunc" let var1 =<< END diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim index 36f07512a8..722fd28beb 100644 --- a/src/nvim/testdir/test_mksession_utf8.vim +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -66,32 +66,32 @@ func Test_mksession_utf8() mksession! test_mks.out let li = filter(readfile('test_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"') let expected =<< trim [DATA] - normal! 016| - normal! 016| - normal! 016| - normal! 08| - normal! 08| - normal! 016| - normal! 016| - normal! 016| - exe 'normal! ' . s:c . '|zs' . 16 . '|' normal! 016| - exe 'normal! ' . s:c . '|zs' . 16 . '|' normal! 016| - exe 'normal! ' . s:c . '|zs' . 16 . '|' normal! 016| - exe 'normal! ' . s:c . '|zs' . 8 . '|' normal! 08| - exe 'normal! ' . s:c . '|zs' . 8 . '|' normal! 08| - exe 'normal! ' . s:c . '|zs' . 16 . '|' normal! 016| - exe 'normal! ' . s:c . '|zs' . 16 . '|' normal! 016| - exe 'normal! ' . s:c . '|zs' . 16 . '|' - normal! 016| - exe 'normal! ' . s:c . '|zs' . 16 . '|' normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 8 . '|' + normal! 08| + exe 'normal! ' . s:c . '|zs' . 8 . '|' + normal! 08| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| + exe 'normal! ' . s:c . '|zs' . 16 . '|' + normal! 016| [DATA] call assert_equal(expected, li) diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index b967f84626..08b0db234e 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1564,34 +1564,34 @@ endfunc fun! Test_normal29_brace() " basic test for { and } movements let text =<< trim [DATA] - A paragraph begins after each empty line, and also at each of a set of - paragraph macros, specified by the pairs of characters in the 'paragraphs' - option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to - the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in - the first column). A section boundary is also a paragraph boundary. - Note that a blank line (only containing white space) is NOT a paragraph - boundary. - - - Also note that this does not include a '{' or '}' in the first column. When - the '{' flag is in 'cpoptions' then '{' in the first column is used as a - paragraph boundary |posix|. - { - This is no paragraph - unless the '{' is set - in 'cpoptions' - } - .IP - The nroff macros IP separates a paragraph - That means, it must be a '.' - followed by IP - .LPIt does not matter, if afterwards some - more characters follow. - .SHAlso section boundaries from the nroff - macros terminate a paragraph. That means - a character like this: - .NH - End of text here + A paragraph begins after each empty line, and also at each of a set of + paragraph macros, specified by the pairs of characters in the 'paragraphs' + option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to + the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in + the first column). A section boundary is also a paragraph boundary. + Note that a blank line (only containing white space) is NOT a paragraph + boundary. + + + Also note that this does not include a '{' or '}' in the first column. When + the '{' flag is in 'cpoptions' then '{' in the first column is used as a + paragraph boundary |posix|. + { + This is no paragraph + unless the '{' is set + in 'cpoptions' + } + .IP + The nroff macros IP separates a paragraph + That means, it must be a '.' + followed by IP + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + .NH + End of text here [DATA] new @@ -1600,17 +1600,17 @@ fun! Test_normal29_brace() norm! 0d2} let expected =<< trim [DATA] - .IP - The nroff macros IP separates a paragraph - That means, it must be a '.' - followed by IP - .LPIt does not matter, if afterwards some - more characters follow. - .SHAlso section boundaries from the nroff - macros terminate a paragraph. That means - a character like this: - .NH - End of text here + .IP + The nroff macros IP separates a paragraph + That means, it must be a '.' + followed by IP + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + .NH + End of text here [DATA] call assert_equal(expected, getline(1, '$')) @@ -1618,13 +1618,13 @@ fun! Test_normal29_brace() norm! 0d} let expected =<< trim [DATA] - .LPIt does not matter, if afterwards some - more characters follow. - .SHAlso section boundaries from the nroff - macros terminate a paragraph. That means - a character like this: - .NH - End of text here + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: + .NH + End of text here [DATA] call assert_equal(expected, getline(1, '$')) @@ -1633,11 +1633,11 @@ fun! Test_normal29_brace() norm! d{ let expected =<< trim [DATA] - .LPIt does not matter, if afterwards some - more characters follow. - .SHAlso section boundaries from the nroff - macros terminate a paragraph. That means - a character like this: + .LPIt does not matter, if afterwards some + more characters follow. + .SHAlso section boundaries from the nroff + macros terminate a paragraph. That means + a character like this: [DATA] call assert_equal(expected, getline(1, '$')) @@ -1645,8 +1645,8 @@ fun! Test_normal29_brace() norm! d{ let expected =<< trim [DATA] - .LPIt does not matter, if afterwards some - more characters follow. + .LPIt does not matter, if afterwards some + more characters follow. [DATA] call assert_equal(expected, getline(1, '$')) @@ -1659,22 +1659,22 @@ fun! Test_normal29_brace() " 1 " norm! 0d2} " let expected =<< trim [DATA] - " { - " This is no paragraph - " unless the '{' is set - " in 'cpoptions' - " } - " .IP - " The nroff macros IP separates a paragraph - " That means, it must be a '.' - " followed by IP - " .LPIt does not matter, if afterwards some - " more characters follow. - " .SHAlso section boundaries from the nroff - " macros terminate a paragraph. That means - " a character like this: - " .NH - " End of text here + " { + " This is no paragraph + " unless the '{' is set + " in 'cpoptions' + " } + " .IP + " The nroff macros IP separates a paragraph + " That means, it must be a '.' + " followed by IP + " .LPIt does not matter, if afterwards some + " more characters follow. + " .SHAlso section boundaries from the nroff + " macros terminate a paragraph. That means + " a character like this: + " .NH + " End of text here " " [DATA] " call assert_equal(expected, getline(1, '$')) @@ -1682,22 +1682,22 @@ fun! Test_normal29_brace() " $ " norm! d} " let expected =<< trim [DATA] - " { - " This is no paragraph - " unless the '{' is set - " in 'cpoptions' - " } - " .IP - " The nroff macros IP separates a paragraph - " That means, it must be a '.' - " followed by IP - " .LPIt does not matter, if afterwards some - " more characters follow. - " .SHAlso section boundaries from the nroff - " macros terminate a paragraph. That means - " a character like this: - " .NH - " End of text here + " { + " This is no paragraph + " unless the '{' is set + " in 'cpoptions' + " } + " .IP + " The nroff macros IP separates a paragraph + " That means, it must be a '.' + " followed by IP + " .LPIt does not matter, if afterwards some + " more characters follow. + " .SHAlso section boundaries from the nroff + " macros terminate a paragraph. That means + " a character like this: + " .NH + " End of text here " " [DATA] " call assert_equal(expected, getline(1, '$')) @@ -1706,11 +1706,11 @@ fun! Test_normal29_brace() " norm! d5} " " let expected =<< trim [DATA] - " { - " This is no paragraph - " unless the '{' is set - " in 'cpoptions' - " } + " { + " This is no paragraph + " unless the '{' is set + " in 'cpoptions' + " } " [DATA] " call assert_equal(expected, getline(1, '$')) diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index c63269e5d2..8083672808 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -737,11 +737,12 @@ func Test_popup_position() if !CanRunVimInTerminal() return endif - call writefile([ - \ '123456789_123456789_123456789_a', - \ '123456789_123456789_123456789_b', - \ ' 123', - \ ], 'Xtest') + let lines =<< trim END + 123456789_123456789_123456789_a + 123456789_123456789_123456789_b + 123 + END + call writefile(lines, 'Xtest') let buf = RunVimInTerminal('Xtest', {}) call term_sendkeys(buf, ":vsplit\") diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 4ab20a9c77..f3eb88abf0 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -312,13 +312,13 @@ endfunc func Test_profile_file() let lines =<< trim [CODE] - func! Foo() - endfunc - for i in range(10) - " a comment + func! Foo() + endfunc + for i in range(10) + " a comment + call Foo() + endfor call Foo() - endfor - call Foo() [CODE] call writefile(lines, 'Xprofile_file.vim') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index b9a22aff51..fc514fc9e6 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -776,67 +776,67 @@ func Test_efm1() endif let l =<< trim [DATA] - "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set. - "Xtestfile", line 6 col 19; this is an error - gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c - Xtestfile:9: parse error before `asd' - make: *** [vim] Error 1 - in file "Xtestfile" linenr 10: there is an error - - 2 returned - "Xtestfile", line 11 col 1; this is an error - "Xtestfile", line 12 col 2; this is another error - "Xtestfile", line 14:10; this is an error in column 10 - =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time - "Xtestfile", linenr 16: yet another problem - Error in "Xtestfile" at line 17: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 - ^ - Error in "Xtestfile" at line 18: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 - .............^ - Error in "Xtestfile" at line 19: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 - --------------^ - Error in "Xtestfile" at line 20: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 - ^ - - Does anyone know what is the problem and how to correction it? - "Xtestfile", line 21 col 9: What is the title of the quickfix window? - "Xtestfile", line 22 col 9: What is the title of the quickfix window? + "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set. + "Xtestfile", line 6 col 19; this is an error + gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c + Xtestfile:9: parse error before `asd' + make: *** [vim] Error 1 + in file "Xtestfile" linenr 10: there is an error + + 2 returned + "Xtestfile", line 11 col 1; this is an error + "Xtestfile", line 12 col 2; this is another error + "Xtestfile", line 14:10; this is an error in column 10 + =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time + "Xtestfile", linenr 16: yet another problem + Error in "Xtestfile" at line 17: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 + ^ + Error in "Xtestfile" at line 18: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 + .............^ + Error in "Xtestfile" at line 19: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 + --------------^ + Error in "Xtestfile" at line 20: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 + ^ + + Does anyone know what is the problem and how to correction it? + "Xtestfile", line 21 col 9: What is the title of the quickfix window? + "Xtestfile", line 22 col 9: What is the title of the quickfix window? [DATA] call writefile(l, 'Xerrorfile1') call writefile(l[:-2], 'Xerrorfile2') - let m =<< trim [DATA] - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22 - [DATA] + let m =<< [DATA] + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 5 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 6 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 7 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 8 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 9 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 10 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 11 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 12 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 13 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 14 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 15 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 16 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21 + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22 +[DATA] call writefile(m, 'Xtestfile') let save_efm = &efm @@ -1053,20 +1053,20 @@ func Test_efm2() " Test for %P, %Q and %t format specifiers let lines =<< trim [DATA] - [Xtestfile1] - (1,17) error: ';' missing - (21,2) warning: variable 'z' not defined - (67,3) error: end of file found before string ended - -- - - [Xtestfile2] - -- - - [Xtestfile3] - NEW compiler v1.1 - (2,2) warning: variable 'x' not defined - (67,3) warning: 's' already defined - - + [Xtestfile1] + (1,17) error: ';' missing + (21,2) warning: variable 'z' not defined + (67,3) error: end of file found before string ended + -- + + [Xtestfile2] + -- + + [Xtestfile3] + NEW compiler v1.1 + (2,2) warning: variable 'x' not defined + (67,3) warning: 's' already defined + -- [DATA] set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r " To exercise the push/pop file functionality in quickfix, the test files @@ -1090,10 +1090,10 @@ func Test_efm2() " Tests for %E, %C and %Z format specifiers let lines =<< trim [DATA] - Error 275 - line 42 - column 3 - ' ' expected after '--' + Error 275 + line 42 + column 3 + ' ' expected after '--' [DATA] set efm=%EError\ %n,%Cline\ %l,%Ccolumn\ %c,%Z%m @@ -1107,8 +1107,8 @@ func Test_efm2() " Test for %> let lines =<< trim [DATA] - Error in line 147 of foo.c: - unknown variable 'i' + Error in line 147 of foo.c: + unknown variable 'i' [DATA] set efm=unknown\ variable\ %m,%E%>Error\ in\ line\ %l\ of\ %f:,%Z%m -- cgit From 14c611ed7788fe68f5752ed29ab295a2f0e7c21e Mon Sep 17 00:00:00 2001 From: erw7 Date: Sun, 13 Oct 2019 17:34:08 +0900 Subject: vim-patch 8.1.0084: user name completion does not work on MS-Windows Problem: User name completion does not work on MS-Windows. Solution: Use NetUserEnum() to get user names. (Yasuhiro Matsumoto) https://github.com/vim/vim/commit/828c3d70833a0689cc07581f2a67d06430675da5 --- src/nvim/CMakeLists.txt | 1 + src/nvim/os/users.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a64944ab0d..b00ac866b7 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -419,6 +419,7 @@ if(Iconv_LIBRARIES) endif() if(WIN32) + list(APPEND NVIM_LINK_LIBRARIES netapi32) list(APPEND NVIM_LINK_LIBRARIES ${WINPTY_LIBRARIES}) endif() diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index c6463c2c92..b24232b680 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -13,6 +13,9 @@ #ifdef HAVE_PWD_H # include #endif +#ifdef WIN32 +# include +#endif // Initialize users garray and fill it with os usernames. // Return Ok for success, FAIL for failure. @@ -34,6 +37,26 @@ int os_get_usernames(garray_T *users) } } endpwent(); +# elif defined(WIN32) + { + DWORD nusers = 0, ntotal = 0, i; + PUSER_INFO_0 uinfo; + + if (NetUserEnum(NULL, 0, 0, (LPBYTE *)&uinfo, MAX_PREFERRED_LENGTH, + &nusers, &ntotal, NULL) == NERR_Success) { + for (i = 0; i < nusers; i++) { + char *user; + int conversion_result = utf16_to_utf8(uinfo[i].usri0_name, -1, &user); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %d", conversion_result); + break; + } + GA_APPEND(char *, users, user); + } + + NetApiBufferFree(uinfo); + } + } # endif return OK; -- cgit From b89e970cfb472af021e56438a5147dd698e66376 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sun, 13 Oct 2019 17:48:01 +0900 Subject: vim-patch 8.1.0085: no test for completing user name and language Problem: No test for completing user name and language. Solution: Add tests. (Dominique Pelle, closes #2978) https://github.com/vim/vim/commit/5f8f2d378a4f6d7db12806f3e35ec6f7fc6bd1f3 --- src/nvim/testdir/test_cmdline.vim | 45 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index e6aafd964b..0a3e6ae625 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1,6 +1,5 @@ " Tests for editing the command line. - func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') call feedkeys(":e Xtestf\t\r", "tx") @@ -477,6 +476,50 @@ func Test_cmdline_complete_user_cmd() delcommand Foo endfunc +func Test_cmdline_complete_user_names() + if has('unix') && executable('whoami') + let whoami = systemlist('whoami')[0] + let first_letter = whoami[0] + if len(first_letter) > 0 + " Trying completion of :e ~x where x is the first letter of + " the user name should complete to at least the user name. + call feedkeys(':e ~' . first_letter . "\\\"\", 'tx') + call assert_match('^"e \~.*\<' . whoami . '\>', @:) + endif + endif + if has('win32') + " Just in case: check that the system has an Administrator account. + let names = system('net user') + if names =~ 'Administrator' + " Trying completion of :e ~A should complete to Administrator. + call feedkeys(':e ~A' . "\\\"\", 'tx') + call assert_match('^"e \~Administrator', @:) + endif + endif +endfunc + +funct Test_cmdline_complete_languages() + let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '') + + call feedkeys(":language \\\"\", 'tx') + call assert_match('^"language .*\.*\.*\', @:) + + if has('unix') + " TODO: these tests don't work on Windows. lang appears to be 'C' + " but C does not appear in the completion. Why? + call assert_match('^"language .*\<' . lang . '\>', @:) + + call feedkeys(":language messages \\\"\", 'tx') + call assert_match('^"language .*\<' . lang . '\>', @:) + + call feedkeys(":language ctype \\\"\", 'tx') + call assert_match('^"language .*\<' . lang . '\>', @:) + + call feedkeys(":language time \\\"\", 'tx') + call assert_match('^"language .*\<' . lang . '\>', @:) + endif +endfunc + func Test_cmdline_write_alternatefile() new call setline('.', ['one', 'two']) -- cgit From fcc24d0df3b1a6bde82c0e5b90f1392639f3fa5b Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sat, 12 Oct 2019 22:49:21 +0200 Subject: vim-patch:8.1.1625: script line numbers are not exactly right Problem: Script line numbers are not exactly right. Solution: Handle heredoc and continuation lines better. (Ozaki Kiichi, closes vim/vim#4611, closes vim/vim#4511) https://github.com/vim/vim/commit/bc2cfe4672d370330b8698d4d025697a9a6ec569 --- src/nvim/eval.c | 26 ++++++++------ src/nvim/ex_cmds2.c | 21 ++++++++--- src/nvim/testdir/test_vimscript.vim | 70 +++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bedfe2d6d0..d314e3a732 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21301,7 +21301,8 @@ void ex_function(exarg_T *eap) hashtab_T *ht; int todo; hashitem_T *hi; - int sourcing_lnum_off; + linenr_T sourcing_lnum_off; + linenr_T sourcing_lnum_top; bool show_block = false; bool do_concat = true; @@ -21550,15 +21551,17 @@ void ex_function(exarg_T *eap) cmdline_row = msg_row; } + // Save the starting line number. + sourcing_lnum_top = sourcing_lnum; + indent = 2; nesting = 0; for (;; ) { if (KeyTyped) { - msg_scroll = TRUE; - saved_wait_return = FALSE; + msg_scroll = true; + saved_wait_return = false; } - need_wait_return = FALSE; - sourcing_lnum_off = sourcing_lnum; + need_wait_return = false; if (line_arg != NULL) { /* Use eap->arg, split up in parts by line breaks. */ @@ -21591,11 +21594,13 @@ void ex_function(exarg_T *eap) ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); } - /* Detect line continuation: sourcing_lnum increased more than one. */ - if (sourcing_lnum > sourcing_lnum_off + 1) - sourcing_lnum_off = sourcing_lnum - sourcing_lnum_off - 1; - else + // Detect line continuation: sourcing_lnum increased more than one. + sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie); + if (sourcing_lnum < sourcing_lnum_off) { + sourcing_lnum_off -= sourcing_lnum; + } else { sourcing_lnum_off = 0; + } if (skip_until != NULL) { // Between ":append" and "." and between ":python <uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; - fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len - 1; + fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + goto ret_free; erret: diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 272c81e29b..84291b3637 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -97,10 +97,11 @@ typedef struct sn_prl_S { struct source_cookie { FILE *fp; ///< opened file for sourcing char_u *nextline; ///< if not NULL: line that was read ahead + linenr_T sourcing_lnum; ///< line number of the source file int finished; ///< ":finish" used #if defined(USE_CRNL) int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS - bool error; ///< true if LF found after CR-LF + bool error; ///< true if LF found after CR-LF #endif linenr_T breakpoint; ///< next line with breakpoint or zero char_u *fname; ///< name of sourced file @@ -3124,6 +3125,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc) #endif cookie.nextline = NULL; + cookie.sourcing_lnum = 0; cookie.finished = false; // Check if this script has a breakpoint. @@ -3375,6 +3377,13 @@ void free_scriptnames(void) } # endif +linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie) +{ + return fgetline == getsourceline + ? ((struct source_cookie *)cookie)->sourcing_lnum + : sourcing_lnum; +} + /// Get one full line from a sourced file. /// Called by do_cmdline() when it's called from do_source(). @@ -3395,6 +3404,8 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat) if (do_profiling == PROF_YES) { script_line_end(); } + // Set the current sourcing line number. + sourcing_lnum = sp->sourcing_lnum + 1; // Get current line. If there is a read-ahead line, use it, otherwise get // one now. if (sp->finished) { @@ -3404,7 +3415,7 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat) } else { line = sp->nextline; sp->nextline = NULL; - sourcing_lnum++; + sp->sourcing_lnum++; } if (line != NULL && do_profiling == PROF_YES) { script_line_start(); @@ -3414,7 +3425,7 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat) // contain the 'C' flag. if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { // compensate for the one line read-ahead - sourcing_lnum--; + sp->sourcing_lnum--; // Get the next line and concatenate it when it starts with a // backslash. We always need to read the next line, keep it in @@ -3492,7 +3503,7 @@ static char_u *get_one_sourceline(struct source_cookie *sp) ga_init(&ga, 1, 250); // Loop until there is a finished line (or end-of-file). - sourcing_lnum++; + sp->sourcing_lnum++; for (;; ) { // make room to read at least 120 (more) characters ga_grow(&ga, 120); @@ -3559,7 +3570,7 @@ retry: // len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) {} if ((len & 1) != (c & 1)) { // escaped NL, read more - sourcing_lnum++; + sp->sourcing_lnum++; continue; } diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index f39e53d6dd..3fcba4134e 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1409,6 +1409,76 @@ func Test_compound_assignment_operators() let @/ = '' endfunc +func Test_function_defined_line() + if has('gui_running') + " Can't catch the output of gvim. + return + endif + + let lines =<< trim [CODE] + " F1 + func F1() + " F2 + func F2() + " + " + " + return + endfunc + " F3 + execute "func F3()\n\n\n\nreturn\nendfunc" + " F4 + execute "func F4()\n + \\n + \\n + \\n + \return\n + \endfunc" + endfunc + " F5 + execute "func F5()\n\n\n\nreturn\nendfunc" + " F6 + execute "func F6()\n + \\n + \\n + \\n + \return\n + \endfunc" + call F1() + verbose func F1 + verbose func F2 + verbose func F3 + verbose func F4 + verbose func F5 + verbose func F6 + qall! + [CODE] + + call writefile(lines, 'Xtest.vim') + let res = system(v:progpath .. ' --clean -es -X -S Xtest.vim') + call assert_equal(0, v:shell_error) + + let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') + call assert_match(' line 2$', m) + + let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') + call assert_match(' line 4$', m) + + let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') + call assert_match(' line 11$', m) + + let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') + call assert_match(' line 13$', m) + + let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') + call assert_match(' line 21$', m) + + let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') + call assert_match(' line 23$', m) + + call delete('Xtest.vim') +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -- cgit From 3b894b1cb18a9d4e399ab5b55004767f63a384c3 Mon Sep 17 00:00:00 2001 From: Jurica Bradaric Date: Sat, 12 Oct 2019 23:47:00 +0200 Subject: vim-patch:8.1.1723: heredoc assignment has no room for new features Problem: Heredoc assignment has no room for new features. (FUJIWARA Takuya) Solution: Require the marker does not start with a lower case character. (closes vim/vim#4705) https://github.com/vim/vim/commit/24582007294b0db3be9669d3b583ea45fc4f19b8 --- src/nvim/eval.c | 9 +++++++-- src/nvim/testdir/test_let.vim | 34 ++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d314e3a732..7db9386937 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1548,7 +1548,7 @@ heredoc_get(exarg_T *eap, char_u *cmd) text_indent_len = -1; } - // The marker is the next word. Default marker is "." + // The marker is the next word. if (*cmd != NUL && *cmd != '"') { marker = skipwhite(cmd); p = skiptowhite(marker); @@ -1557,8 +1557,13 @@ heredoc_get(exarg_T *eap, char_u *cmd) return NULL; } *p = NUL; + if (islower(*marker)) { + EMSG(_("E221: Marker cannot start with lower case letter")); + return NULL; + } } else { - marker = (char_u *)"."; + EMSG(_("E172: Missing marker")); + return NULL; } list_T *l = tv_list_alloc(0); diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index d5100b5a82..66067d3fc0 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -153,14 +153,28 @@ func Test_let_heredoc_fails() call assert_fails('source XheredocFail', 'E126:') call delete('XheredocFail') - let text =<< trim END + let text =<< trim CodeEnd func MissingEnd() let v =<< END endfunc - END + CodeEnd call writefile(text, 'XheredocWrong') call assert_fails('source XheredocWrong', 'E126:') call delete('XheredocWrong') + + let text =<< trim TEXTend + let v =<< " comment + TEXTend + call writefile(text, 'XheredocNoMarker') + call assert_fails('source XheredocNoMarker', 'E172:') + call delete('XheredocNoMarker') + + let text =<< trim TEXTend + let v =<< text + TEXTend + call writefile(text, 'XheredocBadMarker') + call assert_fails('source XheredocBadMarker', 'E221:') + call delete('XheredocBadMarker') endfunc " Test for the setting a variable using the heredoc syntax @@ -173,9 +187,9 @@ END call assert_equal(["Some sample text", "\tText with indent", " !@#$%^&*()-+_={}|[]\\~`:\";'<>?,./"], var1) - let var2 =<< + let var2 =<< XXX Editor -. +XXX call assert_equal(['Editor'], var2) let var3 =< Date: Sun, 13 Oct 2019 00:04:41 +0200 Subject: vim-patch:8.1.1729: heredoc with trim not properly handled in function Problem: Heredoc with trim not properly handled in function. Solution: Allow for missing indent. (FUJIWARA Takuya, closes vim/vim#4713) https://github.com/vim/vim/commit/ecaa75b4cea329a3902b8565e028b32279b8322b --- src/nvim/eval.c | 39 +++++++++++++++++++++++++-------------- src/nvim/testdir/test_let.vim | 9 +++++++++ 2 files changed, 34 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7db9386937..fd37e1001a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21297,8 +21297,6 @@ void ex_function(exarg_T *eap) bool overwrite = false; int indent; int nesting; - char_u *skip_until = NULL; - char_u *trimmed = NULL; dictitem_T *v; funcdict_T fudi; static int func_nr = 0; /* number for nameless function */ @@ -21308,6 +21306,9 @@ void ex_function(exarg_T *eap) hashitem_T *hi; linenr_T sourcing_lnum_off; linenr_T sourcing_lnum_top; + bool is_heredoc = false; + char_u *skip_until = NULL; + char_u *heredoc_trimmed = NULL; bool show_block = false; bool do_concat = true; @@ -21608,14 +21609,27 @@ void ex_function(exarg_T *eap) } if (skip_until != NULL) { - // Between ":append" and "." and between ":python < Date: Mon, 14 Oct 2019 14:44:18 +0900 Subject: vim-patch 8.1.0361: remote user not used for completion Problem: Remote user not used for completion. (Stucki) Solution: Use $USER too. (Dominique Pelle, closes #3407) https://github.com/vim/vim/commit/6b0b83f768cf536b34ce4d3f2de6bf62324229aa --- src/nvim/os/users.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index b24232b680..9374693550 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -17,6 +17,22 @@ # include #endif +// Add a user name to the list of users in garray_T *users. +// Do nothing if user name is NULL or empty. +static void add_user(garray_T *users, char *user, bool need_copy) +{ + char *user_copy = (user != NULL && need_copy) + ? xstrdup(user) : user; + + if (user_copy == NULL || *user_copy == NUL) { + if (need_copy) { + xfree(user); + } + return; + } + GA_APPEND(char *, users, user_copy); +} + // Initialize users garray and fill it with os usernames. // Return Ok for success, FAIL for failure. int os_get_usernames(garray_T *users) @@ -27,16 +43,15 @@ int os_get_usernames(garray_T *users) ga_init(users, sizeof(char *), 20); # if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H) - struct passwd *pw; + { + struct passwd *pw; - setpwent(); - while ((pw = getpwent()) != NULL) { - // pw->pw_name shouldn't be NULL but just in case... - if (pw->pw_name != NULL) { - GA_APPEND(char *, users, xstrdup(pw->pw_name)); + setpwent(); + while ((pw = getpwent()) != NULL) { + add_user(users, pw->pw_name, true); } + endpwent(); } - endpwent(); # elif defined(WIN32) { DWORD nusers = 0, ntotal = 0, i; @@ -51,13 +66,44 @@ int os_get_usernames(garray_T *users) EMSG2("utf16_to_utf8 failed: %d", conversion_result); break; } - GA_APPEND(char *, users, user); + add_user(users, user, false); } NetApiBufferFree(uinfo); } } # endif +# if defined(HAVE_GETPWNAM) + { + const char *user_env = os_getenv("USER"); + + // The $USER environment variable may be a valid remote user name (NIS, + // LDAP) not already listed by getpwent(), as getpwent() only lists + // local user names. If $USER is not already listed, check whether it + // is a valid remote user name using getpwnam() and if it is, add it to + // the list of user names. + + if (user_env != NULL && *user_env != NUL) { + int i; + + for (i = 0; i < users->ga_len; i++) { + char *local_user = ((char **)users->ga_data)[i]; + + if (STRCMP(local_user, user_env) == 0) { + break; + } + } + + if (i == users->ga_len) { + struct passwd *pw = getpwnam(user_env); // NOLINT + + if (pw != NULL) { + add_user(users, pw->pw_name, true); + } + } + } + } +# endif return OK; } -- cgit From d0efc1c9062441c9addc846429794ad4a06cc130 Mon Sep 17 00:00:00 2001 From: dm1try Date: Mon, 14 Oct 2019 14:58:41 +0300 Subject: mac: fix "tags file not sorted" bug on Catalina (#11222) I/O in Catalina is currently known to be broken. This commit works around a pesky bug and also makes the code more consistent by removing the mix of C file and standard I/O. Fixes https://github.com/neovim/neovim/issues/11196 --- src/nvim/tag.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 6fe3efbaae..1f70a10f28 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1508,11 +1508,11 @@ line_read_in: * compute the first offset. */ if (state == TS_BINARY) { - // Get the tag file size. - if ((filesize = vim_lseek(fileno(fp), (off_T)0L, SEEK_END)) <= 0) { + if (vim_fseek(fp, 0, SEEK_END) != 0) { state = TS_LINEAR; } else { - vim_lseek(fileno(fp), (off_T)0L, SEEK_SET); + filesize = vim_ftell(fp); + vim_fseek(fp, 0, SEEK_SET); /* Calculate the first read offset in the file. Start * the search in the middle of the file. */ -- cgit From 913d01bb03616f3bb7468490573a1579d62debbe Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 17 Oct 2019 09:44:10 +0200 Subject: vim-patch:8.1.2096: too many #ifdefs #11229 Problem: Too many #ifdefs. Solution: Graduate FEAT_COMMENTS. https://github.com/vim/vim/commit/8c96af9c05bfcac2d5ae081e098d4863db561511 Fixes https://github.com/vim/vim/issues/4972. --- src/nvim/edit.c | 43 ++++++++++++++++++++++--------------------- src/nvim/misc1.c | 2 -- src/nvim/ops.c | 40 ++++++++++++++++++++-------------------- 3 files changed, 42 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 16c4882975..49cf090962 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -5479,10 +5479,10 @@ insertchar ( if (c == NUL) /* only formatting was wanted */ return; - /* Check whether this character should end a comment. */ + // Check whether this character should end a comment. if (did_ai && c == end_comment_pending) { char_u *line; - char_u lead_end[COM_MAX_LEN]; /* end-comment string */ + char_u lead_end[COM_MAX_LEN]; // end-comment string int middle_len, end_len; int i; @@ -5490,39 +5490,40 @@ insertchar ( * Need to remove existing (middle) comment leader and insert end * comment leader. First, check what comment leader we can find. */ - i = get_leader_len(line = get_cursor_line_ptr(), &p, FALSE, TRUE); - if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { /* Just checking */ - /* Skip middle-comment string */ - while (*p && p[-1] != ':') /* find end of middle flags */ - ++p; + i = get_leader_len(line = get_cursor_line_ptr(), &p, false, true); + if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking + // Skip middle-comment string + while (*p && p[-1] != ':') { // find end of middle flags + p++; + } middle_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); - /* Don't count trailing white space for middle_len */ - while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) - --middle_len; + // Don't count trailing white space for middle_len + while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) { + middle_len--; + } - /* Find the end-comment string */ - while (*p && p[-1] != ':') /* find end of end flags */ - ++p; + // Find the end-comment string + while (*p && p[-1] != ':') { // find end of end flags + p++; + } end_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); - /* Skip white space before the cursor */ + // Skip white space before the cursor i = curwin->w_cursor.col; while (--i >= 0 && ascii_iswhite(line[i])) ; i++; - /* Skip to before the middle leader */ + // Skip to before the middle leader i -= middle_len; - /* Check some expected things before we go on */ + // Check some expected things before we go on if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) { - /* Backspace over all the stuff we want to replace */ + // Backspace over all the stuff we want to replace backspace_until_column(i); - /* - * Insert the end-comment string, except for the last - * character, which will get inserted as normal later. - */ + // Insert the end-comment string, except for the last + // character, which will get inserted as normal later. ins_bytes_len(lead_end, end_len - 1); } } diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index c1de7ab9a4..1db8a1fa11 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -792,8 +792,6 @@ int prompt_for_number(int *mouse_used) cmdline_row = msg_row - 1; } need_wait_return = false; - msg_didany = false; - msg_didout = false; } else { cmdline_row = save_cmdline_row; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 0d27365d2b..030782cbcc 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -270,20 +270,21 @@ void shift_line( int left, int round, int amount, - int call_changed_bytes /* call changed_bytes() */ + int call_changed_bytes // call changed_bytes() ) { int count; int i, j; int p_sw = get_sw_value(curbuf); - count = get_indent(); /* get current indent */ + count = get_indent(); // get current indent - if (round) { /* round off indent */ - i = count / p_sw; /* number of p_sw rounded down */ - j = count % p_sw; /* extra spaces */ - if (j && left) /* first remove extra spaces */ - --amount; + if (round) { // round off indent + i = count / p_sw; // number of p_sw rounded down + j = count % p_sw; // extra spaces + if (j && left) { // first remove extra spaces + amount--; + } if (left) { i -= amount; if (i < 0) @@ -291,7 +292,7 @@ void shift_line( } else i += amount; count = i * p_sw; - } else { /* original vi indent */ + } else { // original vi indent if (left) { count -= p_sw * amount; if (count < 0) @@ -300,11 +301,12 @@ void shift_line( count += p_sw * amount; } - /* Set new indent */ - if (State & VREPLACE_FLAG) - change_indent(INDENT_SET, count, FALSE, NUL, call_changed_bytes); - else + // Set new indent + if (State & VREPLACE_FLAG) { + change_indent(INDENT_SET, count, false, NUL, call_changed_bytes); + } else { (void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0); + } } /* @@ -4281,15 +4283,13 @@ int paragraph_start(linenr_T lnum) return TRUE; /* after empty line */ do_comments = has_format_option(FO_Q_COMS); - if (fmt_check_par(lnum - 1 - , &leader_len, &leader_flags, do_comments - )) - return TRUE; /* after non-paragraph line */ + if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { + return true; // after non-paragraph line + } - if (fmt_check_par(lnum - , &next_leader_len, &next_leader_flags, do_comments - )) - return TRUE; /* "lnum" is not a paragraph line */ + if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { + return true; // "lnum" is not a paragraph line + } if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) return TRUE; /* missing trailing space in previous line. */ -- cgit From 0785f8e8b11b2fa290cfbc0604d570f49b954ba6 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 17 Oct 2019 04:24:23 -0400 Subject: vim-patch:8.1.2140: "gk" and "gj" do not work correctly in number column #11208 Problem: "gk" and "gj" do not work correctly in number column. Solution: Allow for a negative "curswant". (Zach Wegner, closes vim/vim#4969) https://github.com/vim/vim/commit/ceba3dd5187788e09f65bd41b07b40f6f9aab953 --- src/nvim/cursor.c | 11 +++++++---- src/nvim/normal.c | 19 ++++++++++++++----- src/nvim/testdir/test_normal.vim | 25 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index f2b3cfe690..036ae32589 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -93,11 +93,12 @@ int coladvance(colnr_T wcol) static int coladvance2( pos_T *pos, - bool addspaces, /* change the text to achieve our goal? */ - bool finetune, /* change char offset for the exact column */ - colnr_T wcol /* column to move to */ + bool addspaces, // change the text to achieve our goal? + bool finetune, // change char offset for the exact column + colnr_T wcol_arg // column to move to (can be negative) ) { + colnr_T wcol = wcol_arg; int idx; char_u *ptr; char_u *line; @@ -165,6 +166,7 @@ static int coladvance2( if (virtual_active() && addspaces + && wcol >= 0 && ((col != wcol && col != wcol + 1) || csize > 1)) { /* 'virtualedit' is set: The difference between wcol and col is * filled with spaces. */ @@ -244,8 +246,9 @@ static int coladvance2( mark_mb_adjustpos(curbuf, pos); } - if (col < wcol) + if (wcol < 0 || col < wcol) { return FAIL; + } return OK; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e0dc9d4f23..d051ba33b8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3925,15 +3925,17 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1; else n = width1; - if (curwin->w_curswant > (colnr_T)n + 1) - curwin->w_curswant -= ((curwin->w_curswant - n) / width2 + 1) - * width2; + if (curwin->w_curswant >= n) { + curwin->w_curswant = n - 1; + } } while (dist--) { if (dir == BACKWARD) { - if (curwin->w_curswant > width2) { - // move back within line + if (curwin->w_curswant >= width1) { + // Move back within the line. This can give a negative value + // for w_curswant if width1 < width2 (with cpoptions+=n), + // which will get clipped to column 0. curwin->w_curswant -= width2; } else { // to previous line @@ -3973,6 +3975,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) } curwin->w_cursor.lnum++; curwin->w_curswant %= width2; + // Check if the cursor has moved below the number display + // when width1 < width2 (with cpoptions+=n). Subtract width2 + // to get a negative value for w_curswant, which will get + // clipped to column 0. + if (curwin->w_curswant >= width1) { + curwin->w_curswant -= width2; + } linelen = linetabsize(get_cursor_line_ptr()); } } diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index b967f84626..aeae6423d0 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2813,4 +2813,29 @@ func Test_normal_gk() call assert_equal(95, virtcol('.')) bw! bw! + + " needs 80 column new window + new + vert 80new + set number + set numberwidth=10 + set cpoptions+=n + put =[repeat('0',90), repeat('1',90)] + norm! 075l + call assert_equal(76, col('.')) + norm! gk + call assert_equal(1, col('.')) + norm! gk + call assert_equal(76, col('.')) + norm! gk + call assert_equal(1, col('.')) + norm! gj + call assert_equal(76, col('.')) + norm! gj + call assert_equal(1, col('.')) + norm! gj + call assert_equal(76, col('.')) + bw! + bw! + set cpoptions& number& numberwidth& endfunc -- cgit From 0e0d4a7b4c0a38c83282013b732c83d82b1844e9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 15 Oct 2019 20:35:47 -0400 Subject: vim-patch:8.1.2152: problems navigating tags file on MacOS Catalina Problem: Problems navigating tags file on MacOS Catalina. Solution: Use fseek instead of lseek. (John Lamb, fixes vim/vim#5061) https://github.com/vim/vim/commit/27fc8cab227e30f649f52e74efd58ad56d21e9bb --- src/nvim/tag.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 1f70a10f28..c8c9677a98 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1094,7 +1094,6 @@ find_tags ( int low_char; // first char at low_offset int high_char; // first char at high_offset } search_info; - off_T filesize; int tagcmp; off_T offset; int round; @@ -1503,19 +1502,21 @@ line_read_in: state = TS_LINEAR; } - /* - * When starting a binary search, get the size of the file and - * compute the first offset. - */ + // When starting a binary search, get the size of the file and + // compute the first offset. if (state == TS_BINARY) { if (vim_fseek(fp, 0, SEEK_END) != 0) { + // can't seek, don't use binary search state = TS_LINEAR; } else { - filesize = vim_ftell(fp); + // Get the tag file size. + // Don't use lseek(), it doesn't work + // properly on MacOS Catalina. + const off_T filesize = vim_ftell(fp); vim_fseek(fp, 0, SEEK_SET); - /* Calculate the first read offset in the file. Start - * the search in the middle of the file. */ + // Calculate the first read offset in the file. Start + // the search in the middle of the file. search_info.low_offset = 0; search_info.low_char = 0; search_info.high_offset = filesize; -- cgit From c54a7e586bc39ff5798b32641aefd320389a0303 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 16 Oct 2019 19:16:04 -0400 Subject: vim-patch:8.1.2161: mapping test fails Problem: Mapping test fails. Solution: Run the test separately. https://github.com/vim/vim/commit/4bd88d568a81d37df69dc3cf8cdd8d9dbb4011b7 --- src/nvim/testdir/test_alot.vim | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 6bf2e8329c..2c52452f6b 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -27,7 +27,6 @@ source test_jumps.vim source test_fileformat.vim source test_filetype.vim source test_lambda.vim -source test_mapping.vim source test_menu.vim source test_messages.vim source test_modeline.vim -- cgit From 7ba26ef3c01305334ccd84a78ef04d6f54e6b486 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 16 Oct 2019 19:11:03 -0400 Subject: vim-patch:8.1.2162: popup resize test is flaky Problem: Popup resize test is flaky. (Christian Brabandt) Solution: Add the function to the list of flaky tests. https://github.com/vim/vim/commit/4e03933726e3698d962bf7dacdd27f306a4c5086 --- src/nvim/testdir/runtest.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 8f5f3f82e7..49ec308db6 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -289,6 +289,7 @@ let s:flaky_tests = [ \ 'Test_oneshot()', \ 'Test_out_cb()', \ 'Test_paused()', + \ 'Test_popup_and_window_resize()', \ 'Test_quoteplus()', \ 'Test_quotestar()', \ 'Test_reltime()', -- cgit From 6c6abd11f75052d8bbc1a3ff279aab61ed0bca58 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 14 Oct 2019 17:55:07 -0400 Subject: vim-patch:8.1.2151: state test is a bit flaky Problem: State test is a bit flaky. Solution: Add to the list of flaky tests. https://github.com/vim/vim/commit/3c8cd4a1dcbc34d8818a2a38b1d1e4755da9edc2 --- src/nvim/testdir/runtest.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 49ec308db6..5c2e570adf 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -295,6 +295,7 @@ let s:flaky_tests = [ \ 'Test_reltime()', \ 'Test_repeat_many()', \ 'Test_repeat_three()', + \ 'Test_state()', \ 'Test_stop_all_in_callback()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', -- cgit From 1e4a9f9993a26a1495d1a3bdfd80fe079127ba83 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 18 Oct 2019 23:20:15 -0400 Subject: vim-patch:8.1.2175: meson files are not recognized Problem: Meson files are not recognized. Solution: Add the meson filetype. (Liam Beguin , Nirbheek Chauhan, closes vim/vim#5056) Also recognize hollywood. https://github.com/vim/vim/commit/c3bf7b56f2703e2d6f36dfb05fd32b5b43ce3c3f --- src/nvim/testdir/test_filetype.vim | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 7512d599b8..8e76046b13 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -201,6 +201,7 @@ let s:filename_checks = { \ 'hex': ['file.hex', 'file.h32'], \ 'hgcommit': ['hg-editor-file.txt'], \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], + \ 'hollywood': ['file.hws'], \ 'hostconf': ['/etc/host.conf'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'], \ 'template': ['file.tmpl'], @@ -273,6 +274,7 @@ let s:filename_checks = { \ 'mason': ['file.mason', 'file.mhtml', 'file.comp'], \ 'master': ['file.mas', 'file.master'], \ 'mel': ['file.mel'], + \ 'meson': ['meson.build', 'meson_options.txt'], \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', \ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err', -- cgit From 437fe261ab93e5b366fdcd095ccac7be1235b0eb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 19 Oct 2019 11:55:23 -0400 Subject: vim-patch:8.1.2177: Dart files are not recognized Problem: Dart files are not recognized. Solution: Add a filetype rule. (Eugene Ciurana, closes vim/vim#5087) https://github.com/vim/vim/commit/afbdb905c37675851e79d21239f502cd8e4ced9e --- src/nvim/testdir/test_filetype.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 8e76046b13..4053746c82 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -122,6 +122,7 @@ let s:filename_checks = { \ 'cvs': ['cvs123'], \ 'cvsrc': ['.cvsrc'], \ 'cynpp': ['file.cyn'], + \ 'dart': ['file.dart', 'file.drt'], \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], \ 'debcontrol': ['/debian/control'], -- cgit From d27fc0825732d575109ce7d149164e86d7b2cb98 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 19 Oct 2019 11:57:34 -0400 Subject: vim-patch:8.1.2178: accessing uninitialized memory in test Problem: Accessing uninitialized memory in test. Solution: Check if there was a match before using the match position. (Dominique Pelle, closes vim/vim#5088) https://github.com/vim/vim/commit/15ee567809a9808693163dd7c357ef0c172ecc9e --- src/nvim/search.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 1f382d31c5..fb31e76986 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4184,7 +4184,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, nmatched = vim_regexec_multi(®match, curwin, curbuf, pos.lnum, regmatch.startpos[0].col, NULL, NULL); - if (!nmatched) { + if (nmatched != 0) { break; } } while (direction == FORWARD @@ -4196,7 +4196,10 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum && regmatch.startpos[0].col == regmatch.endpos[0].col); // one char width - if (!result && inc(&pos) >= 0 && pos.col == regmatch.endpos[0].col) { + if (!result + && nmatched != 0 + && inc(&pos) >= 0 + && pos.col == regmatch.endpos[0].col) { result = true; } } -- cgit From 68b0873c458f4a0b5ca8483958994d338060202a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 19 Oct 2019 20:31:50 +0200 Subject: vim-patch:8.1.2182: test42 seen as binary by git diff #11256 Problem: Test42 seen as binary by git diff. Solution: Add .gitattributes file. Make explicit that 'cpo' does not contain 'S'. (Daniel Hahler, closes vim/vim#5072) https://github.com/vim/vim/commit/5b39d7adb0b9f02afe242f607d4c96250f06965d --- src/nvim/testdir/test42.in | Bin 2438 -> 2373 bytes 1 file changed, 0 insertions(+), 0 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test42.in b/src/nvim/testdir/test42.in index baa6e67d26..d9057e72fb 100644 Binary files a/src/nvim/testdir/test42.in and b/src/nvim/testdir/test42.in differ -- cgit From 3de4dc539ae938c5fdeddbdf25722fd1f6d9c77c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 19 Oct 2019 23:11:31 +0200 Subject: vim-patch:8.1.2180: Error E303 is not useful when 'directory' is empty (#11257) Problem: Error E303 is not useful when 'directory' is empty. Solution: Skip the error message. (Daniel Hahler, vim/vim#5067) https://github.com/vim/vim/commit/00e192becd50a38cb21a1bc3f86fcc7a21f8ee88 --- src/nvim/memline.c | 6 +++--- src/nvim/testdir/test_recover.vim | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/memline.c b/src/nvim/memline.c index f1d6ee064c..b85c23e50f 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -523,9 +523,9 @@ void ml_open_file(buf_T *buf) } } - if (mfp->mf_fname == NULL) { /* Failed! */ - need_wait_return = TRUE; /* call wait_return later */ - ++no_wait_return; + if (*p_dir != NUL && mfp->mf_fname == NULL) { + need_wait_return = true; // call wait_return later + no_wait_return++; (void)EMSG2(_( "E303: Unable to open swap file for \"%s\", recovery impossible"), buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname); diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index 09c8d1cda6..fc073cacd2 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -14,6 +14,12 @@ func Test_recover_root_dir() set dir=/notexist/ endif call assert_fails('split Xtest', 'E303:') + + " No error with empty 'directory' setting. + set directory= + split XtestOK + close! + set dir& endfunc -- cgit From 93fe30593b47fe98a31c6bb67f4d6effb8b725fe Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 19 Oct 2019 23:45:27 +0200 Subject: ex_echo: fix check for got_int #11225 It needs to return to not output any remaining parts. Followup to https://github.com/neovim/neovim/pull/10926 Ref: https://github.com/neovim/neovim/issues/10923 --- src/nvim/message.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/message.c b/src/nvim/message.c index b518664f32..03cbe8ec18 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -233,7 +233,10 @@ void msg_multiline_attr(const char *s, int attr, bool check_int) { const char *next_spec = s; - while (next_spec != NULL && (!check_int || !got_int)) { + while (next_spec != NULL) { + if (check_int && got_int) { + return; + } next_spec = strpbrk(s, "\t\n\r"); if (next_spec != NULL) { -- cgit From 6fd6f4683d19d5ca18f48c1d1f0d87113e80368e Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 21 Oct 2019 07:47:08 +0900 Subject: TUI/thread: guard env map from potential race with unibilium #11259 unibi_from_term calls getenv internally, so exclusive control is required. --- src/nvim/os/env.c | 10 ++++++++++ src/nvim/tui/tui.c | 2 ++ 2 files changed, 12 insertions(+) (limited to 'src') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index ae61e54993..eb86cb8ac7 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -44,6 +44,16 @@ void env_init(void) uv_mutex_init(&mutex); } +void os_env_var_lock(void) +{ + uv_mutex_lock(&mutex); +} + +void os_env_var_unlock(void) +{ + uv_mutex_unlock(&mutex); +} + /// Like getenv(), but returns NULL if the variable is empty. /// @see os_env_exists const char *os_getenv(const char *name) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 150862bb18..945b093f32 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -234,7 +234,9 @@ static void terminfo_start(UI *ui) // Set up unibilium/terminfo. char *termname = NULL; if (term) { + os_env_var_lock(); data->ut = unibi_from_term(term); + os_env_var_unlock(); if (data->ut) { termname = xstrdup(term); } -- cgit From 2e4465e0585053bddaf8e6e60dc50bf1be0ba54e Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Mon, 21 Oct 2019 02:17:25 +0200 Subject: vim-patch:8.1.2168: heredoc not skipped in if-block #11265 Problem: Heredoc assignment not skipped in if block. Solution: Check if "skip" is set. https://github.com/vim/vim/commit/b1ba9abcb385b0a5355788a7eefef78ec68d2f65 Fixes https://github.com/neovim/neovim/issues/11264 --- src/nvim/eval.c | 10 ++++++---- src/nvim/testdir/test_let.vim | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fd37e1001a..71ffb26cc2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1675,10 +1675,12 @@ static void ex_let_const(exarg_T *eap, const bool is_const) list_T *l = heredoc_get(eap, expr + 3); if (l != NULL) { tv_list_set_ret(&rettv, l); - op[0] = '='; - op[1] = NUL; - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, - is_const, op); + if (!eap->skip) { + op[0] = '='; + op[1] = NUL; + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); + } tv_clear(&rettv); } } else { diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index b5af871ab2..1fce3d6937 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -276,4 +276,12 @@ E app END call assert_equal(['something', 'app'], var1) + + let check = [] + if 0 + let check =<< trim END + from heredoc + END + endif + call assert_equal([], check) endfunc -- cgit From f4cbe96488fce4de971be2e25b254320f0fa71b2 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 20 Oct 2019 05:59:29 -0400 Subject: vim-patch:8.1.2185: syntax test fails Problem: Syntax test fails. Solution: Add missing file patch. https://github.com/vim/vim/commit/bbfd1562aeaa5b40b6451effc399846b692d6992 --- src/nvim/testdir/test_syntax.vim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index b9310e2168..c2025b36e0 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -519,6 +519,7 @@ func Test_syntax_c() \ ' for (int i = 0; i < count; ++i) {', \ ' break;', \ ' }', + \ " Note: asdf", \ '}', \ ], 'Xtest.c') @@ -526,7 +527,8 @@ func Test_syntax_c() " response to t_RB corrects it to "light". let $COLORFGBG = '15;0' - let buf = RunVimInTerminal('Xtest.c', {}) + let buf = RunVimInTerminal('Xtest.c', #{rows: 22}) + call term_sendkeys(buf, ":syn keyword Search Note\r") call VerifyScreenDump(buf, 'Test_syntax_c_01', {}) call StopVimInTerminal(buf) -- cgit From 60415a5d3a0fec6fc42a900aba15943b3d1730cd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 20 Oct 2019 06:04:07 -0400 Subject: vim-patch:8.1.2188: build error for missing define Problem: Build error for missing define. Solution: Add missing change. https://github.com/vim/vim/commit/2b78ab5d0c91c229715ae140a34978506343bde3 These "WILD_" macros are used in earlier vim patches. --- src/nvim/ex_getln.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 051564fbe1..99d5a7786d 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -27,6 +27,8 @@ #define WILD_ESCAPE 0x80 #define WILD_ICASE 0x100 #define WILD_ALLLINKS 0x200 +#define WILD_IGNORE_COMPLETESLASH 0x400 +#define WILD_NOERROR 0x800 // sets EW_NOERROR /// Present history tables typedef enum { -- cgit From 13a6878d187612721baecede181e7dfdc3699a59 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 20 Oct 2019 16:39:48 -0400 Subject: vim-patch:8.1.2190: syntax test fails on Mac Problem: Syntax test fails on Mac. Solution: Limit the window size to 20 rows. https://github.com/vim/vim/commit/83e9a1ce75818a78c5ddf8dcfb820634ca6fabff --- src/nvim/testdir/test_syntax.vim | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index c2025b36e0..6cada1503f 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -502,9 +502,7 @@ func Test_syntax_c() endif call writefile([ \ '/* comment line at the top */', - \ ' int', - \ 'main(int argc, char **argv)// another comment', - \ '{', + \ 'int main(int argc, char **argv) { // another comment', \ '#if 0', \ ' int not_used;', \ '#else', @@ -527,7 +525,7 @@ func Test_syntax_c() " response to t_RB corrects it to "light". let $COLORFGBG = '15;0' - let buf = RunVimInTerminal('Xtest.c', #{rows: 22}) + let buf = RunVimInTerminal('Xtest.c', {}) call term_sendkeys(buf, ":syn keyword Search Note\r") call VerifyScreenDump(buf, 'Test_syntax_c_01', {}) call StopVimInTerminal(buf) -- cgit From c067efa696698d455d9a1488c26e0fb5d8cb5bf5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 20 Oct 2019 20:12:08 -0400 Subject: vim-patch:8.1.2197: ExitPre autocommand may cause accessing freed memory Problem: ExitPre autocommand may cause accessing freed memory. Solution: Check the window pointer is still valid. (closes vim/vim#5093) https://github.com/vim/vim/commit/34ba06b6e6f94bb46062e6c85dbfdcbb0d255ada --- src/nvim/ex_docmd.c | 8 +++++--- src/nvim/testdir/test_exit.vim | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0da2cd67d6..ae3fb4fbfb 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6070,9 +6070,11 @@ static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit) if (quit_all || (check_more(false, forceit) == OK && only_one_window())) { apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf); - // Refuse to quit when locked or when the buffer in the last window is - // being closed (can only happen in autocommands). - if (curbuf_locked() + // Refuse to quit when locked or when the window was closed or the + // buffer in the last window is being closed (can only happen in + // autocommands). + if (!win_valid(wp) + || curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) { return true; } diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index 3797626abf..99a401d4a4 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -40,6 +40,7 @@ func Test_exiting() endif call delete('Xtestout') + " ExitPre autocommand splits the window, so that it's no longer the last one. let after =<< trim [CODE] au QuitPre * call writefile(["QuitPre"], "Xtestout", "a") au ExitPre * call writefile(["ExitPre"], "Xtestout", "a") @@ -58,4 +59,25 @@ func Test_exiting() \ readfile('Xtestout')) endif call delete('Xtestout') + + " ExitPre autocommand splits and closes the window, so that there is still + " one window but it's a different one. + let after =<< trim [CODE] + au QuitPre * call writefile(["QuitPre"], "Xtestout", "a") + au ExitPre * call writefile(["ExitPre"], "Xtestout", "a") + augroup nasty + au ExitPre * split | only + augroup END + quit + augroup nasty + au! ExitPre + augroup END + quit + [CODE] + + if RunVim([], after, '') + call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'], + \ readfile('Xtestout')) + endif + call delete('Xtestout') endfunc -- cgit From e284b7233fb459a7a6d4ce0f98371b34b3639d2b Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 22 Oct 2019 19:55:55 +0100 Subject: Perform HASHTAB_ITER bookkeeping before user-code The `HASHTAB_ITER` logic keeps track of how many entries in the hash table are left to visit, decrementing this on each iteration of the loop. This was previously decremented at the end of the loop body: ```c size_t hi##todo_ = hi##ht_->ht_used; for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { if (!HASHITEM_EMPTY(hi)) { { } hi##todo_--; // <--- important decrement here } } ``` This meant that if the body of the loop (substituted in via macro expansion) contained a `continue` statement, we'd skip decrementing our counter, meaning we'd iterate too many times over the hash table, usually leading to an out of bounds read beyond the hash table's memory, or uninitialised/null pointers from unused hash table slots. Decrementing `hi##todo` before the arbitrary loop body protects us from this, and has no adverse side-effects since only the macro code can (or should) use this variable. Before this commit, no code within `HASHTAB_ITER()` contained a `continue`, meaning this bug was left dormant and the fix has a very minimal chance of introducing any bugs. --- src/nvim/hashtab.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index a70a8bea63..19633d455f 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -81,10 +81,10 @@ typedef struct hashtable_S { size_t hi##todo_ = hi##ht_->ht_used; \ for (hashitem_T *hi = hi##ht_->ht_array; hi##todo_; hi++) { \ if (!HASHITEM_EMPTY(hi)) { \ + hi##todo_--; \ { \ code \ } \ - hi##todo_--; \ } \ } \ } while (0) -- cgit From 194f7bfacea934177d524197127242947bd28471 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 10 Oct 2019 22:06:45 +0100 Subject: vim-patch:8.1.1228: not possible to process tags with a function Problem: Not possible to process tags with a function. Solution: Add tagfunc() (Christian Brabandt, Andy Massimino, closes vim/vim#4010) https://github.com/vim/vim/commit/45e18cbdc40afd8144d20dcc07ad2d981636f4c9 --- src/nvim/buffer.c | 1 + src/nvim/buffer_defs.h | 10 +- src/nvim/edit.c | 2 + src/nvim/ex_cmds.c | 2 +- src/nvim/globals.h | 6 +- src/nvim/normal.c | 2 + src/nvim/option.c | 5 + src/nvim/option_defs.h | 1 + src/nvim/options.lua | 8 + src/nvim/tag.c | 386 +++++++++++++++++++++++++++++++++----- src/nvim/tag.h | 27 +-- src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_tagfunc.vim | 84 +++++++++ src/nvim/window.c | 16 +- 14 files changed, 482 insertions(+), 69 deletions(-) create mode 100644 src/nvim/testdir/test_tagfunc.vim (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index b81ffd09e1..1d5aa8ba9b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1947,6 +1947,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_path); clear_string_option(&buf->b_p_tags); clear_string_option(&buf->b_p_tc); + clear_string_option(&buf->b_p_tfu); clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_qe); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 16c7804be0..ca740dea21 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -119,10 +119,11 @@ typedef uint16_t disptick_T; // display tick type * The taggy struct is used to store the information about a :tag command. */ typedef struct taggy { - char_u *tagname; /* tag name */ - fmark_T fmark; /* cursor position BEFORE ":tag" */ - int cur_match; /* match number */ - int cur_fnum; /* buffer number used for cur_match */ + char_u *tagname; // tag name + fmark_T fmark; // cursor position BEFORE ":tag" + int cur_match; // match number + int cur_fnum; // buffer number used for cur_match + char_u *user_data; // used with tagfunc } taggy_T; typedef struct buffblock buffblock_T; @@ -647,6 +648,7 @@ struct file_buffer { char_u *b_p_cpt; ///< 'complete' char_u *b_p_cfu; ///< 'completefunc' char_u *b_p_ofu; ///< 'omnifunc' + char_u *b_p_tfu; ///< 'tagfunc' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 16c4882975..a4b4e0d980 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4047,12 +4047,14 @@ static int ins_compl_get_exp(pos_T *ini) // Find up to TAG_MANY matches. Avoids that an enormous number // of matches is found when compl_pattern is empty + g_tag_at_cursor = true; if (find_tags(compl_pattern, &num_matches, &matches, TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP | (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } + g_tag_at_cursor = false; p_ic = save_p_ic; break; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 00de7a3d66..2e8bd79c81 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4905,7 +4905,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, *matches = NULL; *num_matches = 0; - int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; + int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; if (keep_lang) { flags |= TAG_KEEP_LANG; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 5237c621f9..c3d1a4d40b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -787,7 +787,11 @@ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */ EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands: height of preview window */ -EXTERN int replace_offset INIT(= 0); /* offset for replace_push() */ +EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes + // from the command line (0) or was + // invoked as a normal command (1) + +EXTERN int replace_offset INIT(= 0); // offset for replace_push() EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); /* need backslash in cmd line */ diff --git a/src/nvim/normal.c b/src/nvim/normal.c index e0dc9d4f23..28183ffa1d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4929,7 +4929,9 @@ static void nv_ident(cmdarg_T *cap) add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); } else { + g_tag_at_cursor = true; do_cmdline_cmd(buf); + g_tag_at_cursor = false; } xfree(buf); diff --git a/src/nvim/option.c b/src/nvim/option.c index 22f7b85133..20351d3908 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -133,6 +133,7 @@ static char_u *p_cms; static char_u *p_cpt; static char_u *p_cfu; static char_u *p_ofu; +static char_u *p_tfu; static int p_eol; static int p_fixeol; static int p_et; @@ -2273,6 +2274,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_ep); check_string_option(&buf->b_p_path); check_string_option(&buf->b_p_tags); + check_string_option(&buf->b_p_tfu); check_string_option(&buf->b_p_tc); check_string_option(&buf->b_p_dict); check_string_option(&buf->b_p_tsr); @@ -5590,6 +5592,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_INC: return (char_u *)&(curbuf->b_p_inc); case PV_DICT: return (char_u *)&(curbuf->b_p_dict); case PV_TSR: return (char_u *)&(curbuf->b_p_tsr); + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_STL: return (char_u *)&(curwin->w_p_stl); case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); @@ -5742,6 +5745,7 @@ static char_u *get_varp(vimoption_T *p) case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf); case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl); case PV_SW: return (char_u *)&(curbuf->b_p_sw); + case PV_TFU: return (char_u *)&(curbuf->b_p_tfu); case PV_TS: return (char_u *)&(curbuf->b_p_ts); case PV_TW: return (char_u *)&(curbuf->b_p_tw); case PV_UDF: return (char_u *)&(curbuf->b_p_udf); @@ -6004,6 +6008,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_cpt = vim_strsave(p_cpt); buf->b_p_cfu = vim_strsave(p_cfu); buf->b_p_ofu = vim_strsave(p_ofu); + buf->b_p_tfu = vim_strsave(p_tfu); buf->b_p_sts = p_sts; buf->b_p_sts_nopaste = p_sts_nopaste; buf->b_p_com = vim_strsave(p_com); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 67cb53ce02..e5a3c0bd95 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -780,6 +780,7 @@ enum { , BV_SUA , BV_SW , BV_SWF + , BV_TFU , BV_TAGS , BV_TC , BV_TS diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 52e788944b..e96b3f8e02 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2388,6 +2388,14 @@ return { varname='p_syn', defaults={if_true={vi=""}} }, + { + full_name='tagfunc', abbreviation='tfu', + type='string', scope={'buffer'}, + vim=true, + vi_def=true, + varname='p_tfu', + defaults={if_true={vi=""}} + }, { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 880c467d30..872fc0f279 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -51,17 +51,20 @@ * Structure to hold pointers to various items in a tag line. */ typedef struct tag_pointers { - /* filled in by parse_tag_line(): */ - char_u *tagname; /* start of tag name (skip "file:") */ - char_u *tagname_end; /* char after tag name */ - char_u *fname; /* first char of file name */ - char_u *fname_end; /* char after file name */ - char_u *command; /* first char of command */ - /* filled in by parse_match(): */ - char_u *command_end; /* first char after command */ - char_u *tag_fname; /* file name of the tags file */ - char_u *tagkind; /* "kind:" value */ - char_u *tagkind_end; /* end of tagkind */ + // filled in by parse_tag_line(): + char_u *tagname; // start of tag name (skip "file:") + char_u *tagname_end; // char after tag name + char_u *fname; // first char of file name + char_u *fname_end; // char after file name + char_u *command; // first char of command + // filled in by parse_match(): + char_u *command_end; // first char after command + char_u *tag_fname; // file name of the tags file. This is used + // when 'tr' is set. + char_u *tagkind; // "kind:" value + char_u *tagkind_end; // end of tagkind + char_u *user_data; // user_data string + char_u *user_data_end; // end of user_data } tagptrs_T; /* @@ -102,6 +105,10 @@ static char_u *nofile_fname = NULL; /* fname for NOTAGFILE error */ static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack"); static char_u *topmsg = (char_u *)N_("E556: at top of tag stack"); +static char_u *recurmsg + = (char_u *)N_("E986: cannot modify the tag stack within tagfunc"); +static char_u *tfu_inv_ret_msg + = (char_u *)N_("E987: invalid return value from tagfunc"); static char_u *tagmatchname = NULL; /* name of last used tag */ @@ -109,7 +116,12 @@ static char_u *tagmatchname = NULL; /* name of last used tag */ * Tag for preview window is remembered separately, to avoid messing up the * normal tagstack. */ -static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; +static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL }; + +static int tfu_in_use = false; // disallow recursive call of tagfunc + +// Used instead of NUL to separate tag fields in the growarrays. +#define TAG_SEP 0x02 /* * Jump to tag; handling of tag commands and tag stack @@ -161,6 +173,7 @@ do_tag( int use_tagstack; int skip_msg = false; char_u *buf_ffname = curbuf->b_ffname; // name for priority computation + int use_tfu = 1; /* remember the matches for the last used tag */ static int num_matches = 0; @@ -168,6 +181,11 @@ do_tag( static char_u **matches = NULL; static int flags; + if (tfu_in_use) { + EMSG(_(recurmsg)); + return false; + } + #ifdef EXITFREE if (type == DT_FREE) { /* remove the list of matches */ @@ -181,6 +199,7 @@ do_tag( if (type == DT_HELP) { type = DT_TAG; no_regexp = true; + use_tfu = 0; } prev_num_matches = num_matches; @@ -196,7 +215,7 @@ do_tag( use_tagstack = false; new_tag = true; if (g_do_tagpreview != 0) { - xfree(ptag_entry.tagname); + tagstack_clear_entry(&ptag_entry); ptag_entry.tagname = vim_strsave(tag); } } else { @@ -220,7 +239,7 @@ do_tag( cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { - xfree(ptag_entry.tagname); + tagstack_clear_entry(&ptag_entry); ptag_entry.tagname = vim_strsave(tag); } } else { @@ -228,16 +247,18 @@ do_tag( * If the last used entry is not at the top, delete all tag * stack entries above it. */ - while (tagstackidx < tagstacklen) - xfree(tagstack[--tagstacklen].tagname); + while (tagstackidx < tagstacklen) { + tagstack_clear_entry(&tagstack[--tagstacklen]); + } /* if the tagstack is full: remove oldest entry */ if (++tagstacklen > TAGSTACKSIZE) { tagstacklen = TAGSTACKSIZE; - xfree(tagstack[0].tagname); - for (i = 1; i < tagstacklen; ++i) + tagstack_clear_entry(&tagstack[0]); + for (i = 1; i < tagstacklen; i++) { tagstack[i - 1] = tagstack[i]; - --tagstackidx; + } + tagstackidx--; } // put the tag name in the tag stack @@ -447,15 +468,22 @@ do_tag( } else flags = TAG_NOIC; - if (type == DT_CSCOPE) + if (type == DT_CSCOPE) { flags = TAG_CSCOPE; - if (verbose) + } + if (verbose) { flags |= TAG_VERBOSE; + } + if (!use_tfu) { + flags |= TAG_NO_TAGFUNC; + } + if (find_tags(name, &new_num_matches, &new_matches, flags, - max_num_matches, buf_ffname) == OK - && new_num_matches < max_num_matches) - max_num_matches = MAXCOL; /* If less than max_num_matches - found: all matches found. */ + max_num_matches, buf_ffname) == OK + && new_num_matches < max_num_matches) { + max_num_matches = MAXCOL; // If less than max_num_matches + // found: all matches found. + } /* If there already were some matches for the same name, move them * to the start. Avoids that the order changes when using @@ -543,9 +571,20 @@ do_tag( cur_match = num_matches - 1; } if (use_tagstack) { + tagptrs_T tagp2; + tagstack[tagstackidx].cur_match = cur_match; tagstack[tagstackidx].cur_fnum = cur_fnum; - ++tagstackidx; + + // store user-provided data originating from tagfunc + if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK + && tagp2.user_data) { + XFREE_CLEAR(tagstack[tagstackidx].user_data); + tagstack[tagstackidx].user_data = vim_strnsave( + tagp2.user_data, tagp2.user_data_end - tagp2.user_data); + } + + tagstackidx++; } else if (g_do_tagpreview != 0) { ptag_entry.cur_match = cur_match; ptag_entry.cur_fnum = cur_fnum; @@ -1083,6 +1122,220 @@ static void prepare_pats(pat_T *pats, int has_re) pats->regmatch.regprog = NULL; } +// +// Call the user-defined function to generate a list of tags used by +// find_tags(). +// +// Return OK if at least 1 tag has been successfully found, +// NOTDONE if the function returns v:null, and FAIL otherwise. +// +static int find_tagfunc_tags( + char_u *pat, // pattern supplied to the user-defined function + garray_T *ga, // the tags will be placed here + int *match_count, // here the number of tags found will be placed + int flags, // flags from find_tags (TAG_*) + char_u *buf_ffname) // name of buffer for priority +{ + pos_T save_pos; + list_T *taglist; + int ntags = 0; + int result = FAIL; + typval_T args[4]; + typval_T rettv; + char_u flagString[3]; + dict_T *d; + taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; + + if (*curbuf->b_p_tfu == NUL) { + return FAIL; + } + + args[0].v_type = VAR_STRING; + args[0].vval.v_string = pat; + args[1].v_type = VAR_STRING; + args[1].vval.v_string = flagString; + + // create 'info' dict argument + d = tv_dict_alloc(); + if (tag->user_data != NULL) { + tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data); + } + if (buf_ffname != NULL) { + tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname); + } + + d->dv_refcount++; + args[2].v_type = VAR_DICT; + args[2].vval.v_dict = d; + + args[3].v_type = VAR_UNKNOWN; + + vim_snprintf((char *)flagString, sizeof(flagString), + "%s%s", + g_tag_at_cursor ? "c": "", + flags & TAG_INS_COMP ? "i": ""); + + save_pos = curwin->w_cursor; + result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); + curwin->w_cursor = save_pos; // restore the cursor position + d->dv_refcount--; + + if (result == FAIL) { + return FAIL; + } + if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) { + tv_clear(&rettv); + return NOTDONE; + } + if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) { + tv_clear(&rettv); + EMSG(_(tfu_inv_ret_msg)); + return FAIL; + } + taglist = rettv.vval.v_list; + + TV_LIST_ITER_CONST(taglist, li, { + char_u *mfp; + char_u *res_name; + char_u *res_fname; + char_u *res_cmd; + char_u *res_kind; + int len; + int has_extra = 0; + int name_only = flags & TAG_NAMES; + + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + EMSG(_(tfu_inv_ret_msg)); + break; + } + + len = 2; + res_name = NULL; + res_fname = NULL; + res_cmd = NULL; + res_kind = NULL; + + TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { + const char_u *dict_key = di->di_key; + typval_T *tv = &di->di_tv; + + if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { + continue; + } + + len += STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE" + if (!STRCMP(dict_key, "name")) { + res_name = tv->vval.v_string; + continue; + } + if (!STRCMP(dict_key, "filename")) { + res_fname = tv->vval.v_string; + continue; + } + if (!STRCMP(dict_key, "cmd")) { + res_cmd = tv->vval.v_string; + continue; + } + has_extra = 1; + if (!STRCMP(dict_key, "kind")) { + res_kind = tv->vval.v_string; + continue; + } + // Other elements will be stored as "\tKEY:VALUE" + // Allocate space for the key and the colon + len += STRLEN(dict_key) + 1; + }); + + if (has_extra) { + len += 2; // need space for ;" + } + + if (!res_name || !res_fname || !res_cmd) { + EMSG(_(tfu_inv_ret_msg)); + break; + } + + if (name_only) { + mfp = vim_strsave(res_name); + } else { + mfp = (char_u *)xmalloc((int)sizeof(char_u) + len + 1); + } + + if (mfp == NULL) { + continue; + } + + if (!name_only) { + char_u *p = mfp; + + *p++ = MT_GL_OTH + 1; // mtt + *p++ = TAG_SEP; // no tag file name + + STRCPY(p, res_name); + p += STRLEN(p); + + *p++ = TAB; + STRCPY(p, res_fname); + p += STRLEN(p); + + *p++ = TAB; + STRCPY(p, res_cmd); + p += STRLEN(p); + + if (has_extra) { + STRCPY(p, ";\""); + p += STRLEN(p); + + if (res_kind) { + *p++ = TAB; + STRCPY(p, res_kind); + p += STRLEN(p); + } + + TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { + const char_u *dict_key = di->di_key; + typval_T *tv = &di->di_tv; + if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { + continue; + } + + if (!STRCMP(dict_key, "name")) { + continue; + } + if (!STRCMP(dict_key, "filename")) { + continue; + } + if (!STRCMP(dict_key, "cmd")) { + continue; + } + if (!STRCMP(dict_key, "kind")) { + continue; + } + + *p++ = TAB; + STRCPY(p, dict_key); + p += STRLEN(p); + STRCPY(p, ":"); + p += STRLEN(p); + STRCPY(p, tv->vval.v_string); + p += STRLEN(p); + }); + } + } + + // Add all matches because tagfunc should do filtering. + ga_grow(ga, 1); + ((char_u **)(ga->ga_data))[ga->ga_len++] = mfp; + ntags++; + result = OK; + }); + + tv_clear(&rettv); + + *match_count = ntags; + return result; +} + /* * find_tags() - search for tags in tags files * @@ -1108,6 +1361,7 @@ static void prepare_pats(pat_T *pats, int has_re) * TAG_NOIC don't always ignore case * TAG_KEEP_LANG keep language * TAG_CSCOPE use cscope results for tags + * TAG_NO_TAGFUNC do not call the 'tagfunc' function */ int find_tags( @@ -1198,6 +1452,7 @@ find_tags( int get_it_again = FALSE; int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); + int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); int save_p_ic = p_ic; // Change the value of 'ignorecase' according to 'tagcase' for the @@ -1275,6 +1530,16 @@ find_tags( // uninitialised. memset(&search_info, 0, 1); // -V512 + if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { + tfu_in_use = true; + retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, + flags, buf_ffname); + tfu_in_use = false; + if (retval != NOTDONE) { + goto findtag_end; + } + } + /* * When finding a specified number of matches, first try with matching * case, so binary search can be used, and try ignore-case matches in a @@ -1856,7 +2121,6 @@ parse_line: } } } else { -#define TAG_SEP 0x02 size_t tag_fname_len = STRLEN(tag_fname); // Save the tag in a buffer. // Use 0x02 to separate fields (Can't use NUL, because the @@ -2040,9 +2304,7 @@ void free_tag_stuff(void) do_tag(NULL, DT_FREE, 0, 0, 0); tag_freematch(); - if (ptag_entry.tagname) { - XFREE_CLEAR(ptag_entry.tagname); - } + tagstack_clear_entry(&ptag_entry); } #endif @@ -2283,6 +2545,7 @@ parse_match( tagp); tagp->tagkind = NULL; + tagp->user_data = NULL; tagp->command_end = NULL; if (retval == OK) { @@ -2300,13 +2563,17 @@ parse_match( while (ASCII_ISALPHA(*p)) { if (STRNCMP(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; + } else if (STRNCMP(p, "user_data:", 10) == 0) { + tagp->user_data = p + 10; + } + if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; } + pc = vim_strchr(p, ':'); pt = vim_strchr(p, '\t'); if (pc == NULL || (pt != NULL && pc > pt)) { tagp->tagkind = p; - break; } if (pt == NULL) break; @@ -2320,6 +2587,12 @@ parse_match( ; tagp->tagkind_end = p; } + if (tagp->user_data != NULL) { + for (p = tagp->user_data; + *p && *p != '\t' && *p != '\r' && *p != '\n'; p++) { + } + tagp->user_data_end = p; + } } return retval; } @@ -2770,6 +3043,15 @@ static int find_extra(char_u **pp) return FAIL; } +// +// Free a single entry in a tag stack +// +static void tagstack_clear_entry(taggy_T *item) +{ + XFREE_CLEAR(item->tagname); + XFREE_CLEAR(item->user_data); +} + int expand_tags ( int tagnames, /* expand tag names */ @@ -2789,14 +3071,16 @@ expand_tags ( tagnmflag = TAG_NAMES; else tagnmflag = 0; - if (pat[0] == '/') + if (pat[0] == '/') { ret = find_tags(pat + 1, num_file, file, - TAG_REGEXP | tagnmflag | TAG_VERBOSE, - TAG_MANY, curbuf->b_ffname); - else + TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NO_TAGFUNC, + TAG_MANY, curbuf->b_ffname); + } else { ret = find_tags(pat, num_file, file, - TAG_REGEXP | tagnmflag | TAG_VERBOSE | TAG_NOIC, - TAG_MANY, curbuf->b_ffname); + TAG_REGEXP | tagnmflag | TAG_VERBOSE + | TAG_NO_TAGFUNC | TAG_NOIC, + TAG_MANY, curbuf->b_ffname); + } if (ret == OK && !tagnames) { /* Reorganize the tags for display and matching as strings of: * "\0\0\0" @@ -2958,6 +3242,9 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict) tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname); tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1); tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum); + if (tag->user_data) { + tv_dict_add_str(retdict, S_LEN("user_data"), (const char *)tag->user_data); + } pos = tv_list_alloc(4); tv_dict_add_list(retdict, S_LEN("from"), pos); @@ -2996,7 +3283,7 @@ static void tagstack_clear(win_T *wp) { // Free the current tag stack for (int i = 0; i < wp->w_tagstacklen; i++) { - xfree(wp->w_tagstack[i].tagname); + tagstack_clear_entry(&wp->w_tagstack[i]); } wp->w_tagstacklen = 0; wp->w_tagstackidx = 0; @@ -3007,7 +3294,7 @@ static void tagstack_clear(win_T *wp) static void tagstack_shift(win_T *wp) { taggy_T *tagstack = wp->w_tagstack; - xfree(tagstack[0].tagname); + tagstack_clear_entry(&tagstack[0]); for (int i = 1; i < wp->w_tagstacklen; i++) { tagstack[i - 1] = tagstack[i]; } @@ -3021,7 +3308,8 @@ static void tagstack_push_item( int cur_fnum, int cur_match, pos_T mark, - int fnum) + int fnum, + char_u *user_data) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack @@ -3041,6 +3329,7 @@ static void tagstack_push_item( } tagstack[idx].fmark.mark = mark; tagstack[idx].fmark.fnum = fnum; + tagstack[idx].user_data = user_data; } // Add a list of items to the tag stack in the specified window @@ -3076,10 +3365,13 @@ static void tagstack_push_items(win_T *wp, list_T *l) if (mark.col > 0) { mark.col--; } - tagstack_push_item(wp, tagname, - (int)tv_dict_get_number(itemdict, "bufnr"), - (int)tv_dict_get_number(itemdict, "matchnr") - 1, - mark, fnum); + tagstack_push_item( + wp, + tagname, + (int)tv_dict_get_number(itemdict, "bufnr"), + (int)tv_dict_get_number(itemdict, "matchnr") - 1, + mark, fnum, + (char_u *)tv_dict_get_string(itemdict, "user_data", true)); } } @@ -3103,6 +3395,12 @@ int set_tagstack(win_T *wp, dict_T *d, int action) dictitem_T *di; list_T *l; + // not allowed to alter the tag stack entries from inside tagfunc + if (tfu_in_use) { + EMSG(_(recurmsg)); + return FAIL; + } + if ((di = tv_dict_find(d, "items", -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) { return FAIL; diff --git a/src/nvim/tag.h b/src/nvim/tag.h index a8fddd05da..9f671043b3 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -20,20 +20,21 @@ #define DT_LTAG 11 /* tag using location list */ #define DT_FREE 99 /* free cached matches */ -/* - * flags for find_tags(). - */ -#define TAG_HELP 1 /* only search for help tags */ -#define TAG_NAMES 2 /* only return name of tag */ -#define TAG_REGEXP 4 /* use tag pattern as regexp */ -#define TAG_NOIC 8 /* don't always ignore case */ -#define TAG_CSCOPE 16 /* cscope tag */ -#define TAG_VERBOSE 32 /* message verbosity */ -#define TAG_INS_COMP 64 /* Currently doing insert completion */ -#define TAG_KEEP_LANG 128 /* keep current language */ +// +// flags for find_tags(). +// +#define TAG_HELP 1 // only search for help tags +#define TAG_NAMES 2 // only return name of tag +#define TAG_REGEXP 4 // use tag pattern as regexp +#define TAG_NOIC 8 // don't always ignore case +#define TAG_CSCOPE 16 // cscope tag +#define TAG_VERBOSE 32 // message verbosity +#define TAG_INS_COMP 64 // Currently doing insert completion +#define TAG_KEEP_LANG 128 // keep current language +#define TAG_NO_TAGFUNC 256 // do not use 'tagfunc' -#define TAG_MANY 300 /* When finding many tags (for completion), - find up to this many tags */ +#define TAG_MANY 300 // When finding many tags (for completion), + // find up to this many tags /* * Structure used for get_tagfname(). diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 6bf2e8329c..a871924d32 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -45,6 +45,7 @@ source test_syn_attr.vim source test_tabline.vim source test_tabpage.vim source test_tagcase.vim +source test_tagfunc.vim source test_tagjump.vim source test_taglist.vim source test_true_false.vim diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim new file mode 100644 index 0000000000..242aa3a235 --- /dev/null +++ b/src/nvim/testdir/test_tagfunc.vim @@ -0,0 +1,84 @@ +" Test 'tagfunc' + +func TagFunc(pat, flag, info) + let g:tagfunc_args = [a:pat, a:flag, a:info] + let tags = [] + for num in range(1,10) + let tags += [{ + \ 'cmd': '2', 'name': 'nothing'.num, 'kind': 'm', + \ 'filename': 'Xfile1', 'user_data': 'somedata'.num, + \}] + endfor + return tags +endfunc + +func Test_tagfunc() + set tagfunc=TagFunc + new Xfile1 + call setline(1, ['empty', 'one()', 'empty']) + write + + call assert_equal({'cmd': '2', 'static': 0, + \ 'name': 'nothing2', 'user_data': 'somedata2', + \ 'kind': 'm', 'filename': 'Xfile1'}, taglist('.')[1]) + + call settagstack(win_getid(), {'items': []}) + + tag arbitrary + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata1', gettagstack().items[0].user_data) + 5tag arbitrary + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata5', gettagstack().items[1].user_data) + pop + tag + call assert_equal('arbitrary', g:tagfunc_args[0]) + call assert_equal('', g:tagfunc_args[1]) + call assert_equal('somedata5', gettagstack().items[1].user_data) + + let g:tagfunc_args=[] + execute "normal! \" + call assert_equal('one', g:tagfunc_args[0]) + call assert_equal('c', g:tagfunc_args[1]) + + set cpt=t + let g:tagfunc_args=[] + execute "normal! i\\" + call assert_equal('ci', g:tagfunc_args[1]) + call assert_equal('nothing1', getline('.')[0:7]) + + func BadTagFunc1(...) + return 0 + endfunc + func BadTagFunc2(...) + return [1] + endfunc + func BadTagFunc3(...) + return [{'name': 'foo'}] + endfunc + + for &tagfunc in ['BadTagFunc1', 'BadTagFunc2', 'BadTagFunc3'] + try + tag nothing + call assert_false(1, 'tag command should have failed') + catch + call assert_exception('E987:') + endtry + exe 'delf' &tagfunc + endfor + + func NullTagFunc(...) + return v:null + endfunc + set tags= tfu=NullTagFunc + call assert_fails('tag nothing', 'E426') + delf NullTagFunc + + bwipe! + set tags& tfu& cpt& + call delete('Xfile1') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index 4d8eaa9dcc..976f1d8ff0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -70,8 +70,8 @@ static char *m_onlyone = N_("Already only one window"); /* * all CTRL-W window commands are handled here, called from normal_cmd(). */ -void -do_window ( +void +do_window( int nchar, long Prenum, int xchar /* extra char from ":wincmd gx" or NUL */ @@ -1537,10 +1537,14 @@ static void win_init(win_T *newp, win_T *oldp, int flags) /* copy tagstack and folds */ for (i = 0; i < oldp->w_tagstacklen; i++) { - newp->w_tagstack[i] = oldp->w_tagstack[i]; - if (newp->w_tagstack[i].tagname != NULL) - newp->w_tagstack[i].tagname = - vim_strsave(newp->w_tagstack[i].tagname); + taggy_T *tag = &newp->w_tagstack[i]; + *tag = oldp->w_tagstack[i]; + if (tag->tagname != NULL) { + tag->tagname = vim_strsave(tag->tagname); + } + if (tag->user_data != NULL) { + tag->user_data = vim_strsave(tag->user_data); + } } newp->w_tagstackidx = oldp->w_tagstackidx; newp->w_tagstacklen = oldp->w_tagstacklen; -- cgit From 3b6b528ea98ca7bf8cd5ae1cf103203e3ca67814 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 10 Oct 2019 23:40:57 +0100 Subject: vim-patch:8.1.1962: leaking memory when using tagfunc() Problem: Leaking memory when using tagfunc(). Solution: Free the user_data. (Dominique Pelle, closes vim/vim#4886) https://github.com/vim/vim/commit/55008aad50601cae079037fda8fb434cde70c0f4 --- src/nvim/window.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 976f1d8ff0..ce5be8e904 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4642,8 +4642,10 @@ win_free ( xfree(wp->w_lines); - for (i = 0; i < wp->w_tagstacklen; ++i) + for (i = 0; i < wp->w_tagstacklen; i++) { xfree(wp->w_tagstack[i].tagname); + xfree(wp->w_tagstack[i].user_data); + } xfree(wp->w_localdir); -- cgit From 6dceaf33613cc4d1ff361053e65ce801ce2678cf Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 12 Oct 2019 18:25:27 -0400 Subject: vim-patch:8.1.0014: qf_init_ext() is too long Problem: qf_init_ext() is too long. Solution: Split it into multiple functions. (Yegappan Lakshmanan, closes vim/vim#2939) https://github.com/vim/vim/commit/6053f2d29a979ffed1fe01b0a2f28e23750530e9 --- src/nvim/memline.c | 1 + src/nvim/quickfix.c | 269 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 163 insertions(+), 107 deletions(-) (limited to 'src') diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b85c23e50f..05cc62bb33 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1929,6 +1929,7 @@ int ml_append_buf( colnr_T len, // length of new line, including NUL, or 0 bool newfile // flag, see above ) + FUNC_ATTR_NONNULL_ARG(1) { if (buf->b_ml.ml_mfp == NULL) return FAIL; diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c5e8d4b490..c220516270 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -229,8 +229,9 @@ static char *e_loc_list_changed = N_("E926: Current location list was changed"); /// @params enc If non-NULL, encoding used to parse errors /// /// @returns -1 for error, number of errors for success. -int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, - char_u *qf_title, char_u *enc) +int qf_init(win_T *wp, const char_u *restrict efile, + char_u *restrict errorformat, int newlist, + const char_u *restrict qf_title, char_u *restrict enc) { qf_info_T *qi = &ql_info; @@ -880,6 +881,79 @@ restofline: return QF_OK; } +// Allocate the fields used for parsing lines and populating a quickfix list. +static void qf_alloc_fields(qffields_T *pfields) + FUNC_ATTR_NONNULL_ALL +{ + pfields->namebuf = xmalloc(CMDBUFFSIZE + 1); + pfields->module = xmalloc(CMDBUFFSIZE + 1); + pfields->errmsglen = CMDBUFFSIZE + 1; + pfields->errmsg = xmalloc(pfields->errmsglen); + pfields->pattern = xmalloc(CMDBUFFSIZE + 1); +} + +// Free the fields used for parsing lines and populating a quickfix list. +static void qf_free_fields(qffields_T *pfields) + FUNC_ATTR_NONNULL_ALL +{ + xfree(pfields->namebuf); + xfree(pfields->module); + xfree(pfields->errmsg); + xfree(pfields->pattern); +} + +// Setup the state information used for parsing lines and populating a +// quickfix list. +static int qf_setup_state( + qfstate_T *pstate, + char_u *restrict enc, + const char_u *restrict efile, + typval_T *tv, + buf_T *buf, + linenr_T lnumfirst, + linenr_T lnumlast) + FUNC_ATTR_NONNULL_ARG(1) +{ + pstate->vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) { + convert_setup(&pstate->vc, enc, p_enc); + } + + if (efile != NULL + && (pstate->fd = os_fopen((const char *)efile, "r")) == NULL) { + EMSG2(_(e_openerrf), efile); + return FAIL; + } + + if (tv != NULL) { + if (tv->v_type == VAR_STRING) { + pstate->p_str = tv->vval.v_string; + } else if (tv->v_type == VAR_LIST) { + pstate->p_li = tv_list_first(tv->vval.v_list); + } + pstate->tv = tv; + } + pstate->buf = buf; + pstate->buflnum = lnumfirst; + pstate->lnumlast = lnumlast; + + return OK; +} + +// Cleanup the state information used for parsing lines and populating a +// quickfix list. +static void qf_cleanup_state(qfstate_T *pstate) + FUNC_ATTR_NONNULL_ALL +{ + if (pstate->fd != NULL) { + fclose(pstate->fd); + } + xfree(pstate->growbuf); + if (pstate->vc.vc_type != CONV_NONE) { + convert_setup(&pstate->vc, NULL, NULL); + } +} + // Read the errorfile "efile" into memory, line by line, building the error // list. // Alternative: when "efile" is NULL read errors from buffer "buf". @@ -892,19 +966,20 @@ static int qf_init_ext( qf_info_T *qi, int qf_idx, - char_u *efile, + const char_u *restrict efile, buf_T *buf, typval_T *tv, - char_u *errorformat, - int newlist, // TRUE: start a new error list + char_u *restrict errorformat, + bool newlist, // true: start a new error list linenr_T lnumfirst, // first line number to use linenr_T lnumlast, // last line number to use - char_u *qf_title, - char_u *enc + const char_u *restrict qf_title, + char_u *restrict enc ) + FUNC_ATTR_NONNULL_ARG(1) { - qfstate_T state; - qffields_T fields; + qfstate_T state = { 0 }; + qffields_T fields = { 0 }; qfline_T *old_last = NULL; bool adding = false; static efm_T *fmt_first = NULL; @@ -916,21 +991,9 @@ qf_init_ext( // Do not used the cached buffer, it may have been wiped out. XFREE_CLEAR(qf_last_bufname); - memset(&state, 0, sizeof(state)); - memset(&fields, 0, sizeof(fields)); - state.vc.vc_type = CONV_NONE; - if (enc != NULL && *enc != NUL) { - convert_setup(&state.vc, enc, p_enc); - } - - fields.namebuf = xmalloc(CMDBUFFSIZE + 1); - fields.module = xmalloc(CMDBUFFSIZE + 1); - fields.errmsglen = CMDBUFFSIZE + 1; - fields.errmsg = xmalloc(fields.errmsglen); - fields.pattern = xmalloc(CMDBUFFSIZE + 1); - - if (efile != NULL && (state.fd = os_fopen((char *)efile, "r")) == NULL) { - EMSG2(_(e_openerrf), efile); + qf_alloc_fields(&fields); + if (qf_setup_state(&state, enc, efile, tv, buf, + lnumfirst, lnumlast) == FAIL) { goto qf_init_end; } @@ -979,19 +1042,6 @@ qf_init_ext( */ got_int = FALSE; - if (tv != NULL) { - if (tv->v_type == VAR_STRING) { - state.p_str = tv->vval.v_string; - } else if (tv->v_type == VAR_LIST) { - state.p_list = tv->vval.v_list; - state.p_li = tv_list_first(tv->vval.v_list); - } - state.tv = tv; - } - state.buf = buf; - state.buflnum = lnumfirst; - state.lnumlast = lnumlast; - /* * Read the lines in the error file one by one. * Try to recognize one of the error formats in each line. @@ -1059,22 +1109,11 @@ error2: } } qf_init_end: - if (state.fd != NULL) { - fclose(state.fd); - } - xfree(fields.namebuf); - xfree(fields.module); - xfree(fields.errmsg); - xfree(fields.pattern); - xfree(state.growbuf); - if (qf_idx == qi->qf_curlist) { qf_update_buffer(qi, old_last); } - - if (state.vc.vc_type != CONV_NONE) { - convert_setup(&state.vc, NULL, NULL); - } + qf_cleanup_state(&state); + qf_free_fields(&fields); return retval; } @@ -1111,7 +1150,7 @@ static char_u * qf_cmdtitle(char_u *cmd) // Prepare for adding a new quickfix list. If the current list is in the // middle of the stack, then all the following lists are freed and then // the new list is added. -static void qf_new_list(qf_info_T *qi, char_u *qf_title) +static void qf_new_list(qf_info_T *qi, const char_u *qf_title) { int i; @@ -2760,10 +2799,12 @@ next_entry: * Remove newlines and leading whitespace from an error message. * Put the result in "buf[bufsize]". */ -static void qf_fmt_text(char_u *text, char_u *buf, int bufsize) +static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf, + int bufsize) + FUNC_ATTR_NONNULL_ALL { int i; - char_u *p = text; + const char_u *p = text; for (i = 0; *p != NUL && i < bufsize - 1; ++i) { if (*p == '\n') { @@ -3415,17 +3456,82 @@ static void qf_set_title_var(qf_info_T *qi) } } +// Add an error line to the quickfix buffer. +static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, + char_u *dirname) + FUNC_ATTR_NONNULL_ALL +{ + int len; + buf_T *errbuf; + + if (qfp->qf_module != NULL) { + STRCPY(IObuff, qfp->qf_module); + len = (int)STRLEN(IObuff); + } else if (qfp->qf_fnum != 0 + && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL + && errbuf->b_fname != NULL) { + if (qfp->qf_type == 1) { // :helpgrep + STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); + } else { + // shorten the file name if not done already + if (errbuf->b_sfname == NULL + || path_is_absolute(errbuf->b_sfname)) { + if (*dirname == NUL) { + os_dirname(dirname, MAXPATHL); + } + shorten_buf_fname(errbuf, dirname, false); + } + STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); + } + len = (int)STRLEN(IObuff); + } else { + len = 0; + } + IObuff[len++] = '|'; + + if (qfp->qf_lnum > 0) { + snprintf((char *)IObuff + len, sizeof(IObuff), "%" PRId64, + (int64_t)qfp->qf_lnum); + len += (int)STRLEN(IObuff + len); + + if (qfp->qf_col > 0) { + snprintf((char *)IObuff + len, sizeof(IObuff), " col %d", qfp->qf_col); + len += (int)STRLEN(IObuff + len); + } + + snprintf((char *)IObuff + len, sizeof(IObuff), "%s", + (char *)qf_types(qfp->qf_type, qfp->qf_nr)); + len += (int)STRLEN(IObuff + len); + } else if (qfp->qf_pattern != NULL) { + qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); + len += (int)STRLEN(IObuff + len); + } + IObuff[len++] = '|'; + IObuff[len++] = ' '; + + // Remove newlines and leading whitespace from the text. + // For an unrecognized line keep the indent, the compiler may + // mark a word with ^^^^. + qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, + IObuff + len, IOSIZE - len); + + if (ml_append_buf(buf, lnum, IObuff, + (colnr_T)STRLEN(IObuff) + 1, false) == FAIL) { + return FAIL; + } + return OK; +} + // Fill current buffer with quickfix errors, replacing any previous contents. // curbuf must be the quickfix buffer! // If "old_last" is not NULL append the items after this one. // When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() // is used and autocommands will be triggered. static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) + FUNC_ATTR_NONNULL_ARG(1, 2) { linenr_T lnum; qfline_T *qfp; - buf_T *errbuf; - int len; const bool old_KeyTyped = KeyTyped; if (old_last == NULL) { @@ -3455,58 +3561,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) lnum = buf->b_ml.ml_line_count; } while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) { - if (qfp->qf_module != NULL) { - STRCPY(IObuff, qfp->qf_module); - len = (int)STRLEN(IObuff); - } else if (qfp->qf_fnum != 0 - && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL - && errbuf->b_fname != NULL) { - if (qfp->qf_type == 1) { // :helpgrep - STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff)); - } else { - // shorten the file name if not done already - if (errbuf->b_sfname == NULL - || path_is_absolute(errbuf->b_sfname)) { - if (*dirname == NUL) { - os_dirname(dirname, MAXPATHL); - } - shorten_buf_fname(errbuf, dirname, false); - } - STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); - } - len = (int)STRLEN(IObuff); - } else { - len = 0; - } - IObuff[len++] = '|'; - - if (qfp->qf_lnum > 0) { - sprintf((char *)IObuff + len, "%" PRId64, (int64_t)qfp->qf_lnum); - len += (int)STRLEN(IObuff + len); - - if (qfp->qf_col > 0) { - sprintf((char *)IObuff + len, " col %d", qfp->qf_col); - len += (int)STRLEN(IObuff + len); - } - - sprintf((char *)IObuff + len, "%s", - (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - len += (int)STRLEN(IObuff + len); - } else if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); - len += (int)STRLEN(IObuff + len); - } - IObuff[len++] = '|'; - IObuff[len++] = ' '; - - /* Remove newlines and leading whitespace from the text. - * For an unrecognized line keep the indent, the compiler may - * mark a word with ^^^^. */ - qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, - IObuff + len, IOSIZE - len); - - if (ml_append_buf(buf, lnum, IObuff, (colnr_T)STRLEN(IObuff) + 1, false) - == FAIL) { + if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) { break; } lnum++; -- cgit From 5e02bd071ed054b52ca7e53536d4b5cd594737eb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 13 Oct 2019 19:19:32 -0400 Subject: vim-patch:8.1.0288: quickfix code uses cmdidx too often Problem: Quickfix code uses cmdidx too often. Solution: Add is_loclist_cmd(). (Yegappan Lakshmanan) https://github.com/vim/vim/commit/396659592fe039decc8c088694912067fe32a681 --- src/nvim/ex_docmd.c | 11 +++++ src/nvim/quickfix.c | 122 ++++++++++++++++++++++++++++------------------------ 2 files changed, 76 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ae3fb4fbfb..16751b3a53 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -10139,6 +10139,17 @@ static void ex_folddo(exarg_T *eap) ml_clearmarked(); // clear rest of the marks } +// Returns true if the supplied Ex cmdidx is for a location list command +// instead of a quickfix command. +bool is_loclist_cmd(int cmdidx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (cmdidx < 0 || cmdidx > CMD_SIZE) { + return false; + } + return cmdnames[cmdidx].cmd_name[0] == 'l'; +} + bool get_pressedreturn(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c220516270..4483ada3e4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2647,7 +2647,7 @@ void qf_list(exarg_T *eap) // recognised errors qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_llist) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); @@ -2856,7 +2856,7 @@ void qf_age(exarg_T *eap) qf_info_T *qi = &ql_info; int count; - if (eap->cmdidx == CMD_lolder || eap->cmdidx == CMD_lnewer) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); @@ -2895,7 +2895,7 @@ void qf_history(exarg_T *eap) qf_info_T *qi = &ql_info; int i; - if (eap->cmdidx == CMD_lhistory) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); } if (qi == NULL || (qi->qf_listcount == 0 @@ -3097,7 +3097,7 @@ void ex_cwindow(exarg_T *eap) qf_info_T *qi = &ql_info; win_T *win; - if (eap->cmdidx == CMD_lwindow) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) return; @@ -3129,7 +3129,7 @@ void ex_cclose(exarg_T *eap) win_T *win = NULL; qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_lclose || eap->cmdidx == CMD_lwindow) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) return; @@ -3155,7 +3155,7 @@ void ex_copen(exarg_T *eap) buf_T *qf_buf; win_T *oldwin = curwin; - if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); @@ -3284,7 +3284,7 @@ void ex_cbottom(exarg_T *eap) { qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_lbottom) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); @@ -3700,9 +3700,9 @@ void ex_make(exarg_T *eap) } } - if (eap->cmdidx == CMD_lmake || eap->cmdidx == CMD_lgrep - || eap->cmdidx == CMD_lgrepadd) + if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; + } autowrite_all(); fname = get_mef_name(); @@ -3817,7 +3817,7 @@ size_t qf_get_size(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { + if (is_loclist_cmd(eap->cmdidx)) { // Location list. qi = GET_LOC_LIST(curwin); if (qi == NULL) { @@ -3857,7 +3857,7 @@ size_t qf_get_cur_idx(exarg_T *eap) { qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { + if (is_loclist_cmd(eap->cmdidx)) { // Location list. qi = GET_LOC_LIST(curwin); if (qi == NULL) { @@ -3877,7 +3877,7 @@ int qf_get_cur_valid_idx(exarg_T *eap) { qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { + if (is_loclist_cmd(eap->cmdidx)) { // Location list. qi = GET_LOC_LIST(curwin); if (qi == NULL) { @@ -3970,12 +3970,7 @@ void ex_cc(exarg_T *eap) { qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_ll - || eap->cmdidx == CMD_lrewind - || eap->cmdidx == CMD_lfirst - || eap->cmdidx == CMD_llast - || eap->cmdidx == CMD_ldo - || eap->cmdidx == CMD_lfdo) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); @@ -3986,13 +3981,22 @@ void ex_cc(exarg_T *eap) int errornr; if (eap->addr_count > 0) { errornr = (int)eap->line2; - } else if (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) { - errornr = 0; - } else if (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind - || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst) { - errornr = 1; } else { - errornr = 32767; + switch (eap->cmdidx) { + case CMD_cc: + case CMD_ll: + errornr = 0; + break; + case CMD_crewind: + case CMD_lrewind: + case CMD_cfirst: + case CMD_lfirst: + errornr = 1; + break; + default: + errornr = 32767; + break; + } } // For cdo and ldo commands, jump to the nth valid error. @@ -4024,14 +4028,7 @@ void ex_cnext(exarg_T *eap) { qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_lnext - || eap->cmdidx == CMD_lNext - || eap->cmdidx == CMD_lprevious - || eap->cmdidx == CMD_lnfile - || eap->cmdidx == CMD_lNfile - || eap->cmdidx == CMD_lpfile - || eap->cmdidx == CMD_ldo - || eap->cmdidx == CMD_lfdo) { + if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); if (qi == NULL) { EMSG(_(e_loclist)); @@ -4048,17 +4045,37 @@ void ex_cnext(exarg_T *eap) errornr = 1; } - qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext - || eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo) - ? FORWARD - : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile - || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) - ? FORWARD_FILE - : (eap->cmdidx == CMD_cpfile || eap->cmdidx == CMD_lpfile - || eap->cmdidx == CMD_cNfile || eap->cmdidx == CMD_lNfile) - ? BACKWARD_FILE - : BACKWARD, - errornr, eap->forceit); + // Depending on the command jump to either next or previous entry/file. + Direction dir; + switch (eap->cmdidx) { + case CMD_cprevious: + case CMD_lprevious: + case CMD_cNext: + case CMD_lNext: + dir = BACKWARD; + break; + case CMD_cnfile: + case CMD_lnfile: + case CMD_cfdo: + case CMD_lfdo: + dir = FORWARD_FILE; + break; + case CMD_cpfile: + case CMD_lpfile: + case CMD_cNfile: + case CMD_lNfile: + dir = BACKWARD_FILE; + break; + case CMD_cnext: + case CMD_lnext: + case CMD_cdo: + case CMD_ldo: + default: + dir = FORWARD; + break; + } + + qf_jump(qi, dir, errornr, eap->forceit); } /* @@ -4087,9 +4104,7 @@ void ex_cfile(exarg_T *eap) char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; - if (eap->cmdidx == CMD_lfile - || eap->cmdidx == CMD_lgetfile - || eap->cmdidx == CMD_laddfile) { + if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; } @@ -4346,10 +4361,7 @@ void ex_vimgrep(exarg_T *eap) } } - if (eap->cmdidx == CMD_lgrep - || eap->cmdidx == CMD_lvimgrep - || eap->cmdidx == CMD_lgrepadd - || eap->cmdidx == CMD_lvimgrepadd) { + if (is_loclist_cmd(eap->cmdidx)) { qi = ll_get_or_alloc_list(curwin); wp = curwin; } @@ -5575,9 +5587,7 @@ void ex_cbuffer(exarg_T *eap) } // Must come after autocommands. - if (eap->cmdidx == CMD_lbuffer - || eap->cmdidx == CMD_lgetbuffer - || eap->cmdidx == CMD_laddbuffer) { + if (is_loclist_cmd(eap->cmdidx)) { qi = ll_get_or_alloc_list(curwin); wp = curwin; } @@ -5677,9 +5687,7 @@ void ex_cexpr(exarg_T *eap) } } - if (eap->cmdidx == CMD_lexpr - || eap->cmdidx == CMD_lgetexpr - || eap->cmdidx == CMD_laddexpr) { + if (is_loclist_cmd(eap->cmdidx)) { qi = ll_get_or_alloc_list(curwin); wp = curwin; } @@ -5878,7 +5886,7 @@ void ex_helpgrep(exarg_T *eap) char_u *const save_cpo = p_cpo; p_cpo = empty_option; - if (eap->cmdidx == CMD_lhelpgrep) { + if (is_loclist_cmd(eap->cmdidx)) { qi = hgr_get_ll(&new_qi); } -- cgit From f3d6d8750b70d56f92d6ece87031ed205abdfcca Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 13 Oct 2019 19:49:17 -0400 Subject: vim-patch:8.1.0345: cannot get the window id associated with the location list Problem: Cannot get the window id associated with the location list. Solution: Add the "filewinid" argument to getloclist(). (Yegappan Lakshmanan, closes vim/vim#3202) https://github.com/vim/vim/commit/c9cc9c78f21caba7ecb5c90403df5e19a57aa96a --- src/nvim/quickfix.c | 40 ++++++++++++++++++++++++++++++--- src/nvim/testdir/test_quickfix.vim | 46 +++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 4483ada3e4..13e8a73af4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4809,7 +4809,8 @@ enum { QF_GETLIST_IDX = 0x40, QF_GETLIST_SIZE = 0x80, QF_GETLIST_TICK = 0x100, - QF_GETLIST_ALL = 0x1FF + QF_GETLIST_FILEWINID = 0x200, + QF_GETLIST_ALL = 0x3FF, }; /// Parse text from 'di' and return the quickfix list items. @@ -4865,12 +4866,17 @@ static int qf_winid(qf_info_T *qi) } /// Convert the keys in 'what' to quickfix list property flags. -static int qf_getprop_keys2flags(dict_T *what) +static int qf_getprop_keys2flags(const dict_T *what, bool loclist) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { int flags = QF_GETLIST_NONE; if (tv_dict_find(what, S_LEN("all")) != NULL) { flags |= QF_GETLIST_ALL; + if (!loclist) { + // File window ID is applicable only to location list windows + flags &= ~QF_GETLIST_FILEWINID; + } } if (tv_dict_find(what, S_LEN("title")) != NULL) { flags |= QF_GETLIST_TITLE; @@ -4899,6 +4905,9 @@ static int qf_getprop_keys2flags(dict_T *what) if (tv_dict_find(what, S_LEN("changedtick")) != NULL) { flags |= QF_GETLIST_TICK; } + if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) { + flags |= QF_GETLIST_FILEWINID; + } return flags; } @@ -4984,6 +4993,9 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) if ((status == OK) && (flags & QF_GETLIST_TICK)) { status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0); } + if ((status == OK) && (qi != &ql_info) && (flags & QF_GETLIST_FILEWINID)) { + status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); + } return status; } @@ -4995,6 +5007,25 @@ static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict) (const char *)qi->qf_lists[qf_idx].qf_title); } +// Returns the identifier of the window used to display files from a location +// list. If there is no associated window, then returns 0. Useful only when +// called from a location list window. +static int qf_getprop_filewinid(const win_T *wp, const qf_info_T *qi, + dict_T *retdict) + FUNC_ATTR_NONNULL_ARG(3) +{ + handle_T winid = 0; + + if (wp != NULL && IS_LL_WINDOW(wp)) { + win_T *ll_wp = qf_find_win_with_loclist(qi); + if (ll_wp != NULL) { + winid = ll_wp->handle; + } + } + + return tv_dict_add_nr(retdict, S_LEN("filewinid"), winid); +} + /// Return the quickfix list items/entries as 'items' in retdict static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict) { @@ -5053,7 +5084,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) qi = GET_LOC_LIST(wp); } - int flags = qf_getprop_keys2flags(what); + const int flags = qf_getprop_keys2flags(what, wp != NULL); if (qi != NULL && qi->qf_listcount != 0) { qf_idx = qf_getprop_qfidx(qi, what); @@ -5093,6 +5124,9 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) status = tv_dict_add_nr(retdict, S_LEN("changedtick"), qi->qf_lists[qf_idx].qf_changedtick); } + if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { + status = qf_getprop_filewinid(wp, qi, retdict); + } return status; } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index fc514fc9e6..4af986ffba 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1963,6 +1963,18 @@ func Xproperty_tests(cchar) call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title) + " Test for getting id of window associated with a location list window + if a:cchar == 'l' + only + call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid) + let wid = win_getid() + Xopen + call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid) + wincmd w + call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid) + only + endif + " The following used to crash Vim with address sanitizer call g:Xsetlist([], 'f') call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]}) @@ -3075,7 +3087,17 @@ func Xgetlist_empty_tests(cchar) call assert_equal('', g:Xgetlist({'title' : 0}).title) call assert_equal(0, g:Xgetlist({'winid' : 0}).winid) call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick) - call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick': 0}, g:Xgetlist({'all' : 0})) + if a:cchar == 'c' + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, + \ 'items' : [], 'nr' : 0, 'size' : 0, + \ 'title' : '', 'winid' : 0, 'changedtick': 0}, + \ g:Xgetlist({'all' : 0})) + else + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, + \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', + \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0}, + \ g:Xgetlist({'all' : 0})) + endif " Quickfix window with empty stack silent! Xopen @@ -3108,7 +3130,16 @@ func Xgetlist_empty_tests(cchar) call assert_equal('', g:Xgetlist({'id' : qfid, 'title' : 0}).title) call assert_equal(0, g:Xgetlist({'id' : qfid, 'winid' : 0}).winid) call assert_equal(0, g:Xgetlist({'id' : qfid, 'changedtick' : 0}).changedtick) - call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) + if a:cchar == 'c' + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) + else + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0}, + \ g:Xgetlist({'id' : qfid, 'all' : 0})) + endif " Non-existing quickfix list number call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context) @@ -3120,7 +3151,16 @@ func Xgetlist_empty_tests(cchar) call assert_equal('', g:Xgetlist({'nr' : 5, 'title' : 0}).title) call assert_equal(0, g:Xgetlist({'nr' : 5, 'winid' : 0}).winid) call assert_equal(0, g:Xgetlist({'nr' : 5, 'changedtick' : 0}).changedtick) - call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0})) + if a:cchar == 'c' + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0})) + else + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0}, + \ g:Xgetlist({'nr' : 5, 'all' : 0})) + endif endfunc func Test_getqflist() -- cgit From 8257d49ff6bd825e8f4ffca189cd6c23bd0c66be Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 14 Oct 2019 01:30:53 -0400 Subject: vim-patch:8.1.0410: the ex_copen() function is too long Problem: The ex_copen() function is too long. Solution: Refactor to split off two functions. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/476c0db00205590974395df717519407a7717270 --- src/nvim/quickfix.c | 192 +++++++++++++++++++++++++++++----------------------- src/nvim/window.c | 2 +- 2 files changed, 109 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 13e8a73af4..3f2688809d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3142,6 +3142,98 @@ void ex_cclose(exarg_T *eap) } } +// Goto a quickfix or location list window (if present). +// Returns OK if the window is found, FAIL otherwise. +static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, + bool vertsplit) +{ + win_T *const win = qf_find_win(qi); + if (win == NULL) { + return FAIL; + } + + win_goto(win); + if (resize) { + if (vertsplit) { + if (sz != win->w_width) { + win_setwidth(sz); + } + } else if (sz != win->w_height) { + win_setheight(sz); + } + } + + return OK; +} + +// Open a new quickfix or location list window, load the quickfix buffer and +// set the appropriate options for the window. +// Returns FAIL if the window could not be opened. +static int qf_open_new_cwindow(const qf_info_T *qi, int height) +{ + win_T *oldwin = curwin; + const tabpage_T *const prevtab = curtab; + int flags = 0; + + const buf_T *const qf_buf = qf_find_buf(qi); + + // The current window becomes the previous window afterwards. + win_T *const win = curwin; + + if (IS_QF_STACK(qi) && cmdmod.split == 0) { + // Create the new quickfix window at the very bottom, except when + // :belowright or :aboveleft is used. + win_goto(lastwin); + } + // Default is to open the window below the current window + if (cmdmod.split == 0) { + flags = WSP_BELOW; + } + flags |= WSP_NEWLOC; + if (win_split(height, flags) == FAIL) { + return FAIL; // not enough room for window + } + RESET_BINDING(curwin); + + if (IS_LL_STACK(qi)) { + // For the location list window, create a reference to the + // location list from the window 'win'. + curwin->w_llist_ref = win->w_llist; + win->w_llist->qf_refcount++; + } + + if (oldwin != curwin) { + oldwin = NULL; // don't store info when in another window + } + if (qf_buf != NULL) { + // Use the existing quickfix buffer + (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, + ECMD_HIDE + ECMD_OLDBUF, oldwin); + } else { + // Create a new quickfix buffer + (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); + + // switch off 'swapfile' + set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value("bt", 0L, "quickfix", OPT_LOCAL); + set_option_value("bh", 0L, "wipe", OPT_LOCAL); + RESET_BINDING(curwin); + curwin->w_p_diff = false; + set_option_value("fdm", 0L, "manual", OPT_LOCAL); + } + + // Only set the height when still in the same tab page and there is no + // window to the side. + if (curtab == prevtab && curwin->w_width == Columns) { + win_setheight(height); + } + curwin->w_p_wfh = true; // set 'winfixheight' + if (win_valid(win)) { + prevwin = win; + } + return OK; +} + /* * ":copen": open a window that shows the list of errors. * ":lopen": open a window that shows the location list. @@ -3150,10 +3242,7 @@ void ex_copen(exarg_T *eap) { qf_info_T *qi = &ql_info; int height; - win_T *win; - tabpage_T *prevtab = curtab; - buf_T *qf_buf; - win_T *oldwin = curwin; + int status = FAIL; if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); @@ -3171,83 +3260,15 @@ void ex_copen(exarg_T *eap) } reset_VIsual_and_resel(); // stop Visual mode - /* - * Find existing quickfix window, or open a new one. - */ - win = qf_find_win(qi); - - if (win != NULL && cmdmod.tab == 0) { - win_goto(win); - if (eap->addr_count != 0) { - if (cmdmod.split & WSP_VERT) { - if (height != win->w_width) { - win_setwidth(height); - } - } else { - if (height != win->w_height) { - win_setheight(height); - } - } - } - } else { - int flags = 0; - - qf_buf = qf_find_buf(qi); - - /* The current window becomes the previous window afterwards. */ - win = curwin; - - if ((eap->cmdidx == CMD_copen || eap->cmdidx == CMD_cwindow) - && cmdmod.split == 0) - // Create the new quickfix window at the very bottom, except when - // :belowright or :aboveleft is used. - win_goto(lastwin); - // Default is to open the window below the current window - if (cmdmod.split == 0) { - flags = WSP_BELOW; - } - flags |= WSP_NEWLOC; - if (win_split(height, flags) == FAIL) { - return; // not enough room for window + // Find an existing quickfix window, or open a new one. + if (cmdmod.tab == 0) { + status = qf_goto_cwindow(qi, eap->addr_count != 0, height, + cmdmod.split & WSP_VERT); + } + if (status == FAIL) { + if (qf_open_new_cwindow(qi, height) == FAIL) { + return; } - RESET_BINDING(curwin); - - if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) { - /* - * For the location list window, create a reference to the - * location list from the window 'win'. - */ - curwin->w_llist_ref = win->w_llist; - win->w_llist->qf_refcount++; - } - - if (oldwin != curwin) - oldwin = NULL; /* don't store info when in another window */ - if (qf_buf != NULL) - /* Use the existing quickfix buffer */ - (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, - ECMD_HIDE + ECMD_OLDBUF, oldwin); - else { - /* Create a new quickfix buffer */ - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); - // Switch off 'swapfile'. - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - RESET_BINDING(curwin); - curwin->w_p_diff = false; - set_option_value("fdm", 0L, "manual", OPT_LOCAL); - } - - /* Only set the height when still in the same tab page and there is no - * window to the side. */ - if (curtab == prevtab - && curwin->w_width == Columns - ) - win_setheight(height); - curwin->w_p_wfh = TRUE; /* set 'winfixheight' */ - if (win_valid(win)) - prevwin = win; } qf_set_title_var(qi); @@ -3258,7 +3279,7 @@ void ex_copen(exarg_T *eap) curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index; curwin->w_cursor.col = 0; check_cursor(); - update_topline(); /* scroll to show the line */ + update_topline(); // scroll to show the line } // Move the cursor in the quickfix window to "lnum". @@ -3349,7 +3370,8 @@ qf_win_pos_update ( /// Checks whether the given window is displaying the specified /// quickfix/location list buffer. -static int is_qf_win(win_T *win, qf_info_T *qi) +static int is_qf_win(const win_T *win, const qf_info_T *qi) + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // // A window displaying the quickfix buffer will have the w_llist_ref field @@ -3369,7 +3391,8 @@ static int is_qf_win(win_T *win, qf_info_T *qi) /// Find a window displaying the quickfix/location list 'qi' /// Only searches in the current tabpage. -static win_T *qf_find_win(qf_info_T *qi) +static win_T *qf_find_win(const qf_info_T *qi) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (is_qf_win(win, qi)) { @@ -3384,7 +3407,8 @@ static win_T *qf_find_win(qf_info_T *qi) * Find a quickfix buffer. * Searches in windows opened in all the tabs. */ -static buf_T *qf_find_buf(qf_info_T *qi) +static buf_T *qf_find_buf(const qf_info_T *qi) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_TAB_WINDOWS(tp, win) { if (is_qf_win(win, qi)) { diff --git a/src/nvim/window.c b/src/nvim/window.c index ce5be8e904..d4a0db9c89 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1574,7 +1574,7 @@ static void win_init_some(win_T *newp, win_T *oldp) /// Check if "win" is a pointer to an existing window in the current tabpage. /// /// @param win window to check -bool win_valid(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (win == NULL) { return false; -- cgit From 279ff233799f23dc8a11882fe78df79f9dafdfa3 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 14 Oct 2019 10:00:14 -0400 Subject: vim-patch:8.1.0434: copy_loclist() is too long Problem: copy_loclist() is too long. Solution: Split in multiple functions. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/09037503ea5f957ad23121bc61e15e4bb1765edf --- src/nvim/quickfix.c | 204 ++++++++++++++++++++++++++++------------------------ src/nvim/window.c | 5 +- 2 files changed, 114 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3f2688809d..9483670864 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1622,116 +1622,134 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) return wp->w_llist; } -/* - * Copy the location list from window "from" to window "to". - */ -void copy_loclist(win_T *from, win_T *to) +// Copy location list entries from 'from_qfl' to 'to_qfl'. +static int copy_loclist_entries(const qf_list_T *from_qfl, + qf_list_T *to_qfl, + qf_info_T *to_qi) + FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi; - int idx; int i; + const qfline_T *from_qfp; + + // copy all the location entries in this list + for (i = 0, from_qfp = from_qfl->qf_start; + i < from_qfl->qf_count && from_qfp != NULL; + i++, from_qfp = from_qfp->qf_next) { + if (qf_add_entry(to_qi, + to_qi->qf_curlist, + NULL, + NULL, + from_qfp->qf_module, + 0, + from_qfp->qf_text, + from_qfp->qf_lnum, + from_qfp->qf_col, + from_qfp->qf_viscol, + from_qfp->qf_pattern, + from_qfp->qf_nr, + 0, + from_qfp->qf_valid) == FAIL) { + return FAIL; + } - /* - * When copying from a location list window, copy the referenced - * location list. For other windows, copy the location list for - * that window. - */ - if (IS_LL_WINDOW(from)) + // qf_add_entry() will not set the qf_num field, as the + // directory and file names are not supplied. So the qf_fnum + // field is copied here. + qfline_T *const prevp = to_qfl->qf_last; + prevp->qf_fnum = from_qfp->qf_fnum; // file number + prevp->qf_type = from_qfp->qf_type; // error type + if (from_qfl->qf_ptr == from_qfp) { + to_qfl->qf_ptr = prevp; // current location + } + } + + return OK; +} + +// Copy the specified location list 'from_qfl' to 'to_qfl'. +static int copy_loclist(const qf_list_T *from_qfl, + qf_list_T *to_qfl, + qf_info_T *to_qi) + FUNC_ATTR_NONNULL_ALL +{ + // Some of the fields are populated by qf_add_entry() + to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; + to_qfl->qf_count = 0; + to_qfl->qf_index = 0; + to_qfl->qf_start = NULL; + to_qfl->qf_last = NULL; + to_qfl->qf_ptr = NULL; + if (from_qfl->qf_title != NULL) { + to_qfl->qf_title = vim_strsave(from_qfl->qf_title); + } else { + to_qfl->qf_title = NULL; + } + if (from_qfl->qf_ctx != NULL) { + to_qfl->qf_ctx = xcalloc(1, sizeof(*to_qfl->qf_ctx)); + if (to_qfl->qf_ctx != NULL) { + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); + } + } else { + to_qfl->qf_ctx = NULL; + } + if (from_qfl->qf_count) { + if (copy_loclist_entries(from_qfl, to_qfl, to_qi) == FAIL) { + return FAIL; + } + } + + to_qfl->qf_index = from_qfl->qf_index; // current index in the list + + // Assign a new ID for the location list + to_qfl->qf_id = ++last_qf_id; + to_qfl->qf_changedtick = 0L; + + // When no valid entries are present in the list, qf_ptr points to + // the first item in the list + if (to_qfl->qf_nonevalid) { + to_qfl->qf_ptr = to_qfl->qf_start; + to_qfl->qf_index = 1; + } + + return OK; +} + +// Copy the location list stack 'from' window to 'to' window. +void copy_loclist_stack(win_T *from, win_T *to) + FUNC_ATTR_NONNULL_ALL +{ + qf_info_T *qi; + + // When copying from a location list window, copy the referenced + // location list. For other windows, copy the location list for + // that window. + if (IS_LL_WINDOW(from)) { qi = from->w_llist_ref; - else + } else { qi = from->w_llist; + } - if (qi == NULL) /* no location list to copy */ + if (qi == NULL) { // no location list to copy return; + } - /* allocate a new location list */ + // allocate a new location list to->w_llist = ll_new_list(); to->w_llist->qf_listcount = qi->qf_listcount; - /* Copy the location lists one at a time */ - for (idx = 0; idx < qi->qf_listcount; idx++) { - qf_list_T *from_qfl; - qf_list_T *to_qfl; - + // Copy the location lists one at a time + for (int idx = 0; idx < qi->qf_listcount; idx++) { to->w_llist->qf_curlist = idx; - from_qfl = &qi->qf_lists[idx]; - to_qfl = &to->w_llist->qf_lists[idx]; - - /* Some of the fields are populated by qf_add_entry() */ - to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; - to_qfl->qf_count = 0; - to_qfl->qf_index = 0; - to_qfl->qf_start = NULL; - to_qfl->qf_last = NULL; - to_qfl->qf_ptr = NULL; - if (from_qfl->qf_title != NULL) - to_qfl->qf_title = vim_strsave(from_qfl->qf_title); - else - to_qfl->qf_title = NULL; - - if (from_qfl->qf_ctx != NULL) { - to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); - tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); - } else { - to_qfl->qf_ctx = NULL; - } - - if (from_qfl->qf_count) { - qfline_T *from_qfp; - qfline_T *prevp; - - // copy all the location entries in this list - for (i = 0, from_qfp = from_qfl->qf_start; - i < from_qfl->qf_count && from_qfp != NULL; - i++, from_qfp = from_qfp->qf_next) { - if (qf_add_entry(to->w_llist, - to->w_llist->qf_curlist, - NULL, - NULL, - from_qfp->qf_module, - 0, - from_qfp->qf_text, - from_qfp->qf_lnum, - from_qfp->qf_col, - from_qfp->qf_viscol, - from_qfp->qf_pattern, - from_qfp->qf_nr, - 0, - from_qfp->qf_valid) == FAIL) { - qf_free_all(to); - return; - } - /* - * qf_add_entry() will not set the qf_num field, as the - * directory and file names are not supplied. So the qf_fnum - * field is copied here. - */ - prevp = to->w_llist->qf_lists[to->w_llist->qf_curlist].qf_last; - prevp->qf_fnum = from_qfp->qf_fnum; // file number - prevp->qf_type = from_qfp->qf_type; // error type - if (from_qfl->qf_ptr == from_qfp) { - to_qfl->qf_ptr = prevp; // current location - } - } - } - - to_qfl->qf_index = from_qfl->qf_index; /* current index in the list */ - - // Assign a new ID for the location list - to_qfl->qf_id = ++last_qf_id; - to_qfl->qf_changedtick = 0L; - - /* When no valid entries are present in the list, qf_ptr points to - * the first item in the list */ - if (to_qfl->qf_nonevalid) { - to_qfl->qf_ptr = to_qfl->qf_start; - to_qfl->qf_index = 1; + if (copy_loclist(&qi->qf_lists[idx], &to->w_llist->qf_lists[idx], + to->w_llist) == FAIL) { + qf_free_all(to); + return; } } - to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ + to->w_llist->qf_curlist = qi->qf_curlist; // current list } // Get buffer number for file "directory/fname". diff --git a/src/nvim/window.c b/src/nvim/window.c index d4a0db9c89..0531ad1938 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1530,8 +1530,9 @@ static void win_init(win_T *newp, win_T *oldp, int flags) /* Don't copy the location list. */ newp->w_llist = NULL; newp->w_llist_ref = NULL; - } else - copy_loclist(oldp, newp); + } else { + copy_loclist_stack(oldp, newp); + } newp->w_localdir = (oldp->w_localdir == NULL) ? NULL : vim_strsave(oldp->w_localdir); -- cgit From aa8f059397313a931706f32a610f70e377043e13 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 18 Oct 2019 01:17:35 -0400 Subject: vim-patch:8.1.0438: the ex_make() function is too long Problem: The ex_make() function is too long. Solution: Split it into several functions. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/b434ae2a1fcbbd43244c6130451de7f14346e224 --- src/nvim/quickfix.c | 85 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 9483670864..b5e0731935 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3708,6 +3708,58 @@ int grep_internal(cmdidx_T cmdidx) *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0; } +// Return the make/grep autocmd name. +static char_u *make_get_auname(cmdidx_T cmdidx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (cmdidx) { + case CMD_make: + return (char_u *)"make"; + case CMD_lmake: + return (char_u *)"lmake"; + case CMD_grep: + return (char_u *)"grep"; + case CMD_lgrep: + return (char_u *)"lgrep"; + case CMD_grepadd: + return (char_u *)"grepadd"; + case CMD_lgrepadd: + return (char_u *)"lgrepadd"; + default: + return NULL; + } +} + +// Form the complete command line to invoke 'make'/'grep'. Quote the command +// using 'shellquote' and append 'shellpipe'. Echo the fully formed command. +static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + size_t len = STRLEN(p_shq) * 2 + STRLEN(makecmd) + 1; + if (*p_sp != NUL) { + len += STRLEN(p_sp) + STRLEN(fname) + 3; + } + char *const cmd = xmalloc(len); + snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)makecmd, (char *)p_shq); + + // If 'shellpipe' empty: don't redirect to 'errorfile'. + if (*p_sp != NUL) { + append_redir(cmd, len, (char *)p_sp, (char *)fname); + } + + // Display the fully formed command. Output a newline if there's something + // else than the :make command that was typed (in which case the cursor is + // in column 0). + if (msg_col == 0) { + msg_didout = false; + } + msg_start(); + MSG_PUTS(":!"); + msg_outtrans((char_u *)cmd); // show what we are doing + + return cmd; +} + /* * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" */ @@ -3717,24 +3769,15 @@ void ex_make(exarg_T *eap) win_T *wp = NULL; qf_info_T *qi = &ql_info; int res; - char_u *au_name = NULL; char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; - /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ + // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". if (grep_internal(eap->cmdidx)) { ex_vimgrep(eap); return; } - switch (eap->cmdidx) { - case CMD_make: au_name = (char_u *)"make"; break; - case CMD_lmake: au_name = (char_u *)"lmake"; break; - case CMD_grep: au_name = (char_u *)"grep"; break; - case CMD_lgrep: au_name = (char_u *)"lgrep"; break; - case CMD_grepadd: au_name = (char_u *)"grepadd"; break; - case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break; - default: break; - } + char_u *const au_name = make_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { @@ -3752,25 +3795,7 @@ void ex_make(exarg_T *eap) return; os_remove((char *)fname); // in case it's not unique - // If 'shellpipe' empty: don't redirect to 'errorfile'. - const size_t len = (STRLEN(p_shq) * 2 + STRLEN(eap->arg) + 1 - + (*p_sp == NUL - ? 0 - : STRLEN(p_sp) + STRLEN(fname) + 3)); - char *const cmd = xmalloc(len); - snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)eap->arg, - (char *)p_shq); - if (*p_sp != NUL) { - append_redir(cmd, len, (char *) p_sp, (char *) fname); - } - // Output a newline if there's something else than the :make command that - // was typed (in which case the cursor is in column 0). - if (msg_col == 0) { - msg_didout = false; - } - msg_start(); - MSG_PUTS(":!"); - msg_outtrans((char_u *)cmd); // show what we are doing + char *const cmd = make_get_fullcmd(eap->arg, fname); do_shell((char_u *)cmd, 0); -- cgit From 8daefa348e24a2fb06a7d13cec0f827c7c3e60c5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 20 Oct 2019 21:21:56 -0400 Subject: vim-patch:8.1.0455: checking for empty quickfix stack is not consistent Problem: Checking for empty quickfix stack is not consistent. Solution: Use qf_stack_empty(). (Yegappan Lakshmanan) https://github.com/vim/vim/commit/019dfe6855e011c02427bb922aafeae0245372c9 --- src/nvim/quickfix.c | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index b5e0731935..ec32a88f79 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -784,6 +784,13 @@ static int qf_get_nextline(qfstate_T *state) return QF_OK; } +// Returns true if the specified quickfix/location stack is empty +static bool qf_stack_empty(const qf_info_T *qi) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return qi == NULL || qi->qf_listcount <= 0; +} + // Returns true if the specified quickfix/location list is empty. static bool qf_list_empty(const qf_info_T *qi, int qf_idx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -2537,8 +2544,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) if (qi == NULL) qi = &ql_info; - if (qi->qf_curlist >= qi->qf_listcount - || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { EMSG(_(e_quickfix)); return; } @@ -2673,8 +2679,7 @@ void qf_list(exarg_T *eap) } } - if (qi->qf_curlist >= qi->qf_listcount - || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { EMSG(_(e_quickfix)); return; } @@ -2916,8 +2921,7 @@ void qf_history(exarg_T *eap) if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); } - if (qi == NULL || (qi->qf_listcount == 0 - && qi->qf_lists[qi->qf_curlist].qf_count == 0)) { + if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { MSG(_("No entries")); } else { for (i = 0; i < qi->qf_listcount; i++) { @@ -3129,13 +3133,15 @@ void ex_cwindow(exarg_T *eap) * close the window. If a quickfix window is not open, then open * it if we have errors; otherwise, leave it closed. */ - if (qi->qf_lists[qi->qf_curlist].qf_nonevalid - || qi->qf_lists[qi->qf_curlist].qf_count == 0 - || qi->qf_curlist >= qi->qf_listcount) { - if (win != NULL) + if (qf_stack_empty(qi) + || qi->qf_lists[qi->qf_curlist].qf_nonevalid + || qf_list_empty(qi, qi->qf_curlist)) { + if (win != NULL) { ex_cclose(eap); - } else if (win == NULL) + } + } else if (win == NULL) { ex_copen(eap); + } } /* @@ -3588,8 +3594,8 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) } } - /* Check if there is anything to display */ - if (qi->qf_curlist < qi->qf_listcount) { + // Check if there is anything to display + if (!qf_stack_empty(qi)) { char_u dirname[MAXPATHL]; *dirname = NUL; @@ -4460,7 +4466,7 @@ void ex_vimgrep(exarg_T *eap) if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd) - || qi->qf_curlist == qi->qf_listcount) { + || qf_stack_empty(qi)) { // make place for a new list qf_new_list(qi, title); } @@ -5153,12 +5159,12 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) const int flags = qf_getprop_keys2flags(what, wp != NULL); - if (qi != NULL && qi->qf_listcount != 0) { + if (!qf_stack_empty(qi)) { qf_idx = qf_getprop_qfidx(qi, what); } // List is not present or is empty - if (qi == NULL || qi->qf_listcount == 0 || qf_idx == INVALID_QFIDX) { + if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) { return qf_getprop_defaults(qi, flags, retdict); } @@ -5350,7 +5356,7 @@ static int qf_setprop_get_qfidx( // non-available list and add the new list at the end of the // stack. *newlist = true; - qf_idx = qi->qf_listcount > 0 ? qi->qf_listcount - 1 : 0; + qf_idx = qf_stack_empty(qi) ? 0 : qi->qf_listcount - 1; } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return INVALID_QFIDX; } else if (action != ' ') { @@ -5358,7 +5364,7 @@ static int qf_setprop_get_qfidx( } } else if (di->di_tv.v_type == VAR_STRING && strequal((const char *)di->di_tv.vval.v_string, "$")) { - if (qi->qf_listcount > 0) { + if (!qf_stack_empty(qi)) { qf_idx = qi->qf_listcount - 1; } else if (*newlist) { qf_idx = 0; @@ -5477,7 +5483,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, { dictitem_T *di; int retval = FAIL; - bool newlist = action == ' ' || qi->qf_curlist == qi->qf_listcount; + bool newlist = action == ' ' || qf_stack_empty(qi); int qf_idx = qf_setprop_get_qfidx(qi, what, action, &newlist); if (qf_idx == INVALID_QFIDX) { // List not found return FAIL; -- cgit From ced2a38ad4dce6898c800dd68026c7b1823e484e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 24 Oct 2019 07:26:22 -0400 Subject: tag: fix pvs/v547 error --- src/nvim/tag.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 0d42deed2b..20fd7caa51 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -988,9 +988,7 @@ add_llist_tags( cmd[len] = NUL; } - if ((dict = tv_dict_alloc()) == NULL) { - continue; - } + dict = tv_dict_alloc(); tv_list_append_dict(list, dict); tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); -- cgit From 0b771cd9aabfdee3ddc14c08af076f5df5cd0150 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 26 Oct 2019 01:17:21 -0400 Subject: vim-patch:8.1.0859: handle multibyte "%v" in 'errorformat' #11285 Problem: "%v" in 'errorformat' does handle multi-byte characters. Solution: Handle multi-byte characters. (Yegappan Lakshmanan, closes vim/vim#3700) https://github.com/vim/vim/commit/c45eb770a5988734ff2c572e5e2ce307158c33c8 --- src/nvim/quickfix.c | 24 ++-------------- src/nvim/testdir/test_quickfix.vim | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ec32a88f79..b951773860 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2437,9 +2437,6 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, char_u *qf_pattern) { linenr_T i; - char_u *line; - colnr_T screen_col; - colnr_T char_col; if (qf_pattern == NULL) { // Go to line with error, unless qf_lnum is 0. @@ -2451,26 +2448,11 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, curwin->w_cursor.lnum = i; } if (qf_col > 0) { - curwin->w_cursor.col = qf_col - 1; curwin->w_cursor.coladd = 0; if (qf_viscol == true) { - // Check each character from the beginning of the error - // line up to the error column. For each tab character - // found, reduce the error column value by the length of - // a tab character. - line = get_cursor_line_ptr(); - screen_col = 0; - for (char_col = 0; char_col < curwin->w_cursor.col; char_col++) { - if (*line == NUL) { - break; - } - if (*line++ == '\t') { - curwin->w_cursor.col -= 7 - (screen_col % 8); - screen_col += 8 - (screen_col % 8); - } else { - screen_col++; - } - } + coladvance(qf_col - 1); + } else { + curwin->w_cursor.col = qf_col - 1; } curwin->w_set_curswant = true; check_cursor(); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 4af986ffba..74f9e5b41d 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -3643,3 +3643,59 @@ func Test_curswant() call assert_equal(getcurpos()[4], virtcol('.')) cclose | helpclose endfunc + +" Test for parsing entries using visual screen column +func Test_viscol() + enew + call writefile(["Col1\tCol2\tCol3"], 'Xfile1') + edit Xfile1 + + " Use byte offset for column number + set efm& + cexpr "Xfile1:1:5:XX\nXfile1:1:9:YY\nXfile1:1:20:ZZ" + call assert_equal([5, 8], [col('.'), virtcol('.')]) + cnext + call assert_equal([9, 12], [col('.'), virtcol('.')]) + cnext + call assert_equal([14, 20], [col('.'), virtcol('.')]) + + " Use screen column offset for column number + set efm=%f:%l:%v:%m + cexpr "Xfile1:1:8:XX\nXfile1:1:12:YY\nXfile1:1:20:ZZ" + call assert_equal([5, 8], [col('.'), virtcol('.')]) + cnext + call assert_equal([9, 12], [col('.'), virtcol('.')]) + cnext + call assert_equal([14, 20], [col('.'), virtcol('.')]) + cexpr "Xfile1:1:6:XX\nXfile1:1:15:YY\nXfile1:1:24:ZZ" + call assert_equal([5, 8], [col('.'), virtcol('.')]) + cnext + call assert_equal([10, 16], [col('.'), virtcol('.')]) + cnext + call assert_equal([14, 20], [col('.'), virtcol('.')]) + + enew + call writefile(["Col1\täü\töß\tCol4"], 'Xfile1') + + " Use byte offset for column number + set efm& + cexpr "Xfile1:1:8:XX\nXfile1:1:11:YY\nXfile1:1:16:ZZ" + call assert_equal([8, 10], [col('.'), virtcol('.')]) + cnext + call assert_equal([11, 17], [col('.'), virtcol('.')]) + cnext + call assert_equal([16, 25], [col('.'), virtcol('.')]) + + " Use screen column offset for column number + set efm=%f:%l:%v:%m + cexpr "Xfile1:1:10:XX\nXfile1:1:17:YY\nXfile1:1:25:ZZ" + call assert_equal([8, 10], [col('.'), virtcol('.')]) + cnext + call assert_equal([11, 17], [col('.'), virtcol('.')]) + cnext + call assert_equal([16, 25], [col('.'), virtcol('.')]) + + enew | only + set efm& + call delete('Xfile1') +endfunc -- cgit From 034077ed1c1fa71aa15829ed2dfb27de4a5e5359 Mon Sep 17 00:00:00 2001 From: Jaehwang Jerry Jung Date: Fri, 25 Oct 2019 19:54:53 +0900 Subject: vim-patch:8.1.2173: searchit() has too many arguments Problem: Searchit() has too many arguments. Solution: Move optional arguments to a struct. Add the "wrapped" argument. https://github.com/vim/vim/commit/92ea26b925a0835badb0af2d5887238a4198cabb --- src/nvim/edit.c | 2 +- src/nvim/eval.c | 13 +++++++++++-- src/nvim/ex_docmd.c | 6 ++---- src/nvim/ex_getln.c | 7 +++++-- src/nvim/normal.c | 24 ++++++++++++++++-------- src/nvim/quickfix.c | 2 +- src/nvim/search.c | 37 +++++++++++++++++++++++-------------- src/nvim/search.h | 9 +++++++++ src/nvim/spell.c | 2 +- src/nvim/tag.c | 10 ++++------ src/nvim/version.c | 1 + 11 files changed, 74 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 62e4f77e6e..93f13c8d3f 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4118,7 +4118,7 @@ static int ins_compl_get_exp(pos_T *ini) compl_direction, compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, (linenr_T)0, NULL, NULL); + RE_LAST, NULL); } msg_silent--; if (!compl_started || set_match_pos) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 71ffb26cc2..fd83bc846b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14617,6 +14617,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) long time_limit = 0; int options = SEARCH_KEEP; int subpatnum; + searchit_arg_T sia; const char *const pat = tv_get_string(&argvars[0]); dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. @@ -14664,8 +14665,11 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } pos = save_cursor = curwin->w_cursor; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = (linenr_T)lnum_stop; + sia.sa_tm = &tm; subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, - options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL); + options, RE_SEARCH, &sia); if (subpatnum != FAIL) { if (flags & SP_SUBPAT) retval = subpatnum; @@ -15237,8 +15241,13 @@ do_searchpair( clearpos(&foundpos); pat = pat3; for (;; ) { + searchit_arg_T sia; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = lnum_stop; + sia.sa_tm = &tm; + n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, lnum_stop, &tm, NULL); + options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL break; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 16751b3a53..da0184aa45 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3750,8 +3750,7 @@ static linenr_T get_address(exarg_T *eap, curwin->w_cursor.col = 0; } searchcmdlen = 0; - if (!do_search(NULL, c, cmd, 1L, - SEARCH_HIS | SEARCH_MSG, NULL, NULL)) { + if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3788,8 +3787,7 @@ static linenr_T get_address(exarg_T *eap, pos.coladd = 0; if (searchit(curwin, curbuf, &pos, NULL, *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, - i, (linenr_T)0, NULL, NULL) != FAIL) { + (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) { lnum = pos.lnum; } else { cmd = NULL; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5235b9e648..f2665ca0b5 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1075,7 +1075,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) int found = searchit(curwin, curbuf, &t, NULL, next_match ? FORWARD : BACKWARD, pat, s->count, search_flags, - RE_SEARCH, 0, NULL, NULL); + RE_SEARCH, NULL); emsg_off--; ui_busy_stop(); if (found) { @@ -1818,6 +1818,7 @@ static int command_line_changed(CommandLineState *s) if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { pos_T end_pos; proftime_T tm; + searchit_arg_T sia; // if there is a character waiting, search and redraw later if (char_avail()) { @@ -1844,8 +1845,10 @@ static int command_line_changed(CommandLineState *s) if (!p_hls) { search_flags += SEARCH_KEEP; } + memset(&sia, 0, sizeof(sia)); + sia.sa_tm = &tm; i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - search_flags, &tm, NULL); + search_flags, &sia); emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f6222f9d3f..d1c6362931 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3794,7 +3794,7 @@ find_decl ( valid = false; (void)valid; // Avoid "dead assignment" warning. t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD, - pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL, NULL); + pat, 1L, searchflags, RE_LAST, NULL); if (curwin->w_cursor.lnum >= old_pos.lnum) { t = false; // match after start is failure too } @@ -4936,7 +4936,8 @@ static void nv_ident(cmdarg_T *cap) /* put pattern in search history */ init_history(); add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); - (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0, + NULL); } else { g_tag_at_cursor = true; do_cmdline_cmd(buf); @@ -5363,7 +5364,7 @@ static void nv_search(cmdarg_T *cap) (void)normal_search(cap, cap->cmdchar, cap->searchbuf, (cap->arg || !equalpos(save_cursor, curwin->w_cursor)) - ? 0 : SEARCH_MARK); + ? 0 : SEARCH_MARK, NULL); } /* @@ -5373,14 +5374,15 @@ static void nv_search(cmdarg_T *cap) static void nv_next(cmdarg_T *cap) { pos_T old = curwin->w_cursor; - int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg); + int wrapped = false; + int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, &wrapped); - if (i == 1 && equalpos(old, curwin->w_cursor)) { + if (i == 1 && !wrapped && equalpos(old, curwin->w_cursor)) { // Avoid getting stuck on the current cursor position, which can happen when // an offset is given and the cursor is on the last char in the buffer: // Repeat with count + 1. cap->count1 += 1; - (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg); + (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, NULL); cap->count1 -= 1; } } @@ -5394,18 +5396,24 @@ static int normal_search( cmdarg_T *cap, int dir, char_u *pat, - int opt /* extra flags for do_search() */ + int opt, // extra flags for do_search() + int *wrapped ) { int i; + searchit_arg_T sia; cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; cap->oap->use_reg_one = true; curwin->w_set_curswant = true; + memset(&sia, 0, sizeof(sia)); i = do_search(cap->oap, dir, pat, cap->count1, - opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, NULL, NULL); + opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); + if (wrapped != NULL) { + *wrapped = sia.sa_wrapped; + } if (i == 0) { clearop(cap->oap); } else { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index b951773860..52dd3c1c98 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2463,7 +2463,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, // Move the cursor to the first line in the buffer pos_T save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL, NULL)) { + if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { curwin->w_cursor = save_cursor; } } diff --git a/src/nvim/search.c b/src/nvim/search.c index fb31e76986..d5cb3d78b5 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -516,19 +516,17 @@ void last_pat_prog(regmmatch_T *regmatch) /// the index of the first matching /// subpattern plus one; one if there was none. int searchit( - win_T *win, /* window to search in, can be NULL for a - buffer without a window! */ + win_T *win, // window to search in; can be NULL for a + // buffer without a window! buf_T *buf, pos_T *pos, - pos_T *end_pos, // set to end of the match, unless NULL + pos_T *end_pos, // set to end of the match, unless NULL Direction dir, char_u *pat, long count, int options, - int pat_use, // which pattern to use when "pat" is empty - linenr_T stop_lnum, // stop after this line number when != 0 - proftime_T *tm, // timeout limit or NULL - int *timed_out // set when timed out or NULL + int pat_use, // which pattern to use when "pat" is empty + searchit_arg_T *extra_arg // optional extra arguments, can be NULL ) { int found; @@ -548,7 +546,16 @@ int searchit( int submatch = 0; bool first_match = true; int save_called_emsg = called_emsg; - int break_loop = FALSE; + int break_loop = false; + linenr_T stop_lnum = 0; // stop after this line number when != 0 + proftime_T *tm = NULL; // timeout limit or NULL + int *timed_out = NULL; // set when timed out or NULL + + if (extra_arg != NULL) { + stop_lnum = extra_arg->sa_stop_lnum; + tm = extra_arg->sa_tm; + timed_out = &extra_arg->sa_timed_out; + } if (search_regcomp(pat, RE_SEARCH, pat_use, (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { @@ -889,6 +896,9 @@ int searchit( give_warning((char_u *)_(dir == BACKWARD ? top_bot_msg : bot_top_msg), true); } + if (extra_arg != NULL) { + extra_arg->sa_wrapped = true; + } } if (got_int || called_emsg || (timed_out != NULL && *timed_out) @@ -983,8 +993,7 @@ int do_search( char_u *pat, long count, int options, - proftime_T *tm, // timeout limit or NULL - int *timed_out // flag set on timeout or NULL + searchit_arg_T *sia // optional arguments or NULL ) { pos_T pos; /* position of the last match */ @@ -1271,7 +1280,7 @@ int do_search( & (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG + SEARCH_START + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))), - RE_LAST, (linenr_T)0, tm, timed_out); + RE_LAST, sia); if (dircp != NULL) { *dircp = dirc; // restore second '/' or '?' for normal_cmd() @@ -4078,7 +4087,7 @@ current_search( result = searchit(curwin, curbuf, &pos, &end_pos, (dir ? FORWARD : BACKWARD), spats[last_idx].pat, i ? count : 1, - SEARCH_KEEP | flags, RE_SEARCH, 0, NULL, NULL); + SEARCH_KEEP | flags, RE_SEARCH, NULL); // First search may fail, but then start searching from the // beginning of the file (cursor might be on the search match) @@ -4175,7 +4184,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, flag = SEARCH_START; } if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1, - SEARCH_KEEP + flag, RE_SEARCH, 0, NULL, NULL) != FAIL) { + SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if // start and end are in the same position. called_emsg = false; @@ -4265,7 +4274,7 @@ static void search_stat(int dirc, pos_T *pos, start = profile_setlimit(20L); while (!got_int && searchit(curwin, curbuf, &lastpos, NULL, FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, - (linenr_T)0, NULL, NULL) != FAIL) { + NULL) != FAIL) { // Stop after passing the time limit. if (profile_passed_limit(start)) { cnt = OUT_OF_TIME; diff --git a/src/nvim/search.h b/src/nvim/search.h index cb094aab8c..0366aee8a1 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -70,6 +70,15 @@ typedef struct spat { dict_T *additional_data; ///< Additional data from ShaDa file. } SearchPattern; +/// Optional extra arguments for searchit(). +typedef struct { + linenr_T sa_stop_lnum; ///< stop after this line number when != 0 + proftime_T *sa_tm; ///< timeout limit or NULL + int sa_timed_out; ///< set when timed out + int sa_wrapped; ///< search wrapped around +} searchit_arg_T; + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.h.generated.h" #endif diff --git a/src/nvim/spell.c b/src/nvim/spell.c index a3c1746383..687c86b4a8 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3031,7 +3031,7 @@ void ex_spellrepall(exarg_T *eap) sub_nlines = 0; curwin->w_cursor.lnum = 0; while (!got_int) { - if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL, NULL) == 0 + if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 || u_save_cursor() == FAIL) { break; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 20fd7caa51..a3967c70b5 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2813,7 +2813,7 @@ static int jumpto_tag( save_lnum = curwin->w_cursor.lnum; curwin->w_cursor.lnum = 0; /* start search before first line */ if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, - search_options, NULL, NULL)) { + search_options, NULL)) { retval = OK; } else { int found = 1; @@ -2824,20 +2824,18 @@ static int jumpto_tag( */ p_ic = TRUE; if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1, - search_options, NULL, NULL)) { + search_options, NULL)) { // Failed to find pattern, take a guess: "^func (" found = 2; (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, - search_options, NULL, NULL)) { + if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \ Date: Fri, 25 Oct 2019 21:25:23 +0900 Subject: vim-patch:8.1.2207: "gn" doesn't work quite right Problem: "gn" doesn't work quite right. (Jaehwang Jerry Jung) Solution: Improve and simplify the search logic. (Christian Brabandt, closes vim/vim#5103, closes vim/vim#5075) https://github.com/vim/vim/commit/edaad6e0a0e3c1fcb6a5c2771e647c52475bb19c --- src/nvim/search.c | 28 +++++++++------------------- src/nvim/testdir/test_gn.vim | 21 +++++++++++++++++++++ src/nvim/version.c | 1 + 3 files changed, 31 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index d5cb3d78b5..893b5f7d89 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4049,23 +4049,20 @@ current_search( pos_T pos; // position after the pattern int result; // result of various function calls + orig_pos = pos = curwin->w_cursor; if (VIsual_active) { - orig_pos = pos = curwin->w_cursor; - // Searching further will extend the match. if (forward) { incl(&pos); } else { decl(&pos); } - } else { - orig_pos = pos = curwin->w_cursor; } // Is the pattern is zero-width?, this time, don't care about the direction - int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor, - FORWARD); - if (one_char == -1) { + int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, + FORWARD); + if (zero_width == -1) { p_ws = old_p_ws; return FAIL; /* pattern not found */ } @@ -4079,7 +4076,7 @@ current_search( int dir = forward ? i : !i; int flags = 0; - if (!dir && !one_char) { + if (!dir && !zero_width) { flags = SEARCH_END; } end_pos = pos; @@ -4109,7 +4106,6 @@ current_search( ml_get(curwin->w_buffer->b_ml.ml_line_count)); } } - p_ws = old_p_ws; } pos_T start_pos = pos; @@ -4124,11 +4120,12 @@ current_search( curwin->w_cursor = end_pos; if (lt(VIsual, end_pos)) { dec_cursor(); + } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) { + curwin->w_cursor = pos; // put the cursor on the start of the match } VIsual_active = true; VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // Update the inversion. if (*p_sel == 'e') { // Correction for exclusive selection depends on the direction. if (forward && ltoreq(VIsual, curwin->w_cursor)) { @@ -4155,8 +4152,8 @@ current_search( /// else from position "cur". /// "direction" is FORWARD or BACKWARD. /// Returns TRUE, FALSE or -1 for failure. -static int is_one_char(char_u *pattern, bool move, pos_T *cur, - Direction direction) +static int +is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) { regmmatch_T regmatch; int nmatched = 0; @@ -4204,13 +4201,6 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, result = (nmatched != 0 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum && regmatch.startpos[0].col == regmatch.endpos[0].col); - // one char width - if (!result - && nmatched != 0 - && inc(&pos) >= 0 - && pos.col == regmatch.endpos[0].col) { - result = true; - } } } diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 5e74289b00..77c5e8ecd5 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -129,6 +129,27 @@ func Test_gn_command() call assert_equal([' nnoremap', '', 'match'], getline(1,'$')) sil! %d_ + " make sure it works correctly for one-char wide search items + call setline('.', ['abcdefghi']) + let @/ = 'a' + exe "norm! 0fhvhhgNgU" + call assert_equal(['ABCDEFGHi'], getline(1,'$')) + call setline('.', ['abcdefghi']) + let @/ = 'b' + exe "norm! 0fhvhhgngU" + call assert_equal(['abcdefghi'], getline(1,'$')) + sil! %d _ + call setline('.', ['abcdefghi']) + let @/ = 'f' + exe "norm! 0vllgngU" + call assert_equal(['ABCDEFghi'], getline(1,'$')) + sil! %d _ + call setline('.', ['12345678']) + let @/ = '5' + norm! gg0f7vhhhhgnd + call assert_equal(['12348'], getline(1,'$')) + sil! %d _ + set wrapscan&vim set belloff&vim endfu diff --git a/src/nvim/version.c b/src/nvim/version.c index 67fc0b976f..7cb2334012 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -68,6 +68,7 @@ NULL // clang-format off static const int included_patches[] = { + 2207, 2173, 1850, 1849, -- cgit From 6dcc1d100572b462f75f64f7e98a0b5d80144cba Mon Sep 17 00:00:00 2001 From: Jaehwang Jerry Jung Date: Sat, 26 Oct 2019 22:32:25 +0900 Subject: vim-patch:8.1.2218: "gN" is off by one in Visual mode Problem: "gN" is off by one in Visual mode. Solution: Check moving forward. (Christian Brabandt, vim/vim#5075) https://github.com/vim/vim/commit/453c19257f6d97904ec2e3823e88e63c983f2f9a --- src/nvim/search.c | 4 ++-- src/nvim/testdir/test_gn.vim | 5 +++++ src/nvim/version.c | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 893b5f7d89..c4c8633ed9 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4118,7 +4118,7 @@ current_search( // put cursor on last character of match curwin->w_cursor = end_pos; - if (lt(VIsual, end_pos)) { + if (lt(VIsual, end_pos) && forward) { dec_cursor(); } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) { curwin->w_cursor = pos; // put the cursor on the start of the match @@ -4147,7 +4147,7 @@ current_search( return OK; } -/// Check if the pattern is one character long or zero-width. +/// Check if the pattern is zero-width. /// If move is true, check from the beginning of the buffer, /// else from position "cur". /// "direction" is FORWARD or BACKWARD. diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 77c5e8ecd5..834397126f 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -149,6 +149,11 @@ func Test_gn_command() norm! gg0f7vhhhhgnd call assert_equal(['12348'], getline(1,'$')) sil! %d _ + call setline('.', ['12345678']) + let @/ = '5' + norm! gg0f2vf7gNd + call assert_equal(['1678'], getline(1,'$')) + sil! %d _ set wrapscan&vim set belloff&vim diff --git a/src/nvim/version.c b/src/nvim/version.c index 7cb2334012..db02279fa3 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -68,6 +68,7 @@ NULL // clang-format off static const int included_patches[] = { + 2218, 2207, 2173, 1850, -- cgit From c3d81a490224905e3d20dc15ad3e7bd3facf9ed7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 26 Oct 2019 20:06:29 -0400 Subject: vim-patch:8.1.2220: :cfile does not abort like other quickfix commands Problem: :cfile does not abort like other quickfix commands. Solution: Abort when desired. Add tests for aborting. (Yegappan Lakshmanan, closes vim/vim#5121) https://github.com/vim/vim/commit/6a0cc916bd3cd6c2fd88b2972c92ade225603229 --- src/nvim/quickfix.c | 11 +++-- src/nvim/testdir/test_quickfix.vim | 92 +++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 52dd3c1c98..8f891751d6 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4152,10 +4152,15 @@ void ex_cfile(exarg_T *eap) case CMD_laddfile: au_name = (char_u *)"laddfile"; break; default: break; } - if (au_name != NULL) - apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, FALSE, curbuf); - if (*eap->arg != NUL) + if (au_name != NULL + && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, false, curbuf)) { + if (aborting()) { + return; + } + } + if (*eap->arg != NUL) { set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + } char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 74f9e5b41d..6f58b0084c 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2515,7 +2515,7 @@ func Test_file_from_copen() cclose augroup! QF_Test -endfunction +endfunc func Test_resize_from_copen() augroup QF_Test @@ -2534,6 +2534,94 @@ func Test_resize_from_copen() endtry endfunc +" Test for aborting quickfix commands using QuickFixCmdPre +func Xtest_qfcmd_abort(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + + " cexpr/lexpr + let e = '' + try + Xexpr ["F1:10:Line10", "F2:20:Line20"] + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " cfile/lfile + call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1') + let e = '' + try + Xfile Xfile1 + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + call delete('Xfile1') + + " cgetbuffer/lgetbuffer + enew! + call append(0, ["F1:10:Line10", "F2:20:Line20"]) + let e = '' + try + Xgetbuffer + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + enew! + + " vimgrep/lvimgrep + let e = '' + try + Xvimgrep /func/ test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " helpgrep/lhelpgrep + let e = '' + try + Xhelpgrep quickfix + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " grep/lgrep + if has('unix') + let e = '' + try + silent Xgrep func test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + endif +endfunc + +func Test_qfcmd_abort() + augroup QF_Test + au! + autocmd QuickFixCmdPre * throw "AbortCmd" + augroup END + + call Xtest_qfcmd_abort('c') + call Xtest_qfcmd_abort('l') + + augroup QF_Test + au! + augroup END +endfunc + " Tests for the quickfix buffer b:changedtick variable func Xchangedtick_tests(cchar) call s:setup_commands(a:cchar) @@ -3699,3 +3787,5 @@ func Test_viscol() set efm& call delete('Xfile1') endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From 7c26be61bb5a405f28ae230cd3602ecb6e30a3a9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 27 Oct 2019 11:28:13 -0400 Subject: vim-patch:8.1.1245: ":copen 10" sets height in full-height window Problem: ":copen 10" sets height in full-height window. (Daniel Hahler) Solution: Don't set the height if the quickfix window is full height. (closes vim/vim#4325) https://github.com/vim/vim/commit/36d502225c3ec5e8b30771d58ee20171ce564b2f --- src/nvim/quickfix.c | 3 ++- src/nvim/testdir/test_quickfix.vim | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 8f891751d6..d900ea3c70 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3164,7 +3164,8 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, if (sz != win->w_width) { win_setwidth(sz); } - } else if (sz != win->w_height) { + } else if (sz != win->w_height + && win->w_height + win->w_status_height < cmdline_row) { win_setheight(sz); } } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 6f58b0084c..2eb20e61bd 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -273,6 +273,15 @@ func Test_cwindow() call XwindowTests('l') endfunc +func Test_copenHeight() + copen + wincmd H + let height = winheight(0) + copen 10 + call assert_equal(height, winheight(0)) + quit +endfunc + " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. func XfileTests(cchar) -- cgit From 0f7a645f544cb02d6a48749a5f60f8c43e89fd3d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 27 Oct 2019 11:31:38 -0400 Subject: vim-patch:8.1.2154: quickfix window height wrong when there is a tabline Problem: Quickfix window height wrong when there is a tabline. (Daniel Hahler) Solution: Take the tabline height into account. (closes vim/vim#5058) https://github.com/vim/vim/commit/1142a31b8c44c4e7dbf28a83ae52995113b37917 --- src/nvim/quickfix.c | 3 ++- src/nvim/testdir/test_quickfix.vim | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index d900ea3c70..4ca9ca2a3e 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3165,7 +3165,8 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, win_setwidth(sz); } } else if (sz != win->w_height - && win->w_height + win->w_status_height < cmdline_row) { + && (win->w_height + win->w_status_height + tabline_height() + < cmdline_row)) { win_setheight(sz); } } diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 2eb20e61bd..31b0c0cd2c 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -282,6 +282,18 @@ func Test_copenHeight() quit endfunc +func Test_copenHeight_tabline() + set tabline=foo showtabline=2 + copen + wincmd H + let height = winheight(0) + copen 10 + call assert_equal(height, winheight(0)) + quit + set tabline& showtabline& +endfunc + + " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. func XfileTests(cchar) -- cgit From 8ee7c94a92598d46b488b7fe3b1a5cff6b1bf94a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 25 Aug 2019 22:01:35 +0200 Subject: lua: add vim.fn.{func} for direct access to vimL function compared to vim.api.|nvim_call_function|, this fixes some typing issues due to the indirect conversion via the API. float values are preserved as such (fixes #9389) as well as empty dicts/arrays. Ref https://github.com/norcalli/nvim.lua for the call syntax --- src/nvim/lua/converter.c | 14 +++++++++-- src/nvim/lua/executor.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++- src/nvim/lua/vim.lua | 12 +++++++++ 3 files changed, 86 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9665655e74..844232c64a 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -401,6 +401,8 @@ nlua_pop_typval_table_processing_end: return ret; } +static bool typval_conv_special = false; + #define TYPVAL_ENCODE_ALLOW_SPECIALS true #define TYPVAL_ENCODE_CONV_NIL(tv) \ @@ -439,7 +441,13 @@ nlua_pop_typval_table_processing_end: lua_createtable(lstate, 0, 0) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) + do { \ + if (typval_conv_special) { \ + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ + } else { \ + lua_createtable(lstate, 0, 0); \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ @@ -548,9 +556,11 @@ nlua_pop_typval_table_processing_end: /// @param[in] tv typval_T to convert. /// /// @return true in case of success, false otherwise. -bool nlua_push_typval(lua_State *lstate, typval_T *const tv) +bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) { + typval_conv_special = special; const int initial_size = lua_gettop(lstate); + if (!lua_checkstack(lstate, initial_size + 2)) { emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 127458fe39..b7c1d57964 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -295,6 +295,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // in_fast_event lua_pushcfunction(lstate, &nlua_in_fast_event); lua_setfield(lstate, -2, "in_fast_event"); + // call + lua_pushcfunction(lstate, &nlua_call); + lua_setfield(lstate, -2, "call"); // vim.loop luv_set_loop(lstate, &main_loop.uv); @@ -539,6 +542,64 @@ int nlua_in_fast_event(lua_State *lstate) return 1; } +int nlua_call(lua_State *lstate) +{ + Error err = ERROR_INIT; + size_t name_len; + const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len); + int nargs = lua_gettop(lstate)-1; + if (nargs > MAX_FUNC_ARGS) { + return luaL_error(lstate, "Function called with too many arguments"); + } + + typval_T vim_args[MAX_FUNC_ARGS + 1]; + int i = 0; // also used for freeing the variables + for (; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+2); + if (!nlua_pop_typval(lstate, &vim_args[i])) { + api_set_error(&err, kErrorTypeException, + "error converting argument %d", i+1); + goto free_vim_args; + } + } + + // TODO(bfredl): this should be simplified in error handling refactor + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list = NULL; + msg_list = &private_msg_list; + + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + did_emsg = false; + + try_start(); + typval_T rettv; + int dummy; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (see above) to capture abort-causing non-exception errors. + (void)call_func(name, (int)name_len, &rettv, nargs, + vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, NULL); + if (!try_end(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); + + msg_list = saved_msg_list; + +free_vim_args: + while (i > 0) { + tv_clear(&vim_args[--i]); + } + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + return 1; +} + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// @@ -623,7 +684,7 @@ void executor_eval_lua(const String str, typval_T *const arg, if (arg->v_type == VAR_UNKNOWN) { lua_pushnil(lstate); } else { - nlua_push_typval(lstate, arg); + nlua_push_typval(lstate, arg, true); } if (lua_pcall(lstate, 1, 1, 0)) { nlua_error(lstate, diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b67762e48e..5514819a02 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -242,6 +242,17 @@ local function __index(t, key) end end + +-- vim.fn.{func}(...) +local function fn_index(t, key) + local function func(...) + return vim.call(key, ...) + end + t[key] = func + return func +end +local fn = setmetatable({}, {__index=fn_index}) + local module = { _update_package_paths = _update_package_paths, _os_proc_children = _os_proc_children, @@ -249,6 +260,7 @@ local module = { _system = _system, paste = paste, schedule_wrap = schedule_wrap, + fn=fn, } setmetatable(module, { -- cgit From a123050c1a8db57d0d34464eb61bc3248747725a Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Wed, 18 Sep 2019 16:00:06 -0400 Subject: vim-patch:8.1.0252: quickfix functions are too long Problem: Quickfix functions are too long. Solution: Refactor. (Yegappan Lakshmanan, closes vim/vim#2950) https://github.com/vim/vim/commit/de3b3677f7eace66be454196db0fbf710cfc8c5e --- src/nvim/quickfix.c | 717 +++++++++++++++++++++++-------------- src/nvim/testdir/test_quickfix.vim | 4 +- 2 files changed, 444 insertions(+), 277 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 8f891751d6..f14d9f445b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -163,6 +163,8 @@ enum { QF_MULTISCAN = 5, }; +// State information used to parse lines and add entries to a quickfix/location +// list. typedef struct { char_u *linebuf; size_t linelen; @@ -266,92 +268,95 @@ static struct fmtpattern }; // Convert an errorformat pattern to a regular expression pattern. -// See fmt_pat definition above for the list of supported patterns. -static char_u *fmtpat_to_regpat( - const char_u *efmp, - efm_T *fmt_ptr, +// See fmt_pat definition above for the list of supported patterns. The +// pattern specifier is supplied in "efmpat". The converted pattern is stored +// in "regpat". Returns a pointer to the location after the pattern. +static char_u * efmpat_to_regpat( + const char_u *efmpat, + char_u *regpat, + efm_T *efminfo, int idx, int round, - char_u *ptr, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - if (fmt_ptr->addr[idx]) { + if (efminfo->addr[idx]) { // Each errorformat pattern can occur only once snprintf((char *)errmsg, errmsglen, - _("E372: Too many %%%c in format string"), *efmp); + _("E372: Too many %%%c in format string"), *efmpat); EMSG(errmsg); return NULL; } if ((idx && idx < 6 - && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) + && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL) || (idx == 6 - && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { + && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) { snprintf((char *)errmsg, errmsglen, - _("E373: Unexpected %%%c in format string"), *efmp); + _("E373: Unexpected %%%c in format string"), *efmpat); EMSG(errmsg); return NULL; } - fmt_ptr->addr[idx] = (char_u)++round; - *ptr++ = '\\'; - *ptr++ = '('; + efminfo->addr[idx] = (char_u)++round; + *regpat++ = '\\'; + *regpat++ = '('; #ifdef BACKSLASH_IN_FILENAME - if (*efmp == 'f') { + if (*efmpat == 'f') { // Also match "c:" in the file name, even when // checking for a colon next: "%f:". // "\%(\a:\)\=" - STRCPY(ptr, "\\%(\\a:\\)\\="); - ptr += 10; + STRCPY(regpat, "\\%(\\a:\\)\\="); + regpat += 10; } #endif - if (*efmp == 'f' && efmp[1] != NUL) { - if (efmp[1] != '\\' && efmp[1] != '%') { + if (*efmpat == 'f' && efmpat[1] != NUL) { + if (efmpat[1] != '\\' && efmpat[1] != '%') { // A file name may contain spaces, but this isn't // in "\f". For "%f:%l:%m" there may be a ":" in // the file name. Use ".\{-1,}x" instead (x is // the next character), the requirement that :999: // follows should work. - STRCPY(ptr, ".\\{-1,}"); - ptr += 7; + STRCPY(regpat, ".\\{-1,}"); + regpat += 7; } else { // File name followed by '\\' or '%': include as // many file name chars as possible. - STRCPY(ptr, "\\f\\+"); - ptr += 4; + STRCPY(regpat, "\\f\\+"); + regpat += 4; } } else { char_u *srcptr = (char_u *)fmt_pat[idx].pattern; - while ((*ptr = *srcptr++) != NUL) { - ptr++; + while ((*regpat = *srcptr++) != NUL) { + regpat++; } } - *ptr++ = '\\'; - *ptr++ = ')'; + *regpat++ = '\\'; + *regpat++ = ')'; - return ptr; + return regpat; } // Convert a scanf like format in 'errorformat' to a regular expression. -static char_u *scanf_fmt_to_regpat( +// Returns a pointer to the location after the pattern. +static char_u * scanf_fmt_to_regpat( + const char_u **pefmp, const char_u *efm, int len, - const char_u **pefmp, - char_u *ptr, + char_u *regpat, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { const char_u *efmp = *pefmp; - if (*++efmp == '[' || *efmp == '\\') { - if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. + if (*efmp == '[' || *efmp == '\\') { + if ((*regpat++ = *efmp) == '[') { // %*[^a-z0-9] etc. if (efmp[1] == '^') { - *ptr++ = *++efmp; + *regpat++ = *++efmp; } if (efmp < efm + len) { - *ptr++ = *++efmp; // could be ']' - while (efmp < efm + len && (*ptr++ = *++efmp) != ']') { + *regpat++ = *++efmp; // could be ']' + while (efmp < efm + len && (*regpat++ = *++efmp) != ']') { } if (efmp == efm + len) { EMSG(_("E374: Missing ] in format string")); @@ -359,10 +364,10 @@ static char_u *scanf_fmt_to_regpat( } } } else if (efmp < efm + len) { // %*\D, %*\s etc. - *ptr++ = *++efmp; + *regpat++ = *++efmp; } - *ptr++ = '\\'; - *ptr++ = '+'; + *regpat++ = '\\'; + *regpat++ = '+'; } else { // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? snprintf((char *)errmsg, errmsglen, @@ -373,31 +378,27 @@ static char_u *scanf_fmt_to_regpat( *pefmp = efmp; - return ptr; + return regpat; } // Analyze/parse an errorformat prefix. -static int efm_analyze_prefix(const char_u **pefmp, efm_T *fmt_ptr, - char_u *errmsg, size_t errmsglen) +static char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, + char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - const char_u *efmp = *pefmp; - if (vim_strchr((char_u *)"+-", *efmp) != NULL) { - fmt_ptr->flags = *efmp++; + efminfo->flags = *efmp++; } if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { - fmt_ptr->prefix = *efmp; + efminfo->prefix = *efmp; } else { snprintf((char *)errmsg, errmsglen, _("E376: Invalid %%%c in format string prefix"), *efmp); EMSG(errmsg); - return FAIL; + return NULL; } - *pefmp = efmp; - - return OK; + return efmp; } @@ -420,16 +421,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } } if (idx < FMT_PATTERNS) { - ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, - errmsg, errmsglen); + ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, errmsg, + errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } round++; } else if (*efmp == '*') { - ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg, errmsglen); + efmp++; + ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg, errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } } else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) { *ptr++ = *efmp; // regexp magic characters @@ -438,14 +440,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } else if (*efmp == '>') { fmt_ptr->conthere = true; } else if (efmp == efm + 1) { // analyse prefix - if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg, errmsglen) == FAIL) { - return -1; + // prefix is allowed only at the beginning of the errorformat + // option part + efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg, errmsglen); + if (efmp == NULL) { + return FAIL; } } else { snprintf((char *)errmsg, CMDBUFFSIZE + 1, _("E377: Invalid %%%c in format string"), *efmp); EMSG(errmsg); - return -1; + return FAIL; } } else { // copy normal character if (*efmp == '\\' && efmp + 1 < efm + len) { @@ -461,7 +466,7 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, *ptr++ = '$'; *ptr = NUL; - return 0; + return OK; } static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls @@ -477,7 +482,42 @@ static void free_efm_list(efm_T **efm_first) fmt_start = NULL; } -// Parse 'errorformat' option +// Compute the size of the buffer used to convert a 'errorformat' pattern into +// a regular expression pattern. +static size_t efm_regpat_bufsz(char_u *efm) +{ + size_t sz; + + sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); + for (int i = FMT_PATTERNS - 1; i >= 0; ) { + sz += STRLEN(fmt_pat[i--].pattern); + } +#ifdef BACKSLASH_IN_FILENAME + sz += 12; // "%f" can become twelve chars longer (see efm_to_regpat) +#else + sz += 2; // "%f" can become two chars longer +#endif + + return sz; +} + +// Return the length of a 'errorformat' option part (separated by ","). +static int efm_option_part_len(char_u *efm) +{ + int len; + + for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { + if (efm[len] == '\\' && efm[len + 1] != NUL) { + len++; + } + } + + return len; +} + +// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option +// are parsed and converted to regular expressions. Returns information about +// the parsed 'errorformat' option. static efm_T * parse_efm_option(char_u *efm) { efm_T *fmt_ptr = NULL; @@ -489,16 +529,8 @@ static efm_T * parse_efm_option(char_u *efm) char_u *errmsg = xmalloc(errmsglen); // Get some space to modify the format string into. - size_t i = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); - for (int round = FMT_PATTERNS - 1; round >= 0; ) { - i += STRLEN(fmt_pat[round--].pattern); - } -#ifdef BACKSLASH_IN_FILENAME - i += 12; // "%f" can become twelve chars longer (see efm_to_regpat) -#else - i += 2; // "%f" can become two chars longer -#endif - char_u *fmtstr = xmalloc(i); + size_t sz = efm_regpat_bufsz(efm); + char_u *fmtstr = xmalloc(sz); while (efm[0] != NUL) { // Allocate a new eformat structure and put it at the end of the list @@ -511,13 +543,9 @@ static efm_T * parse_efm_option(char_u *efm) fmt_last = fmt_ptr; // Isolate one part in the 'errorformat' option - for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { - if (efm[len] == '\\' && efm[len + 1] != NUL) { - len++; - } - } + len = efm_option_part_len(efm); - if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == -1) { + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == FAIL) { goto parse_efm_error; } if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) { @@ -543,6 +571,7 @@ parse_efm_end: return fmt_first; } +// Allocate more memory for the line buffer used for parsing lines. static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz) { // If the line exceeds LINE_MAXLEN exclude the last @@ -1011,7 +1040,7 @@ qf_init_ext( } else { // Adding to existing list, use last entry. adding = true; - if (qi->qf_lists[qf_idx].qf_count > 0) { + if (!qf_list_empty(qi, qf_idx)) { old_last = qi->qf_lists[qf_idx].qf_last; } } @@ -1183,6 +1212,219 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; } +// Parse the match for filename ('%f') pattern in regmatch. +// Return the matched value in "fields->namebuf". +static int qf_parse_fmt_f(regmatch_T *rmp, + int midx, + qffields_T *fields, + int prefix) +{ + char_u c; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + + // Expand ~/file and $HOME/file to full path. + c = *rmp->endp[midx]; + *rmp->endp[midx] = NUL; + expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); + *rmp->endp[midx] = c; + + // For separate filename patterns (%O, %P and %Q), the specified file + // should exist. + if (vim_strchr((char_u *)"OPQ", prefix) != NULL + && !os_path_exists(fields->namebuf)) { + return QF_FAIL; + } + + return QF_OK; +} + +// Parse the match for error number ('%n') pattern in regmatch. +// Return the matched value in "fields->enr". +static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->enr = (int)atol((char *)rmp->startp[midx]); + return QF_OK; +} + +// Parse the match for line number (%l') pattern in regmatch. +// Return the matched value in "fields->lnum". +static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->lnum = atol((char *)rmp->startp[midx]); + return QF_OK; +} + +// Parse the match for column number ('%c') pattern in regmatch. +// Return the matched value in "fields->col". +static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->col = (int)atol((char *)rmp->startp[midx]); + return QF_OK; +} + +// Parse the match for error type ('%t') pattern in regmatch. +// Return the matched value in "fields->type". +static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->type = *rmp->startp[midx]; + return QF_OK; +} + +// Parse the match for '%+' format pattern. The whole matching line is included +// in the error string. Return the matched line in "fields->errmsg". +static int qf_parse_fmt_plus(char_u *linebuf, + size_t linelen, + qffields_T *fields) +{ + if (linelen >= fields->errmsglen) { + // linelen + null terminator + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + STRLCPY(fields->errmsg, linebuf, linelen + 1); + return QF_OK; +} + +// Parse the match for error message ('%m') pattern in regmatch. +// Return the matched value in "fields->errmsg". +static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len >= fields->errmsglen) { + // len + null terminator + fields->errmsg = xrealloc(fields->errmsg, len + 1); + fields->errmsglen = len + 1; + } + STRLCPY(fields->errmsg, rmp->startp[midx], len + 1); + return QF_OK; +} + +// Parse the match for rest of a single-line file message ('%r') pattern. +// Return the matched value in "tail". +static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + *tail = rmp->startp[midx]; + return QF_OK; +} + + +// Parse the match for the pointer line ('%p') pattern in regmatch. +// Return the matched value in "fields->col". +static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) +{ + char_u *match_ptr; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + fields->col = 0; + for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx]; + match_ptr++) { + fields->col++; + if (*match_ptr == TAB) { + fields->col += 7; + fields->col -= fields->col % 8; + } + } + fields->col++; + fields->use_viscol = true; + return QF_OK; +} + + +// Parse the match for the virtual column number ('%v') pattern in regmatch. +// Return the matched value in "fields->col". +static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->col = (int)atol((char *)rmp->startp[midx]); + fields->use_viscol = true; + return QF_OK; +} + + +// Parse the match for the search text ('%s') pattern in regmatch. +// Return the matched value in "fields->pattern". +static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len > CMDBUFFSIZE - 5) { + len = CMDBUFFSIZE - 5; + } + STRCPY(fields->pattern, "^\\V"); + xstrlcat((char *)fields->pattern, (char *)rmp->startp[midx], len + 4); + fields->pattern[len + 3] = '\\'; + fields->pattern[len + 4] = '$'; + fields->pattern[len + 5] = NUL; + return QF_OK; +} + +// Parse the match for the module ('%o') pattern in regmatch. +// Return the matched value in "fields->module". +static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + size_t dsize; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + dsize = STRLEN(fields->module) + len + 1; + if (dsize > CMDBUFFSIZE) { + dsize = CMDBUFFSIZE; + } + xstrlcat((char *)fields->module, (char *)rmp->startp[midx], dsize); + return QF_OK; +} + +// 'errorformat' format pattern parser functions. +// The '%f' and '%r' formats are parsed differently from other formats. +// See qf_parse_match() for details. +static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = { + NULL, + qf_parse_fmt_n, + qf_parse_fmt_l, + qf_parse_fmt_c, + qf_parse_fmt_t, + qf_parse_fmt_m, + NULL, + qf_parse_fmt_p, + qf_parse_fmt_v, + qf_parse_fmt_s, + qf_parse_fmt_o +}; + /// Parse the error format matches in 'regmatch' and set the values in 'fields'. /// fmt_ptr contains the 'efm' format specifiers/prefixes that have a match. /// Returns QF_OK if all the matches are successfully parsed. On failure, @@ -1193,7 +1435,8 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, { char_u idx = fmt_ptr->prefix; int i; - size_t len; + int midx; + int status; if ((idx == 'C' || idx == 'Z') && !qf_multiline) { return QF_FAIL; @@ -1207,118 +1450,26 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, // Extract error message data from matched line. // We check for an actual submatch, because "\[" and "\]" in // the 'errorformat' may cause the wrong submatch to be used. - if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - - // Expand ~/file and $HOME/file to full path. - char_u c = *regmatch->endp[i]; - *regmatch->endp[i] = NUL; - expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE); - *regmatch->endp[i] = c; - - if (vim_strchr((char_u *)"OPQ", idx) != NULL - && !os_path_exists(fields->namebuf)) { - return QF_FAIL; - } - } - if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->enr = (int)atol((char *)regmatch->startp[i]); - } - if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->lnum = atol((char *)regmatch->startp[i]); - } - if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->col = (int)atol((char *)regmatch->startp[i]); - } - if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->type = *regmatch->startp[i]; - } - if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ - if (linelen >= fields->errmsglen) { - // linelen + null terminator - fields->errmsg = xrealloc(fields->errmsg, linelen + 1); - fields->errmsglen = linelen + 1; - } - STRLCPY(fields->errmsg, linebuf, linelen + 1); - } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len >= fields->errmsglen) { - // len + null terminator - fields->errmsg = xrealloc(fields->errmsg, len + 1); - fields->errmsglen = len + 1; - } - STRLCPY(fields->errmsg, regmatch->startp[i], len + 1); - } - if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - *tail = regmatch->startp[i]; - } - if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - fields->col = 0; - char_u *match_ptr; - for (match_ptr = regmatch->startp[i]; match_ptr != regmatch->endp[i]; - match_ptr++) { - fields->col++; - if (*match_ptr == TAB) { - fields->col += 7; - fields->col -= fields->col % 8; + for (i = 0; i < FMT_PATTERNS; i++) { + status = QF_OK; + midx = (int)fmt_ptr->addr[i]; + if (i == 0 && midx > 0) { // %f + status = qf_parse_fmt_f(regmatch, midx, fields, idx); + } else if (i == 5) { + if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ + status = qf_parse_fmt_plus(linebuf, linelen, fields); + } else if (midx > 0) { // %m + status = qf_parse_fmt_m(regmatch, midx, fields); } + } else if (i == 6 && midx > 0) { // %r + status = qf_parse_fmt_r(regmatch, midx, tail); + } else if (midx > 0) { // others + status = (qf_parse_fmt[i])(regmatch, midx, fields); } - fields->col++; - fields->use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v - if (regmatch->startp[i] == NULL) { - return QF_FAIL; - } - fields->col = (int)atol((char *)regmatch->startp[i]); - fields->use_viscol = true; - } - if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len > CMDBUFFSIZE - 5) { - len = CMDBUFFSIZE - 5; - } - STRCPY(fields->pattern, "^\\V"); - STRNCAT(fields->pattern, regmatch->startp[i], len); - fields->pattern[len + 3] = '\\'; - fields->pattern[len + 4] = '$'; - fields->pattern[len + 5] = NUL; - } - if ((i = (int)fmt_ptr->addr[10]) > 0) { // %o - if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) { - return QF_FAIL; - } - len = (size_t)(regmatch->endp[i] - regmatch->startp[i]); - if (len > CMDBUFFSIZE) { - len = CMDBUFFSIZE; + + if (status != QF_OK) { + return status; } - STRNCAT(fields->module, regmatch->startp[i], len); } return QF_OK; @@ -1572,7 +1723,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_valid = valid; lastp = &qi->qf_lists[qf_idx].qf_last; - if (qi->qf_lists[qf_idx].qf_count == 0) { + if (qf_list_empty(qi, qf_idx)) { // first element in the list qi->qf_lists[qf_idx].qf_start = qfp; qi->qf_lists[qf_idx].qf_ptr = qfp; @@ -2633,22 +2784,106 @@ theend: } } + +// Highlight attributes used for displaying entries from the quickfix list. +static int qfFileAttr; +static int qfSepAttr; +static int qfLineAttr; + +// Display information about a single entry from the quickfix/location list. +// Used by ":clist/:llist" commands. +static void qf_list_entry(qf_info_T *qi, qfline_T *qfp, int qf_idx) +{ + char_u *fname; + buf_T *buf; + + fname = NULL; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, + (char *)qfp->qf_module); + } else { + if (qfp->qf_fnum != 0 + && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) { + fname = buf->b_fname; + if (qfp->qf_type == 1) { // :helpgrep + fname = path_tail(fname); + } + } + if (fname == NULL) { + snprintf((char *)IObuff, IOSIZE, "%2d", qf_idx); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", + qf_idx, (char *)fname); + } + } + + // Support for filtering entries using :filter /pat/ clist + // Match against the module name, file name, search pattern and + // text of the entry. + bool filter_entry = true; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + filter_entry &= message_filtered(qfp->qf_module); + } + if (filter_entry && fname != NULL) { + filter_entry &= message_filtered(fname); + } + if (filter_entry && qfp->qf_pattern != NULL) { + filter_entry &= message_filtered(qfp->qf_pattern); + } + if (filter_entry) { + filter_entry &= message_filtered(qfp->qf_text); + } + if (filter_entry) { + return; + } + + msg_putchar('\n'); + msg_outtrans_attr(IObuff, qf_idx == qi->qf_lists[qi->qf_curlist].qf_index + ? HL_ATTR(HLF_QFL) : qfFileAttr); + + if (qfp->qf_lnum != 0) { + msg_puts_attr(":", qfSepAttr); + } + if (qfp->qf_lnum == 0) { + IObuff[0] = NUL; + } else if (qfp->qf_col == 0) { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", + qfp->qf_lnum, qfp->qf_col); + } + vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", + (char *)qf_types(qfp->qf_type, qfp->qf_nr)); + msg_puts_attr((const char *)IObuff, qfLineAttr); + msg_puts_attr(":", qfSepAttr); + if (qfp->qf_pattern != NULL) { + qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); + msg_puts((const char *)IObuff); + msg_puts_attr(":", qfSepAttr); + } + msg_puts(" "); + + // Remove newlines and leading whitespace from the text. For an + // unrecognized line keep the indent, the compiler may mark a word + // with ^^^^. */ + qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) + ? skipwhite(qfp->qf_text) : qfp->qf_text, + IObuff, IOSIZE); + msg_prt_line(IObuff, false); + ui_flush(); // show one line at a time +} + /* * ":clist": list all errors * ":llist": list all locations */ void qf_list(exarg_T *eap) { - buf_T *buf; - char_u *fname; qfline_T *qfp; int i; int idx1 = 1; int idx2 = -1; char_u *arg = eap->arg; - int qfFileAttr; - int qfSepAttr; - int qfLineAttr; int all = eap->forceit; // if not :cl!, only show // recognised errors qf_info_T *qi = &ql_info; @@ -2717,80 +2952,9 @@ void qf_list(exarg_T *eap) break; } - fname = NULL; - if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, - (char *)qfp->qf_module); - } else { - if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) { - fname = buf->b_fname; - if (qfp->qf_type == 1) { // :helpgrep - fname = path_tail(fname); - } - } - if (fname == NULL) { - snprintf((char *)IObuff, IOSIZE, "%2d", i); - } else { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname); - } - } - - // Support for filtering entries using :filter /pat/ clist - // Match against the module name, file name, search pattern and - // text of the entry. - bool filter_entry = true; - if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - filter_entry &= message_filtered(qfp->qf_module); - } - if (filter_entry && fname != NULL) { - filter_entry &= message_filtered(fname); - } - if (filter_entry && qfp->qf_pattern != NULL) { - filter_entry &= message_filtered(qfp->qf_pattern); - } - if (filter_entry) { - filter_entry &= message_filtered(qfp->qf_text); - } - if (filter_entry) { - goto next_entry; - } - msg_putchar('\n'); - msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index - ? HL_ATTR(HLF_QFL) : qfFileAttr); - - if (qfp->qf_lnum != 0) { - msg_puts_attr(":", qfSepAttr); - } - if (qfp->qf_lnum == 0) { - IObuff[0] = NUL; - } else if (qfp->qf_col == 0) { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); - } else { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", - qfp->qf_lnum, qfp->qf_col); - } - vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", - (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - msg_puts_attr((const char *)IObuff, qfLineAttr); - msg_puts_attr(":", qfSepAttr); - if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); - msg_puts((const char *)IObuff); - msg_puts_attr(":", qfSepAttr); - } - msg_puts(" "); - - /* Remove newlines and leading whitespace from the text. For an - * unrecognized line keep the indent, the compiler may mark a word - * with ^^^^. */ - qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) - ? skipwhite(qfp->qf_text) : qfp->qf_text, - IObuff, IOSIZE); - msg_prt_line(IObuff, FALSE); - ui_flush(); /* show one line at a time */ + qf_list_entry(qi, qfp, i); } -next_entry: qfp = qfp->qf_next; if (qfp == NULL) { break; @@ -2995,8 +3159,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qi = wp->w_llist; } - for (idx = 0; idx < qi->qf_listcount; ++idx) - if (qi->qf_lists[idx].qf_count) + for (idx = 0; idx < qi->qf_listcount; idx++) { + if (!qf_list_empty(qi, idx)) { for (i = 0, qfp = qi->qf_lists[idx].qf_start; i < qi->qf_lists[idx].qf_count && qfp != NULL; i++, qfp = qfp->qf_next) { @@ -3011,6 +3175,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qfp->qf_lnum += amount_after; } } + } + } return found_one; } @@ -4596,7 +4762,7 @@ void ex_vimgrep(exarg_T *eap) } // Jump to first match. - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { + if (!qf_list_empty(qi, qi->qf_curlist)) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir); @@ -4805,7 +4971,7 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) } if (qf_idx >= qi->qf_listcount - || qi->qf_lists[qf_idx].qf_count == 0) { + || qf_list_empty(qi, qf_idx)) { return FAIL; } @@ -5119,7 +5285,7 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) { int idx = qi->qf_lists[qf_idx].qf_index; - if (qi->qf_lists[qf_idx].qf_count == 0) { + if (qf_list_empty(qi, qf_idx)) { // For empty lists, qf_index is set to 1 idx = 0; } @@ -5276,7 +5442,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // make place for a new list qf_new_list(qi, title); qf_idx = qi->qf_curlist; - } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { + } else if (action == 'a' && !qf_list_empty(qi, qf_idx)) { // Adding to existing list, use last entry. old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { @@ -5308,7 +5474,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, } if (action != 'a') { qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; - if (qi->qf_lists[qf_idx].qf_count > 0) { + if (!qf_list_empty(qi, qf_idx)) { qi->qf_lists[qf_idx].qf_index = 1; } } @@ -6023,11 +6189,12 @@ void ex_helpgrep(exarg_T *eap) } } - /* Jump to first match. */ - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) - qf_jump(qi, 0, 0, FALSE); - else + // Jump to first match. + if (!qf_list_empty(qi, qi->qf_curlist)) { + qf_jump(qi, 0, 0, false); + } else { EMSG2(_(e_nomatch2), eap->arg); + } if (eap->cmdidx == CMD_lhelpgrep) { // If the help window is not opened or if it already points to the diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 6f58b0084c..c0f39884ea 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1045,8 +1045,8 @@ func Test_efm2() set efm=%f:%s cexpr 'Xtestfile:Line search text' let l = getqflist() - call assert_equal(l[0].pattern, '^\VLine search text\$') - call assert_equal(l[0].lnum, 0) + call assert_equal('^\VLine search text\$', l[0].pattern) + call assert_equal(0, l[0].lnum) let l = split(execute('clist', ''), "\n") call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) -- cgit From b041096ed4647e3b73b273e6fd6e81e5265672b9 Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Wed, 18 Sep 2019 13:04:47 -0400 Subject: vim-patch:8.1.0407: quickfix code mixes using the stack and a list pointer Problem: Quickfix code mixes using the stack and a list pointer. Solution: Use a list pointer in more places. (Yegappan Lakshmanan, closes vim/vim#3443) https://github.com/vim/vim/commit/fe15b7dfa628d4edd683dae9528194c0e5510128 --- src/nvim/quickfix.c | 239 +++++++++++++++++++++++++++------------------------- 1 file changed, 123 insertions(+), 116 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f14d9f445b..56f6f8b91b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -382,7 +382,7 @@ static char_u * scanf_fmt_to_regpat( } // Analyze/parse an errorformat prefix. -static char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, +static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { @@ -1138,7 +1138,7 @@ qf_init_ext( error2: if (!adding) { // Error when creating a new list. Free the new list - qf_free(qi, qi->qf_curlist); + qf_free(&qi->qf_lists[qi->qf_curlist]); qi->qf_listcount--; if (qi->qf_curlist > 0) { qi->qf_curlist--; @@ -1156,16 +1156,16 @@ qf_init_end: /// Set the title of the specified quickfix list. Frees the previous title. /// Prepends ':' to the title. -static void qf_store_title(qf_info_T *qi, int qf_idx, const char_u *title) +static void qf_store_title(qf_list_T *qfl, const char_u *title) FUNC_ATTR_NONNULL_ARG(1) { - XFREE_CLEAR(qi->qf_lists[qf_idx].qf_title); + XFREE_CLEAR(qfl->qf_title); if (title != NULL) { size_t len = STRLEN(title) + 1; char_u *p = xmallocz(len); - qi->qf_lists[qf_idx].qf_title = p; + qfl->qf_title = p; xstrlcpy((char *)p, (const char *)title, len + 1); } } @@ -1193,22 +1193,24 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) // If the current entry is not the last entry, delete entries beyond // the current entry. This makes it possible to browse in a tree-like // way with ":grep'. - while (qi->qf_listcount > qi->qf_curlist + 1) - qf_free(qi, --qi->qf_listcount); + while (qi->qf_listcount > qi->qf_curlist + 1) { + qf_free(&qi->qf_lists[--qi->qf_listcount]); + } /* * When the stack is full, remove to oldest entry * Otherwise, add a new entry. */ if (qi->qf_listcount == LISTCOUNT) { - qf_free(qi, 0); - for (i = 1; i < LISTCOUNT; ++i) + qf_free(&qi->qf_lists[0]); + for (i = 1; i < LISTCOUNT; i++) { qi->qf_lists[i - 1] = qi->qf_lists[i]; + } qi->qf_curlist = LISTCOUNT - 1; } else qi->qf_curlist = qi->qf_listcount++; memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qi->qf_curlist, qf_title); + qf_store_title(&qi->qf_lists[qi->qf_curlist], qf_title); qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; } @@ -1628,7 +1630,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, return QF_IGNORE_LINE; } -/// Free a location list. +/// Free a location list stack static void ll_free_all(qf_info_T **pqi) { int i; @@ -1641,9 +1643,10 @@ static void ll_free_all(qf_info_T **pqi) qi->qf_refcount--; if (qi->qf_refcount < 1) { - /* No references to this location list */ - for (i = 0; i < qi->qf_listcount; ++i) - qf_free(qi, i); + // No references to this location list + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(&qi->qf_lists[i]); + } xfree(qi); } } @@ -1658,10 +1661,12 @@ void qf_free_all(win_T *wp) /* location list */ ll_free_all(&wp->w_llist); ll_free_all(&wp->w_llist_ref); - } else - /* quickfix list */ - for (i = 0; i < qi->qf_listcount; ++i) - qf_free(qi, i); + } else { + // quickfix list + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(&qi->qf_lists[i]); + } + } } /// Add an entry to the end of the list of errors. @@ -1687,6 +1692,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, int col, char_u vis_col, char_u *pattern, int nr, char_u type, char_u valid) { + qf_list_T *qfl = &qi->qf_lists[qf_idx]; qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1722,12 +1728,12 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qf_idx].qf_last; + lastp = &qfl->qf_last; if (qf_list_empty(qi, qf_idx)) { // first element in the list - qi->qf_lists[qf_idx].qf_start = qfp; - qi->qf_lists[qf_idx].qf_ptr = qfp; - qi->qf_lists[qf_idx].qf_index = 0; + qfl->qf_start = qfp; + qfl->qf_ptr = qfp; + qfl->qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1737,19 +1743,17 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qf_idx].qf_count++; - if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { + qfl->qf_count++; + if (qfl->qf_index == 0 && qfp->qf_valid) { // first valid entry - qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; - qi->qf_lists[qf_idx].qf_ptr = qfp; + qfl->qf_index = qfl->qf_count; + qfl->qf_ptr = qfp; } return OK; } -/* - * Allocate a new location list - */ +// Allocate a new location list stack static qf_info_T *ll_new_list(void) FUNC_ATTR_NONNULL_RET { @@ -1759,10 +1763,8 @@ static qf_info_T *ll_new_list(void) return qi; } -/* - * Return the location list for window 'wp'. - * If not present, allocate a location list - */ +// Return the location list stack for window 'wp'. +// If not present, allocate a location list stack static qf_info_T *ll_get_or_alloc_list(win_T *wp) { if (IS_LL_WINDOW(wp)) @@ -1936,7 +1938,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, // directory change. if (!os_path_exists(ptr)) { xfree(ptr); - directory = qf_guess_filepath(qi, qf_idx, fname); + directory = qf_guess_filepath(&qi->qf_lists[qf_idx], fname); if (directory) { ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true); } else { @@ -2084,12 +2086,11 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr) // x.c:9: Error // Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. // qf_guess_filepath will return NULL. -static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename) +static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *filename) { struct dir_stack_T *ds_ptr; struct dir_stack_T *ds_tmp; char_u *fullname; - qf_list_T *qfl = &qi->qf_lists[qf_idx]; // no dirs on the stack - there's nothing we can do if (qfl->qf_dir_stack == NULL) { @@ -2147,14 +2148,11 @@ static bool qflist_valid(win_T *wp, unsigned int qf_id) /// This may invalidate the current quickfix entry. This function checks /// whether an entry is still present in the quickfix list. /// Similar to location list. -static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) +static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr) { - qf_list_T *qfl; qfline_T *qfp; int i; - qfl = &qi->qf_lists[qi->qf_curlist]; - // Search for the entry in the current list for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; i++, qfp = qfp->qf_next) { if (qfp == NULL || qfp == qf_ptr) { @@ -2171,20 +2169,19 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) /// Get the next valid entry in the current quickfix/location list. The search /// starts from the current entry. Returns NULL on failure. -static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, +static qfline_T *get_next_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, int *qf_index, int dir) { int idx = *qf_index; int old_qf_fnum = qf_ptr->qf_fnum; do { - if (idx == qi->qf_lists[qi->qf_curlist].qf_count - || qf_ptr->qf_next == NULL) { + if (idx == qfl->qf_count || qf_ptr->qf_next == NULL) { return NULL; } idx++; qf_ptr = qf_ptr->qf_next; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; @@ -2193,7 +2190,7 @@ static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, /// Get the previous valid entry in the current quickfix/location list. The /// search starts from the current entry. Returns NULL on failure. -static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, +static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, int *qf_index, int dir) { int idx = *qf_index; @@ -2205,7 +2202,7 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, } idx--; qf_ptr = qf_ptr->qf_prev; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; @@ -2216,7 +2213,7 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, /// the quickfix list. /// dir == FORWARD or FORWARD_FILE: next valid entry /// dir == BACKWARD or BACKWARD_FILE: previous valid entry -static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, +static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr, int *qf_index, int dir) { qfline_T *prev_qf_ptr; @@ -2229,9 +2226,9 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, prev_index = *qf_index; if (dir == FORWARD || dir == FORWARD_FILE) { - qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir); + qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir); } else { - qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir); + qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir); } if (qf_ptr == NULL) { @@ -2251,7 +2248,7 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, } /// Get n'th (errornr) quickfix entry -static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, +static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr, int *cur_qfidx) { int qf_idx = *cur_qfidx; @@ -2264,7 +2261,7 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, // New error number is greater than the current error number while (errornr > qf_idx - && qf_idx < qi->qf_lists[qi->qf_curlist].qf_count + && qf_idx < qfl->qf_count && qf_ptr->qf_next != NULL) { qf_idx++; qf_ptr = qf_ptr->qf_next; @@ -2535,6 +2532,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, int *opened_window, int *abort) { + qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; int retval = OK; if (qf_ptr->qf_type == 1) { @@ -2549,7 +2547,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, oldwin == curwin ? curwin : NULL); } } else { - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qfl->qf_id; retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); @@ -2565,7 +2563,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, EMSG(_(e_loc_list_changed)); *abort = true; } - } else if (!is_qf_entry_present(qi, qf_ptr)) { + } else if (!is_qf_entry_present(qfl, qf_ptr)) { if (IS_QF_STACK(qi)) { EMSG(_("E925: Current quickfix was changed")); } else { @@ -2660,6 +2658,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, /// else go to entry "errornr" void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) { + qf_list_T *qfl; qfline_T *qf_ptr; qfline_T *old_qf_ptr; int qf_index; @@ -2682,26 +2681,29 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) return; } - qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; + qfl = &qi->qf_lists[qi->qf_curlist]; + + qf_ptr = qfl->qf_ptr; old_qf_ptr = qf_ptr; - qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + qf_index = qfl->qf_index; old_qf_index = qf_index; if (dir != 0) { // next/prev valid entry - qf_ptr = get_nth_valid_entry(qi, errornr, qf_ptr, &qf_index, dir); + qf_ptr = get_nth_valid_entry(qfl, errornr, qf_ptr, &qf_index, dir); if (qf_ptr == NULL) { qf_ptr = old_qf_ptr; qf_index = old_qf_index; goto theend; // The horror... the horror... } } else if (errornr != 0) { // go to specified number - qf_ptr = get_nth_entry(qi, errornr, qf_ptr, &qf_index); + qf_ptr = get_nth_entry(qfl, errornr, qf_ptr, &qf_index); } - qi->qf_lists[qi->qf_curlist].qf_index = qf_index; - if (qf_win_pos_update(qi, old_qf_index)) - /* No need to print the error message if it's visible in the error - * window */ - print_message = FALSE; + qfl->qf_index = qf_index; + if (qf_win_pos_update(qi, old_qf_index)) { + // No need to print the error message if it's visible in the error + // window + print_message = false; + } // For ":helpgrep" find a help window or open one. if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { @@ -2770,8 +2772,8 @@ failed: } theend: if (qi != NULL) { - qi->qf_lists[qi->qf_curlist].qf_ptr = qf_ptr; - qi->qf_lists[qi->qf_curlist].qf_index = qf_index; + qfl->qf_ptr = qf_ptr; + qfl->qf_index = qf_index; } if (p_swb != old_swb && opened_window) { /* Restore old 'switchbuf' value, but not when an autocommand or @@ -2792,7 +2794,9 @@ static int qfLineAttr; // Display information about a single entry from the quickfix/location list. // Used by ":clist/:llist" commands. -static void qf_list_entry(qf_info_T *qi, qfline_T *qfp, int qf_idx) +// 'cursel' will be set to TRUE for the currently selected entry in the +// quickfix list. +static void qf_list_entry(qfline_T *qfp, int qf_idx, int cursel) { char_u *fname; buf_T *buf; @@ -2838,8 +2842,7 @@ static void qf_list_entry(qf_info_T *qi, qfline_T *qfp, int qf_idx) } msg_putchar('\n'); - msg_outtrans_attr(IObuff, qf_idx == qi->qf_lists[qi->qf_curlist].qf_index - ? HL_ATTR(HLF_QFL) : qfFileAttr); + msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); if (qfp->qf_lnum != 0) { msg_puts_attr(":", qfSepAttr); @@ -2879,7 +2882,8 @@ static void qf_list_entry(qf_info_T *qi, qfline_T *qfp, int qf_idx) */ void qf_list(exarg_T *eap) { - qfline_T *qfp; + qf_list_T *qfl; + qfline_T *qfp; int i; int idx1 = 1; int idx2 = -1; @@ -2910,12 +2914,13 @@ void qf_list(exarg_T *eap) EMSG(_(e_trailing)); return; } + qfl = &qi->qf_lists[qi->qf_curlist]; if (plus) { - i = qi->qf_lists[qi->qf_curlist].qf_index; + i = qfl->qf_index; idx2 = i + idx1; idx1 = i; } else { - i = qi->qf_lists[qi->qf_curlist].qf_count; + i = qfl->qf_count; if (idx1 < 0) { idx1 = (-idx1 > i) ? 0 : idx1 + i + 1; } @@ -2942,17 +2947,17 @@ void qf_list(exarg_T *eap) qfLineAttr = HL_ATTR(HLF_N); } - if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) { + if (qfl->qf_nonevalid) { all = true; } - qfp = qi->qf_lists[qi->qf_curlist].qf_start; - for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { + qfp = qfl->qf_start; + for (i = 1; !got_int && i <= qfl->qf_count; ) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { if (got_int) { break; } - qf_list_entry(qi, qfp, i); + qf_list_entry(qfp, i, i == qfl->qf_index); } qfp = qfp->qf_next; @@ -3078,12 +3083,11 @@ void qf_history(exarg_T *eap) /// Free all the entries in the error list "idx". Note that other information /// associated with the list like context and title are not freed. -static void qf_free_items(qf_info_T *qi, int idx) +static void qf_free_items(qf_list_T *qfl) { qfline_T *qfp; qfline_T *qfpnext; bool stop = false; - qf_list_T *qfl = &qi->qf_lists[idx]; while (qfl->qf_count && qfl->qf_start != NULL) { qfp = qfl->qf_start; @@ -3124,10 +3128,9 @@ static void qf_free_items(qf_info_T *qi, int idx) /// Free error list "idx". Frees all the entries in the quickfix list, /// associated context information and the title. -static void qf_free(qf_info_T *qi, int idx) +static void qf_free(qf_list_T *qfl) { - qf_list_T *qfl = &qi->qf_lists[idx]; - qf_free_items(qi, idx); + qf_free_items(qfl); XFREE_CLEAR(qfl->qf_title); tv_free(qfl->qf_ctx); @@ -3406,6 +3409,14 @@ static int qf_open_new_cwindow(const qf_info_T *qi, int height) return OK; } +// Set "w:quickfix_title" if "qi" has a title. +static void qf_set_title_var(qf_list_T *qfl) +{ + if (qfl->qf_title != NULL) { + set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title); + } +} + /* * ":copen": open a window that shows the list of errors. * ":lopen": open a window that shows the location list. @@ -3443,7 +3454,7 @@ void ex_copen(exarg_T *eap) } } - qf_set_title_var(qi); + qf_set_title_var(&qi->qf_lists[qi->qf_curlist]); // Fill the buffer with the quickfix list. qf_fill_buffer(qi, curbuf, NULL); @@ -3541,7 +3552,7 @@ qf_win_pos_update ( } /// Checks whether the given window is displaying the specified -/// quickfix/location list buffer. +/// quickfix/location stack. static int is_qf_win(const win_T *win, const qf_info_T *qi) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3561,7 +3572,7 @@ static int is_qf_win(const win_T *win, const qf_info_T *qi) return false; } -/// Find a window displaying the quickfix/location list 'qi' +/// Find a window displaying the quickfix/location stack 'qi' /// Only searches in the current tabpage. static win_T *qf_find_win(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -3599,7 +3610,7 @@ static void qf_update_win_titlevar(qf_info_T *qi) if ((win = qf_find_win(qi)) != NULL) { win_T *curwin_save = curwin; curwin = win; - qf_set_title_var(qi); + qf_set_title_var(&qi->qf_lists[qi->qf_curlist]); curwin = curwin_save; } } @@ -3643,15 +3654,6 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) } } -// Set "w:quickfix_title" if "qi" has a title. -static void qf_set_title_var(qf_info_T *qi) -{ - if (qi->qf_lists[qi->qf_curlist].qf_title != NULL) { - set_internal_string_var((char_u *)"w:quickfix_title", - qi->qf_lists[qi->qf_curlist].qf_title); - } -} - // Add an error line to the quickfix buffer. static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, char_u *dirname) @@ -4144,11 +4146,9 @@ int qf_get_cur_valid_idx(exarg_T *eap) /// Used by :cdo, :ldo, :cfdo and :lfdo commands. /// For :cdo and :ldo, returns the 'n'th valid error entry. /// For :cfdo and :lfdo, returns the 'n'th valid file entry. -static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo) +static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) FUNC_ATTR_NONNULL_ALL { - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; - // Check if the list has valid errors. if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { return 1; @@ -4231,8 +4231,9 @@ void ex_cc(exarg_T *eap) } else { n = 1; } - size_t valid_entry = qf_get_nth_valid_entry(qi, n, - eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); + size_t valid_entry = qf_get_nth_valid_entry( + &qi->qf_lists[qi->qf_curlist], n, + eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); assert(valid_entry <= INT_MAX); errornr = (int)valid_entry; } @@ -5065,7 +5066,7 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { (void)get_errorlist(qi, NULL, 0, l); - qf_free(qi, 0); + qf_free(&qi->qf_lists[0]); } xfree(qi); @@ -5298,6 +5299,7 @@ static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + qf_list_T *qfl; dictitem_T *di = NULL; int status = OK; int qf_idx; @@ -5321,6 +5323,8 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) return qf_getprop_defaults(qi, flags, retdict); } + qfl = &qi->qf_lists[qf_idx]; + if (flags & QF_GETLIST_TITLE) { status = qf_getprop_title(qi, qf_idx, retdict); } @@ -5337,18 +5341,18 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) status = qf_getprop_ctx(qi, qf_idx, retdict); } if ((status == OK) && (flags & QF_GETLIST_ID)) { - status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id); + status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id); } if ((status == OK) && (flags & QF_GETLIST_IDX)) { status = qf_getprop_idx(qi, qf_idx, retdict); } if ((status == OK) && (flags & QF_GETLIST_SIZE)) { status = tv_dict_add_nr(retdict, S_LEN("size"), - qi->qf_lists[qf_idx].qf_count); + qfl->qf_count); } if ((status == OK) && (flags & QF_GETLIST_TICK)) { status = tv_dict_add_nr(retdict, S_LEN("changedtick"), - qi->qf_lists[qf_idx].qf_changedtick); + qfl->qf_changedtick); } if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { status = qf_getprop_filewinid(wp, qi, retdict); @@ -5435,6 +5439,7 @@ static int qf_add_entry_from_dict( static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char_u *title, int action) { + qf_list_T *qfl = &qi->qf_lists[qf_idx]; qfline_T *old_last = NULL; int retval = OK; @@ -5442,12 +5447,13 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // make place for a new list qf_new_list(qi, title); qf_idx = qi->qf_curlist; + qfl = &qi->qf_lists[qf_idx]; } else if (action == 'a' && !qf_list_empty(qi, qf_idx)) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qf_idx].qf_last; + old_last = qfl->qf_last; } else if (action == 'r') { - qf_free_items(qi, qf_idx); - qf_store_title(qi, qf_idx, title); + qf_free_items(qfl); + qf_store_title(qfl, title); } TV_LIST_ITER_CONST(list, li, { @@ -5466,16 +5472,16 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, } }); - if (qi->qf_lists[qf_idx].qf_index == 0) { + if (qfl->qf_index == 0) { // no valid entry - qi->qf_lists[qf_idx].qf_nonevalid = true; + qfl->qf_nonevalid = true; } else { - qi->qf_lists[qf_idx].qf_nonevalid = false; + qfl->qf_nonevalid = false; } if (action != 'a') { - qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; + qfl->qf_ptr = qfl->qf_start; if (!qf_list_empty(qi, qf_idx)) { - qi->qf_lists[qf_idx].qf_index = 1; + qfl->qf_index = 1; } } @@ -5605,7 +5611,7 @@ static int qf_setprop_items_from_lines( } if (action == 'r') { - qf_free_items(qi, qf_idx); + qf_free_items(&qi->qf_lists[qf_idx]); } if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat, false, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { @@ -5616,13 +5622,13 @@ static int qf_setprop_items_from_lines( } // Set quickfix list context. -static int qf_setprop_context(qf_info_T *qi, int qf_idx, dictitem_T *di) +static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di) FUNC_ATTR_NONNULL_ALL { - tv_free(qi->qf_lists[qf_idx].qf_ctx); + tv_free(qfl->qf_ctx); typval_T *ctx = xcalloc(1, sizeof(typval_T)); tv_copy(&di->di_tv, ctx); - qi->qf_lists[qf_idx].qf_ctx = ctx; + qfl->qf_ctx = ctx; return OK; } @@ -5658,7 +5664,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action); } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { - retval = qf_setprop_context(qi, qf_idx, di); + retval = qf_setprop_context(&qi->qf_lists[qf_idx], di); } if (retval == OK) { @@ -5668,7 +5674,8 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, return retval; } -// Find the non-location list window with the specified location list. +// Find the non-location list window with the specified location list in the +// current tabpage. static win_T * find_win_with_ll(qf_info_T *qi) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -5689,7 +5696,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) if (qfwin != NULL) { // If the quickfix/location list window is open, then clear it if (qi->qf_curlist < qi->qf_listcount) { - qf_free(qi, qi->qf_curlist); + qf_free(&qi->qf_lists[qi->qf_curlist]); } qf_update_buffer(qi, NULL); } -- cgit From f0dc110f31a0103811a14719a5e2cc39310171d1 Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Wed, 18 Sep 2019 11:49:11 -0400 Subject: vim-patch:8.1.0469: too often indexing in qf_lists[] Problem: Too often indexing in qf_lists[]. Solution: Use a qf_list_T pointer. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/108e7b422b7b59153dd5af1fb75e83fa36ff3db4 --- src/nvim/quickfix.c | 113 +++++++++++++++++++++---------------- src/nvim/testdir/test_quickfix.vim | 2 + 2 files changed, 65 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 56f6f8b91b..6ac7f374ea 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -383,7 +383,7 @@ static char_u * scanf_fmt_to_regpat( // Analyze/parse an errorformat prefix. static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, - char_u *errmsg, size_t errmsglen) + char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { if (vim_strchr((char_u *)"+-", *efmp) != NULL) { @@ -1138,7 +1138,7 @@ qf_init_ext( error2: if (!adding) { // Error when creating a new list. Free the new list - qf_free(&qi->qf_lists[qi->qf_curlist]); + qf_free(qfl); qi->qf_listcount--; if (qi->qf_curlist > 0) { qi->qf_curlist--; @@ -1189,6 +1189,7 @@ static char_u * qf_cmdtitle(char_u *cmd) static void qf_new_list(qf_info_T *qi, const char_u *qf_title) { int i; + qf_list_T *qfl; // If the current entry is not the last entry, delete entries beyond // the current entry. This makes it possible to browse in a tree-like @@ -1209,9 +1210,10 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qi->qf_curlist = LISTCOUNT - 1; } else qi->qf_curlist = qi->qf_listcount++; - memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(&qi->qf_lists[qi->qf_curlist], qf_title); - qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; + qfl = &qi->qf_lists[qi->qf_curlist]; + memset(qfl, 0, (size_t)(sizeof(qf_list_T))); + qf_store_title(qfl, qf_title); + qfl->qf_id = ++last_qf_id; } // Parse the match for filename ('%f') pattern in regmatch. @@ -1917,6 +1919,7 @@ void copy_loclist_stack(win_T *from, win_T *to) static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, char_u *fname) { + qf_list_T *qfl = &qi->qf_lists[qf_idx]; char_u *ptr = NULL; char_u *bufname; buf_T *buf; @@ -1938,7 +1941,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, // directory change. if (!os_path_exists(ptr)) { xfree(ptr); - directory = qf_guess_filepath(&qi->qf_lists[qf_idx], fname); + directory = qf_guess_filepath(qfl, fname); if (directory) { ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true); } else { @@ -3268,6 +3271,7 @@ void qf_view_result(bool split) void ex_cwindow(exarg_T *eap) { qf_info_T *qi = &ql_info; + qf_list_T *qfl; win_T *win; if (is_loclist_cmd(eap->cmdidx)) { @@ -3276,6 +3280,8 @@ void ex_cwindow(exarg_T *eap) return; } + qfl = &qi->qf_lists[qi->qf_curlist]; + /* Look for an existing quickfix window. */ win = qf_find_win(qi); @@ -3285,7 +3291,7 @@ void ex_cwindow(exarg_T *eap) * it if we have errors; otherwise, leave it closed. */ if (qf_stack_empty(qi) - || qi->qf_lists[qi->qf_curlist].qf_nonevalid + || qfl->qf_nonevalid || qf_list_empty(qi, qi->qf_curlist)) { if (win != NULL) { ex_cclose(eap); @@ -3424,6 +3430,7 @@ static void qf_set_title_var(qf_list_T *qfl) void ex_copen(exarg_T *eap) { qf_info_T *qi = &ql_info; + qf_list_T *qfl; int height; int status = FAIL; @@ -3454,12 +3461,13 @@ void ex_copen(exarg_T *eap) } } - qf_set_title_var(&qi->qf_lists[qi->qf_curlist]); + qfl = &qi->qf_lists[qi->qf_curlist]; + qf_set_title_var(qfl); // Fill the buffer with the quickfix list. qf_fill_buffer(qi, curbuf, NULL); - curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index; + curwin->w_cursor.lnum = qfl->qf_index; curwin->w_cursor.col = 0; check_cursor(); update_topline(); // scroll to show the line @@ -3746,13 +3754,14 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) // Check if there is anything to display if (!qf_stack_empty(qi)) { + qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; char_u dirname[MAXPATHL]; *dirname = NUL; // Add one line for each error if (old_last == NULL) { - qfp = qi->qf_lists[qi->qf_curlist].qf_start; + qfp = qfl->qf_start; lnum = 0; } else { qfp = old_last->qf_next; @@ -3801,9 +3810,9 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) KeyTyped = old_KeyTyped; } -static void qf_list_changed(qf_info_T *qi, int qf_idx) +static void qf_list_changed(qf_list_T *qfl) { - qi->qf_lists[qf_idx].qf_changedtick++; + qfl->qf_changedtick++; } /// Return the quickfix/location list number with the given identifier. @@ -3967,7 +3976,7 @@ void ex_make(exarg_T *eap) } } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. @@ -4040,6 +4049,7 @@ size_t qf_get_size(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { qf_info_T *qi = &ql_info; + qf_list_T *qfl; if (is_loclist_cmd(eap->cmdidx)) { // Location list. qi = GET_LOC_LIST(curwin); @@ -4053,8 +4063,8 @@ size_t qf_get_size(exarg_T *eap) qfline_T *qfp; size_t i; assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0); - for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start; - i < (size_t)qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL; + qfl = &qi->qf_lists[qi->qf_curlist]; + for (i = 0, qfp = qfl->qf_start; i < (size_t)qfl->qf_count && qfp != NULL; i++, qfp = qfp->qf_next) { if (!qfp->qf_valid) { continue; @@ -4353,7 +4363,7 @@ void ex_cfile(exarg_T *eap) } } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; if (au_name != NULL) { @@ -4564,6 +4574,7 @@ void ex_vimgrep(exarg_T *eap) char_u *p; int fi; qf_info_T *qi = &ql_info; + qf_list_T *qfl; win_T *wp = NULL; buf_T *buf; int duplicate_name = FALSE; @@ -4741,10 +4752,11 @@ void ex_vimgrep(exarg_T *eap) FreeWild(fcount, fnames); - qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE; - qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; - qi->qf_lists[qi->qf_curlist].qf_index = 1; - qf_list_changed(qi, qi->qf_curlist); + qfl = &qi->qf_lists[qi->qf_curlist]; + qfl->qf_nonevalid = false; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); qf_update_buffer(qi, NULL); @@ -5228,10 +5240,10 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) } /// Return the quickfix list title as 'title' in retdict -static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict) +static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict) { return tv_dict_add_str(retdict, S_LEN("title"), - (const char *)qi->qf_lists[qf_idx].qf_title); + (const char *)qfl->qf_title); } // Returns the identifier of the window used to display files from a location @@ -5264,13 +5276,13 @@ static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict) } /// Return the quickfix list context (if any) as 'context' in retdict. -static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) +static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict) { int status; - if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + if (qfl->qf_ctx != NULL) { dictitem_T *di = tv_dict_item_alloc_len(S_LEN("context")); - tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + tv_copy(qfl->qf_ctx, &di->di_tv); status = tv_dict_add(retdict, di); if (status == FAIL) { tv_dict_item_free(di); @@ -5282,15 +5294,15 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) return status; } -/// Return the quickfix list index as 'idx' in retdict +/// Return the current quickfix list index as 'idx' in retdict static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) { - int idx = qi->qf_lists[qf_idx].qf_index; + int curidx = qi->qf_lists[qf_idx].qf_index; if (qf_list_empty(qi, qf_idx)) { - // For empty lists, qf_index is set to 1 - idx = 0; + // For empty lists, current index is set to 0 + curidx = 0; } - return tv_dict_add_nr(retdict, S_LEN("idx"), idx); + return tv_dict_add_nr(retdict, S_LEN("idx"), curidx); } /// Return quickfix/location list details (title) as a dictionary. @@ -5302,7 +5314,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) qf_list_T *qfl; dictitem_T *di = NULL; int status = OK; - int qf_idx; + int qf_idx = INVALID_QFIDX; if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { return qf_get_list_from_lines(what, di, retdict); @@ -5326,7 +5338,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) qfl = &qi->qf_lists[qf_idx]; if (flags & QF_GETLIST_TITLE) { - status = qf_getprop_title(qi, qf_idx, retdict); + status = qf_getprop_title(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_NR)) { status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1); @@ -5338,7 +5350,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) status = qf_getprop_items(qi, qf_idx, retdict); } if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { - status = qf_getprop_ctx(qi, qf_idx, retdict); + status = qf_getprop_ctx(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_ID)) { status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id); @@ -5551,13 +5563,13 @@ static int qf_setprop_title(qf_info_T *qi, int qf_idx, const dict_T *what, const dictitem_T *di) FUNC_ATTR_NONNULL_ALL { + qf_list_T *qfl = &qi->qf_lists[qf_idx]; if (di->di_tv.v_type != VAR_STRING) { return FAIL; } - xfree(qi->qf_lists[qf_idx].qf_title); - qi->qf_lists[qf_idx].qf_title = - (char_u *)tv_dict_get_string(what, "title", true); + xfree(qfl->qf_title); + qfl->qf_title = (char_u *)tv_dict_get_string(what, "title", true); if (qf_idx == qi->qf_curlist) { qf_update_win_titlevar(qi); } @@ -5640,6 +5652,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char_u *title) FUNC_ATTR_NONNULL_ALL { + qf_list_T *qfl; dictitem_T *di; int retval = FAIL; bool newlist = action == ' ' || qf_stack_empty(qi); @@ -5654,6 +5667,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, qf_idx = qi->qf_curlist; } + qfl = &qi->qf_lists[qf_idx]; if ((di = tv_dict_find(what, S_LEN("title"))) != NULL) { retval = qf_setprop_title(qi, qf_idx, what, di); } @@ -5664,18 +5678,18 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action); } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { - retval = qf_setprop_context(&qi->qf_lists[qf_idx], di); + retval = qf_setprop_context(qfl, di); } if (retval == OK) { - qf_list_changed(qi, qf_idx); + qf_list_changed(qfl); } return retval; } -// Find the non-location list window with the specified location list in the -// current tabpage. +// Find the non-location list window with the specified location list stack in +// the current tabpage. static win_T * find_win_with_ll(qf_info_T *qi) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -5754,7 +5768,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, } else { retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); if (retval == OK) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } } @@ -5889,7 +5903,7 @@ void ex_cbuffer(exarg_T *eap) && eap->cmdidx != CMD_laddbuffer), eap->line1, eap->line2, qf_title, NULL); if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. @@ -5971,7 +5985,7 @@ void ex_cexpr(exarg_T *eap) (linenr_T)0, (linenr_T)0, qf_cmdtitle(*eap->cmdlinep), NULL); if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. @@ -6171,10 +6185,12 @@ void ex_helpgrep(exarg_T *eap) vim_regfree(regmatch.regprog); - qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE; - qi->qf_lists[qi->qf_curlist].qf_ptr = - qi->qf_lists[qi->qf_curlist].qf_start; - qi->qf_lists[qi->qf_curlist].qf_index = 1; + qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qfl->qf_nonevalid = false; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); + qf_update_buffer(qi, NULL); } if (p_cpo == empty_option) { @@ -6184,9 +6200,6 @@ void ex_helpgrep(exarg_T *eap) free_string_option(save_cpo); } - qf_list_changed(qi, qi->qf_curlist); - qf_update_buffer(qi, NULL); - if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index c0f39884ea..a5ce41d8d2 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -540,6 +540,8 @@ func s:test_xhelpgrep(cchar) " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') + " Invalid regular expression + call assert_fails('Xhelpgrep \@ Date: Sun, 27 Oct 2019 16:17:35 -0400 Subject: vim-patch:8.1.0488: using freed memory in quickfix code Problem: Using freed memory in quickfix code. (Dominique Pelle) Solution: Add the quickfix_busy() flag to postpone deleting quickfix lists until it is safe. (Yegappan Lakshmanan, closes #3538) https://github.com/vim/vim/commit/9f84ded38b62c82a4ee57b54f403b1b185ed8170 --- src/nvim/memory.c | 2 + src/nvim/quickfix.c | 140 ++++++++++++++++++++++++++++++++++--- src/nvim/testdir/test_quickfix.vim | 21 ++++++ 3 files changed, 152 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 64aae71433..9bc6b23ce3 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -693,6 +693,8 @@ void free_all_mem(void) clear_hl_tables(false); list_free_log(); + + check_quickfix_busy(); } #endif diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6ac7f374ea..78e68b0885 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -154,6 +154,13 @@ struct efm_S { int conthere; /* %> used */ }; +// List of location lists to be deleted. +// Used to delay the deletion of locations lists by autocmds. +typedef struct qf_delq_S { + struct qf_delq_S *next; + qf_info_T *qi; +} qf_delq_T; + enum { QF_FAIL = 0, QF_OK = 1, @@ -220,6 +227,11 @@ static bufref_T qf_last_bufref = { NULL, 0, 0 }; static char *e_loc_list_changed = N_("E926: Current location list was changed"); +// Counter to prevent autocmds from freeing up location lists when they are +// still being used. +static int quickfix_busy = 0; +static qf_delq_T *qf_delq_head = NULL; + /// Read the errorfile "efile" into memory, line by line, building the error /// list. Set the error list's title to qf_title. /// @@ -1632,6 +1644,17 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, return QF_IGNORE_LINE; } +// Queue location list stack delete request. +static void locstack_queue_delreq(qf_info_T *qi) +{ + qf_delq_T *q; + + q = xmalloc(sizeof(qf_delq_T)); + q->qi = qi; + q->next = qf_delq_head; + qf_delq_head = q; +} + /// Free a location list stack static void ll_free_all(qf_info_T **pqi) { @@ -1645,11 +1668,17 @@ static void ll_free_all(qf_info_T **pqi) qi->qf_refcount--; if (qi->qf_refcount < 1) { - // No references to this location list - for (i = 0; i < qi->qf_listcount; i++) { - qf_free(&qi->qf_lists[i]); + // No references to this location list. + // If the location list is still in use, then queue the delete request + // to be processed later. + if (quickfix_busy > 0) { + locstack_queue_delreq(qi); + } else { + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(&qi->qf_lists[i]); + } + xfree(qi); } - xfree(qi); } } @@ -1671,6 +1700,50 @@ void qf_free_all(win_T *wp) } } +// Delay freeing of location list stacks when the quickfix code is running. +// Used to avoid problems with autocmds freeing location list stacks when the +// quickfix code is still referencing the stack. +// Must always call decr_quickfix_busy() exactly once after this. +static void incr_quickfix_busy(void) +{ + quickfix_busy++; +} + +// Safe to free location list stacks. Process any delayed delete requests. +static void decr_quickfix_busy(void) +{ + quickfix_busy--; + if (quickfix_busy == 0) { + // No longer referencing the location lists. Process all the pending + // delete requests. + while (qf_delq_head != NULL) { + qf_delq_T *q = qf_delq_head; + + qf_delq_head = q->next; + ll_free_all(&q->qi); + xfree(q); + } + } +#ifdef ABORT_ON_INTERNAL_ERROR + if (quickfix_busy < 0) { + EMSG("quickfix_busy has become negative"); + abort(); + } +#endif +} + +#if defined(EXITFREE) +void check_quickfix_busy(void) +{ + if (quickfix_busy != 0) { + EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy); +# ifdef ABORT_ON_INTERNAL_ERROR + abort(); +# endif + } +} +#endif + /// Add an entry to the end of the list of errors. /// /// @param qi quickfix list @@ -3433,6 +3506,7 @@ void ex_copen(exarg_T *eap) qf_list_T *qfl; int height; int status = FAIL; + int lnum; if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); @@ -3442,6 +3516,8 @@ void ex_copen(exarg_T *eap) } } + incr_quickfix_busy(); + if (eap->addr_count != 0) { assert(eap->line2 <= INT_MAX); height = (int)eap->line2; @@ -3457,17 +3533,23 @@ void ex_copen(exarg_T *eap) } if (status == FAIL) { if (qf_open_new_cwindow(qi, height) == FAIL) { + decr_quickfix_busy(); return; } } qfl = &qi->qf_lists[qi->qf_curlist]; qf_set_title_var(qfl); + // Save the current index here, as updating the quickfix buffer may free + // the quickfix list + lnum = qfl->qf_index; // Fill the buffer with the quickfix list. qf_fill_buffer(qi, curbuf, NULL); - curwin->w_cursor.lnum = qfl->qf_index; + decr_quickfix_busy(); + + curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; check_cursor(); update_topline(); // scroll to show the line @@ -3964,6 +4046,7 @@ void ex_make(exarg_T *eap) do_shell((char_u *)cmd, 0); + incr_quickfix_busy(); res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, @@ -3991,6 +4074,7 @@ void ex_make(exarg_T *eap) } cleanup: + decr_quickfix_busy(); os_remove((char *)fname); xfree(fname); xfree(cmd); @@ -4345,6 +4429,8 @@ void ex_cfile(exarg_T *eap) wp = curwin; } + incr_quickfix_busy(); + // This function is used by the :cfile, :cgetfile and :caddfile // commands. // :cfile always creates a new quickfix list and jumps to the @@ -4359,6 +4445,7 @@ void ex_cfile(exarg_T *eap) if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + decr_quickfix_busy(); return; } } @@ -4376,6 +4463,8 @@ void ex_cfile(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + + decr_quickfix_busy(); } /// Return the vimgrep autocmd name. @@ -4651,6 +4740,8 @@ void ex_vimgrep(exarg_T *eap) * ":lcd %:p:h" changes the meaning of short path names. */ os_dirname(dirname_start, MAXPATHL); + incr_quickfix_busy(); + // Remember the current quickfix list identifier, so that we can check for // autocommands changing the current quickfix list. unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; @@ -4682,6 +4773,7 @@ void ex_vimgrep(exarg_T *eap) // buffer above, autocommands might have changed the quickfix list. if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) { FreeWild(fcount, fnames); + decr_quickfix_busy(); goto theend; } save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; @@ -4766,11 +4858,9 @@ void ex_vimgrep(exarg_T *eap) // The QuickFixCmdPost autocmd may free the quickfix list. Check the list // is still valid. - if (!qflist_valid(wp, save_qfid)) { - goto theend; - } - - if (qf_restore_list(qi, save_qfid) == FAIL) { + if (!qflist_valid(wp, save_qfid) + || qf_restore_list(qi, save_qfid) == FAIL) { + decr_quickfix_busy(); goto theend; } @@ -4783,6 +4873,8 @@ void ex_vimgrep(exarg_T *eap) } else EMSG2(_(e_nomatch2), s); + decr_quickfix_busy(); + /* If we loaded a dummy buffer into the current window, the autocommands * may have messed up things, need to redraw and recompute folds. */ if (redraw_for_dummy) { @@ -5763,7 +5855,12 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, if (action == 'f') { // Free the entire quickfix or location list stack qf_free_stack(wp, qi); - } else if (what != NULL) { + return OK; + } + + incr_quickfix_busy(); + + if (what != NULL) { retval = qf_set_properties(qi, what, action, title); } else { retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); @@ -5772,6 +5869,8 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, } } + decr_quickfix_busy(); + return retval; } @@ -5898,10 +5997,16 @@ void ex_cbuffer(exarg_T *eap) qf_title = IObuff; } + incr_quickfix_busy(); + int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, (eap->cmdidx != CMD_caddbuffer && eap->cmdidx != CMD_laddbuffer), eap->line1, eap->line2, qf_title, NULL); + if (qf_stack_empty(qi)) { + decr_quickfix_busy(); + return; + } if (res >= 0) { qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } @@ -5925,6 +6030,7 @@ void ex_cbuffer(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + decr_quickfix_busy(); } } } @@ -5979,11 +6085,16 @@ void ex_cexpr(exarg_T *eap) if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) || tv.v_type == VAR_LIST) { + incr_quickfix_busy(); int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), (linenr_T)0, (linenr_T)0, qf_cmdtitle(*eap->cmdlinep), NULL); + if (qf_stack_empty(qi)) { + decr_quickfix_busy(); + goto cleanup; + } if (res >= 0) { qf_list_changed(&qi->qf_lists[qi->qf_curlist]); } @@ -6002,9 +6113,11 @@ void ex_cexpr(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + decr_quickfix_busy(); } else { EMSG(_("E777: String or List expected")); } +cleanup: tv_clear(&tv); } } @@ -6171,6 +6284,8 @@ void ex_helpgrep(exarg_T *eap) qi = hgr_get_ll(&new_qi); } + incr_quickfix_busy(); + // Check for a specified language char_u *const lang = check_help_lang(eap->arg); regmatch_T regmatch = { @@ -6205,6 +6320,7 @@ void ex_helpgrep(exarg_T *eap) curbuf->b_fname, true, curbuf); if (!new_qi && IS_LL_STACK(qi) && qf_find_buf(qi) == NULL) { // autocommands made "qi" invalid + decr_quickfix_busy(); return; } } @@ -6216,6 +6332,8 @@ void ex_helpgrep(exarg_T *eap) EMSG2(_(e_nomatch2), eap->arg); } + decr_quickfix_busy(); + if (eap->cmdidx == CMD_lhelpgrep) { // If the help window is not opened or if it already points to the // correct location list, then free the new location list. diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index a5ce41d8d2..26b43d9818 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -3339,7 +3339,28 @@ func Test_lexpr_crash() augroup QF_Test au! augroup END + + enew | only + augroup QF_Test + au! + au BufNew * call setloclist(0, [], 'f') + augroup END + lexpr 'x:1:x' + augroup QF_Test + au! + augroup END + enew | only + lexpr '' + lopen + augroup QF_Test + au! + au FileType * call setloclist(0, [], 'f') + augroup END + lexpr '' + augroup QF_Test + au! + augroup END endfunc " The following test used to crash Vim -- cgit From 0f93528d1f44eb3fdee7e91c6d21c86eb0dcdbf8 Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Tue, 24 Sep 2019 02:07:51 -0400 Subject: vim-patch:8.1.0532: cannot distinguish between quickfix and location list Problem: Cannot distinguish between quickfix and location list. Solution: Add an explicit type variable. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/2d67d307ee5dba911e8fbe73193bf596ebf76c1a --- src/nvim/quickfix.c | 54 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 78e68b0885..7fd357a76a 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -80,6 +80,14 @@ struct qfline_S { #define LISTCOUNT 10 #define INVALID_QFIDX (-1) +// Quickfix list type. +typedef enum +{ + QFLT_QUICKFIX, // Quickfix list - global list + QFLT_LOCATION, // Location list - per window list + QFLT_INTERNAL // Internal - Temporary list used by getqflist()/getloclist() +} qfltype_T; + /// Quickfix/Location list definition /// /// Usually the list contains one or more entries. But an empty list can be @@ -87,6 +95,7 @@ struct qfline_S { /// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { unsigned qf_id; ///< Unique identifier for this list + qfltype_T qfl_type; qfline_T *qf_start; ///< pointer to the first error qfline_T *qf_last; ///< pointer to the last error qfline_T *qf_ptr; ///< pointer to the current error @@ -120,6 +129,7 @@ struct qf_info_S { int qf_listcount; /* current number of lists */ int qf_curlist; /* current error list */ qf_list_T qf_lists[LISTCOUNT]; + qfltype_T qfl_type; // type of list }; static qf_info_T ql_info; // global quickfix list @@ -211,8 +221,10 @@ typedef struct { #define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) // Quickfix and location list stack check helper macros -#define IS_QF_STACK(qi) (qi == &ql_info) -#define IS_LL_STACK(qi) (qi != &ql_info) +#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX) +#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION) +#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX) +#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION) // // Return location list for window 'wp' @@ -1225,6 +1237,7 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qfl = &qi->qf_lists[qi->qf_curlist]; memset(qfl, 0, (size_t)(sizeof(qf_list_T))); qf_store_title(qfl, qf_title); + qfl->qfl_type = qi->qfl_type; qfl->qf_id = ++last_qf_id; } @@ -1777,7 +1790,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_fnum = bufnum; if (buf != NULL) { buf->b_has_qf_entry |= - IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; } } else { qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname); @@ -1828,12 +1841,13 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, return OK; } -// Allocate a new location list stack -static qf_info_T *ll_new_list(void) +// Allocate a new quickfix/location list stack +static qf_info_T *qf_alloc_stack(qfltype_T qfltype) FUNC_ATTR_NONNULL_RET { qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; + qi->qfl_type = qfltype; return qi; } @@ -1852,8 +1866,9 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) */ ll_free_all(&wp->w_llist_ref); - if (wp->w_llist == NULL) - wp->w_llist = ll_new_list(); /* new location list */ + if (wp->w_llist == NULL) { + wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list + } return wp->w_llist; } @@ -1908,6 +1923,7 @@ static int copy_loclist(const qf_list_T *from_qfl, FUNC_ATTR_NONNULL_ALL { // Some of the fields are populated by qf_add_entry() + to_qfl->qfl_type = from_qfl->qfl_type; to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; to_qfl->qf_count = 0; to_qfl->qf_index = 0; @@ -1969,7 +1985,7 @@ void copy_loclist_stack(win_T *from, win_T *to) } // allocate a new location list - to->w_llist = ll_new_list(); + to->w_llist = qf_alloc_stack(QFLT_LOCATION); to->w_llist->qf_listcount = qi->qf_listcount; @@ -2042,7 +2058,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, return 0; } buf->b_has_qf_entry = - IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; return buf->b_fnum; } @@ -2609,6 +2625,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, int *opened_window, int *abort) { qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qfltype_T qfl_type = qfl->qfl_type; int retval = OK; if (qf_ptr->qf_type == 1) { @@ -2628,7 +2645,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); - if (IS_LL_STACK(qi)) { + if (qfl_type == QFLT_LOCATION) { // Location list. Check whether the associated window is still // present and the list is still valid. if (!win_valid_any_tab(oldwin)) { @@ -2640,7 +2657,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, *abort = true; } } else if (!is_qf_entry_present(qfl, qf_ptr)) { - if (IS_QF_STACK(qi)) { + if (qfl_type == QFLT_QUICKFIX) { EMSG(_("E925: Current quickfix was changed")); } else { EMSG(_(e_loc_list_changed)); @@ -5165,7 +5182,7 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) } list_T *l = tv_list_alloc(kListLenMayKnow); - qf_info_T *const qi = ll_new_list(); + qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { @@ -5292,7 +5309,10 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what) } /// Return default values for quickfix list properties in retdict. -static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) +static int qf_getprop_defaults(qf_info_T *qi, + int flags, + int locstack, + dict_T *retdict) { int status = OK; @@ -5324,7 +5344,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) if ((status == OK) && (flags & QF_GETLIST_TICK)) { status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0); } - if ((status == OK) && (qi != &ql_info) && (flags & QF_GETLIST_FILEWINID)) { + if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) { status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); } @@ -5424,7 +5444,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) // List is not present or is empty if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) { - return qf_getprop_defaults(qi, flags, retdict); + return qf_getprop_defaults(qi, flags, wp != NULL, retdict); } qfl = &qi->qf_lists[qf_idx]; @@ -5826,7 +5846,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) } else if (IS_LL_WINDOW(orig_wp)) { // If the location list window is open, then create a new empty location // list - qf_info_T *new_ll = ll_new_list(); + qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); // first free the list reference in the location list window ll_free_all(&orig_wp->w_llist_ref); @@ -6144,7 +6164,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) } if (qi == NULL) { // Allocate a new location list for help text matches - qi = ll_new_list(); + qi = qf_alloc_stack(QFLT_LOCATION); *new_ll = true; } -- cgit From 7714c9e2104bdf4d7e1e03e9402a23a5659b5b4f Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Wed, 18 Sep 2019 10:55:09 -0400 Subject: vim-patch:8.1.1006: repeated code in quickfix support Problem: Repeated code in quickfix support. Solution: Move code to functions. (Yegappan Lakshmanan, closes vim/vim#4091) https://github.com/vim/vim/commit/4aa47b28f453b40d3b93ef209a3447c62b6f855b --- src/nvim/quickfix.c | 103 +++++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 7fd357a76a..e61dbe8409 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1207,6 +1207,12 @@ static char_u * qf_cmdtitle(char_u *cmd) return qftitle_str; } +// Return a pointer to the current list in the specified quickfix stack +static qf_list_T * qf_get_curlist(qf_info_T *qi) +{ + return &qi->qf_lists[qi->qf_curlist]; +} + // Prepare for adding a new quickfix list. If the current list is in the // middle of the stack, then all the following lists are freed and then // the new list is added. @@ -1234,7 +1240,7 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qi->qf_curlist = LISTCOUNT - 1; } else qi->qf_curlist = qi->qf_listcount++; - qfl = &qi->qf_lists[qi->qf_curlist]; + qfl = qf_get_curlist(qi); memset(qfl, 0, (size_t)(sizeof(qf_list_T))); qf_store_title(qfl, qf_title); qfl->qfl_type = qi->qfl_type; @@ -2375,6 +2381,13 @@ static win_T *qf_find_help_win(void) return NULL; } +// Set the location list for the specified window to 'qi'. +static void win_set_loclist(win_T *wp, qf_info_T *qi) +{ + wp->w_llist = qi; + qi->qf_refcount++; +} + /// Find a help window or open one. static int jump_to_help_window(qf_info_T *qi, int *opened_window) { @@ -2414,8 +2427,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) if (IS_LL_STACK(qi)) { // not a quickfix list // The new window should use the supplied location list - curwin->w_llist = qi; - qi->qf_refcount++; + win_set_loclist(curwin, qi); } } @@ -2481,8 +2493,7 @@ static int qf_open_new_file_win(qf_info_T *ll_ref) if (ll_ref != NULL) { // The new window should use the location list from the // location list window - curwin->w_llist = ll_ref; - ll_ref->qf_refcount++; + win_set_loclist(curwin, ll_ref); } return OK; } @@ -2524,8 +2535,9 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, // If the location list for the window is not set, then set it // to the location list from the location window if (win->w_llist == NULL) { - win->w_llist = ll_ref; - ll_ref->qf_refcount++; + // The new window should use the location list from the + // location list window + win_set_loclist(win, ll_ref); } } @@ -2624,7 +2636,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, int *opened_window, int *abort) { - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qf_list_T *qfl = qf_get_curlist(qi); qfltype_T qfl_type = qfl->qfl_type; int retval = OK; @@ -2721,7 +2733,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, update_topline_redraw(); } snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, - qi->qf_lists[qi->qf_curlist].qf_count, + qf_get_curlist(qi)->qf_count, qf_ptr->qf_cleared ? _(" (line deleted)") : "", (char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); // Add the message, skipping leading whitespace and newlines. @@ -2774,7 +2786,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) return; } - qfl = &qi->qf_lists[qi->qf_curlist]; + qfl = qf_get_curlist(qi); qf_ptr = qfl->qf_ptr; old_qf_ptr = qf_ptr; @@ -3007,7 +3019,7 @@ void qf_list(exarg_T *eap) EMSG(_(e_trailing)); return; } - qfl = &qi->qf_lists[qi->qf_curlist]; + qfl = qf_get_curlist(qi); if (plus) { i = qfl->qf_index; idx2 = i + idx1; @@ -3370,7 +3382,7 @@ void ex_cwindow(exarg_T *eap) return; } - qfl = &qi->qf_lists[qi->qf_curlist]; + qfl = qf_get_curlist(qi); /* Look for an existing quickfix window. */ win = qf_find_win(qi); @@ -3555,14 +3567,14 @@ void ex_copen(exarg_T *eap) } } - qfl = &qi->qf_lists[qi->qf_curlist]; + qfl = qf_get_curlist(qi); qf_set_title_var(qfl); // Save the current index here, as updating the quickfix buffer may free // the quickfix list lnum = qfl->qf_index; // Fill the buffer with the quickfix list. - qf_fill_buffer(qi, curbuf, NULL); + qf_fill_buffer(qfl, curbuf, NULL); decr_quickfix_busy(); @@ -3622,7 +3634,7 @@ linenr_T qf_current_entry(win_T *wp) /* In the location list window, use the referenced location list */ qi = wp->w_llist_ref; - return qi->qf_lists[qi->qf_curlist].qf_index; + return qf_get_curlist(qi)->qf_index; } /* @@ -3636,7 +3648,7 @@ qf_win_pos_update ( ) { win_T *win; - int qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + int qf_index = qf_get_curlist(qi)->qf_index; /* * Put the cursor on the current error in the quickfix window, so that @@ -3717,7 +3729,7 @@ static void qf_update_win_titlevar(qf_info_T *qi) if ((win = qf_find_win(qi)) != NULL) { win_T *curwin_save = curwin; curwin = win; - qf_set_title_var(&qi->qf_lists[qi->qf_curlist]); + qf_set_title_var(qf_get_curlist(qi)); curwin = curwin_save; } } @@ -3743,7 +3755,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) qf_update_win_titlevar(qi); - qf_fill_buffer(qi, buf, old_last); + qf_fill_buffer(qf_get_curlist(qi), buf, old_last); buf_inc_changedtick(buf); if (old_last == NULL) { @@ -3832,8 +3844,8 @@ static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, // If "old_last" is not NULL append the items after this one. // When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() // is used and autocommands will be triggered. -static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) - FUNC_ATTR_NONNULL_ARG(1, 2) +static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) + FUNC_ATTR_NONNULL_ARG(2) { linenr_T lnum; qfline_T *qfp; @@ -3852,8 +3864,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) } // Check if there is anything to display - if (!qf_stack_empty(qi)) { - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + if (qfl != NULL) { char_u dirname[MAXPATHL]; *dirname = NUL; @@ -3866,7 +3877,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) qfp = old_last->qf_next; lnum = buf->b_ml.ml_line_count; } - while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) { + while (lnum < qfl->qf_count) { if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) { break; } @@ -3935,7 +3946,7 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) static int qf_restore_list(qf_info_T *qi, unsigned save_qfid) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (qi->qf_lists[qi->qf_curlist].qf_id != save_qfid) { + if (qf_get_curlist(qi)->qf_id != save_qfid) { const int curlist = qf_id2nr(qi, save_qfid); if (curlist < 0) { // list is not present @@ -4076,11 +4087,11 @@ void ex_make(exarg_T *eap) } } if (res >= 0) { - qf_list_changed(&qi->qf_lists[qi->qf_curlist]); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); @@ -4163,8 +4174,8 @@ size_t qf_get_size(exarg_T *eap) size_t sz = 0; qfline_T *qfp; size_t i; - assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0); - qfl = &qi->qf_lists[qi->qf_curlist]; + assert(qf_get_curlist(qi)->qf_count >= 0); + qfl = qf_get_curlist(qi); for (i = 0, qfp = qfl->qf_start; i < (size_t)qfl->qf_count && qfp != NULL; i++, qfp = qfp->qf_next) { if (!qfp->qf_valid) { @@ -4199,8 +4210,8 @@ size_t qf_get_cur_idx(exarg_T *eap) } } - assert(qi->qf_lists[qi->qf_curlist].qf_index >= 0); - return (size_t)qi->qf_lists[qi->qf_curlist].qf_index; + assert(qf_get_curlist(qi)->qf_index >= 0); + return (size_t)qf_get_curlist(qi)->qf_index; } /// Returns the current index in the quickfix/location list, @@ -4219,7 +4230,7 @@ int qf_get_cur_valid_idx(exarg_T *eap) } } - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qf_list_T *qfl = qf_get_curlist(qi); // Check if the list has valid errors. if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { @@ -4343,7 +4354,7 @@ void ex_cc(exarg_T *eap) n = 1; } size_t valid_entry = qf_get_nth_valid_entry( - &qi->qf_lists[qi->qf_curlist], n, + qf_get_curlist(qi), n, eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); assert(valid_entry <= INT_MAX); errornr = (int)valid_entry; @@ -4467,9 +4478,9 @@ void ex_cfile(exarg_T *eap) } } if (res >= 0) { - qf_list_changed(&qi->qf_lists[qi->qf_curlist]); + qf_list_changed(qf_get_curlist(qi)); } - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf); } @@ -4761,7 +4772,7 @@ void ex_vimgrep(exarg_T *eap) // Remember the current quickfix list identifier, so that we can check for // autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; seconds = (time_t)0; for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { @@ -4793,7 +4804,7 @@ void ex_vimgrep(exarg_T *eap) decr_quickfix_busy(); goto theend; } - save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + save_qfid = qf_get_curlist(qi)->qf_id; if (buf == NULL) { if (!got_int) @@ -4861,7 +4872,7 @@ void ex_vimgrep(exarg_T *eap) FreeWild(fcount, fnames); - qfl = &qi->qf_lists[qi->qf_curlist]; + qfl = qf_get_curlist(qi); qfl->qf_nonevalid = false; qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; @@ -5822,7 +5833,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) if (qfwin != NULL) { // If the quickfix/location list window is open, then clear it if (qi->qf_curlist < qi->qf_listcount) { - qf_free(&qi->qf_lists[qi->qf_curlist]); + qf_free(qf_get_curlist(qi)); } qf_update_buffer(qi, NULL); } @@ -5853,8 +5864,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) orig_wp->w_llist_ref = new_ll; if (llwin != NULL) { - llwin->w_llist = new_ll; - new_ll->qf_refcount++; + win_set_loclist(wp, new_ll); } } } @@ -5885,7 +5895,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, } else { retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); if (retval == OK) { - qf_list_changed(&qi->qf_lists[qi->qf_curlist]); + qf_list_changed(qf_get_curlist(qi)); } } @@ -6028,11 +6038,11 @@ void ex_cbuffer(exarg_T *eap) return; } if (res >= 0) { - qf_list_changed(&qi->qf_lists[qi->qf_curlist]); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { const buf_T *const curbuf_old = curbuf; apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, @@ -6116,11 +6126,11 @@ void ex_cexpr(exarg_T *eap) goto cleanup; } if (res >= 0) { - qf_list_changed(&qi->qf_lists[qi->qf_curlist]); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -6320,7 +6330,7 @@ void ex_helpgrep(exarg_T *eap) vim_regfree(regmatch.regprog); - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qf_list_T *qfl = qf_get_curlist(qi); qfl->qf_nonevalid = false; qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; @@ -6367,3 +6377,4 @@ void ex_helpgrep(exarg_T *eap) } } + -- cgit From d10d7aed79bc9945c2e8bd4ca4e476c46360983e Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Mon, 23 Sep 2019 13:37:05 -0400 Subject: vim-patch:8.1.1030: quickfix function arguments are inconsistent Problem: Quickfix function arguments are inconsistent. Solution: Pass a list pointer instead of info and index. (Yegappan Lakshmanan, closes vim/vim#4135) https://github.com/vim/vim/commit/0398e00a1bf79e85223fb26938c8dd0d54883b77 --- src/nvim/quickfix.c | 152 +++++++++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 78 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index e61dbe8409..a03d73ea1d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -845,24 +845,26 @@ static bool qf_stack_empty(const qf_info_T *qi) } // Returns true if the specified quickfix/location list is empty. -static bool qf_list_empty(const qf_info_T *qi, int qf_idx) +static bool qf_list_empty(qf_list_T *qfl) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT) { - return true; - } - return qi->qf_lists[qf_idx].qf_count <= 0; + return qfl == NULL || qfl->qf_count <= 0; +} + +// Return a pointer to a list in the specified quickfix stack +static qf_list_T * qf_get_list(qf_info_T *qi, int idx) +{ + return &qi->qf_lists[idx]; } /// Parse a line and get the quickfix fields. /// Return the QF_ status. -static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf, +static int qf_parse_line(qf_list_T *qfl, char_u *linebuf, size_t linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; int idx = 0; char_u *tail = NULL; - qf_list_T *qfl = &qi->qf_lists[qf_idx]; int status; restofline: @@ -918,7 +920,7 @@ restofline: qfl->qf_multiignore = false; // reset continuation } else if (vim_strchr((char_u *)"CZ", idx) != NULL) { // continuation of multi-line msg - status = qf_parse_multiline_pfx(qi, qf_idx, idx, qfl, fields); + status = qf_parse_multiline_pfx(idx, qfl, fields); if (status != QF_OK) { return status; } @@ -1064,12 +1066,12 @@ qf_init_ext( } else { // Adding to existing list, use last entry. adding = true; - if (!qf_list_empty(qi, qf_idx)) { + if (!qf_list_empty(qf_get_list(qi, qf_idx) )) { old_last = qi->qf_lists[qf_idx].qf_last; } } - qf_list_T *qfl = &qi->qf_lists[qf_idx]; + qf_list_T *qfl = qf_get_list(qi, qf_idx); // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { @@ -1113,7 +1115,7 @@ qf_init_ext( break; } - status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen, + status = qf_parse_line(qfl, state.linebuf, state.linelen, fmt_first, &fields); if (status == QF_FAIL) { goto error2; @@ -1122,8 +1124,7 @@ qf_init_ext( continue; } - if (qf_add_entry(qi, - qf_idx, + if (qf_add_entry(qfl, qfl->qf_directory, (*fields.namebuf || qfl->qf_directory) ? fields.namebuf : ((qfl->qf_currfile && fields.valid) @@ -1210,7 +1211,7 @@ static char_u * qf_cmdtitle(char_u *cmd) // Return a pointer to the current list in the specified quickfix stack static qf_list_T * qf_get_curlist(qf_info_T *qi) { - return &qi->qf_lists[qi->qf_curlist]; + return qf_get_list(qi, qi->qf_curlist); } // Prepare for adding a new quickfix list. If the current list is in the @@ -1616,8 +1617,7 @@ static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen, } /// Parse multi-line error format prefixes (%C and %Z) -static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, - qf_list_T *qfl, qffields_T *fields) +static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) { if (!qfl->qf_multiignore) { qfline_T *qfprev = qfl->qf_last; @@ -1648,7 +1648,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, } qfprev->qf_viscol = fields->use_viscol; if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory, + qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory, *fields->namebuf || qfl->qf_directory ? fields->namebuf : qfl->qf_currfile && fields->valid @@ -1694,7 +1694,7 @@ static void ll_free_all(qf_info_T **pqi) locstack_queue_delreq(qi); } else { for (i = 0; i < qi->qf_listcount; i++) { - qf_free(&qi->qf_lists[i]); + qf_free(qf_get_list(qi, i)); } xfree(qi); } @@ -1714,7 +1714,7 @@ void qf_free_all(win_T *wp) } else { // quickfix list for (i = 0; i < qi->qf_listcount; i++) { - qf_free(&qi->qf_lists[i]); + qf_free(qf_get_list(qi, i)); } } } @@ -1765,8 +1765,7 @@ void check_quickfix_busy(void) /// Add an entry to the end of the list of errors. /// -/// @param qi quickfix list -/// @param qf_idx list index +/// @param qfl quickfix list entry /// @param dir optional directory name /// @param fname file name or NULL /// @param module module name or NULL @@ -1781,12 +1780,11 @@ void check_quickfix_busy(void) /// @param valid valid entry /// /// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, +static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, char_u *module, int bufnum, char_u *mesg, long lnum, int col, char_u vis_col, char_u *pattern, int nr, char_u type, char_u valid) { - qf_list_T *qfl = &qi->qf_lists[qf_idx]; qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1799,7 +1797,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; } } else { - qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname); + qfp->qf_fnum = qf_get_fnum(qfl, dir, fname); } qfp->qf_text = vim_strsave(mesg); qfp->qf_lnum = lnum; @@ -1823,7 +1821,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_valid = valid; lastp = &qfl->qf_last; - if (qf_list_empty(qi, qf_idx)) { + if (qf_list_empty(qfl)) { // first element in the list qfl->qf_start = qfp; qfl->qf_ptr = qfp; @@ -1880,19 +1878,17 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) // Copy location list entries from 'from_qfl' to 'to_qfl'. static int copy_loclist_entries(const qf_list_T *from_qfl, - qf_list_T *to_qfl, - qf_info_T *to_qi) + qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { int i; - const qfline_T *from_qfp; + qfline_T *from_qfp; // copy all the location entries in this list for (i = 0, from_qfp = from_qfl->qf_start; i < from_qfl->qf_count && from_qfp != NULL; i++, from_qfp = from_qfp->qf_next) { - if (qf_add_entry(to_qi, - to_qi->qf_curlist, + if (qf_add_entry(to_qfl, NULL, NULL, from_qfp->qf_module, @@ -1923,9 +1919,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, } // Copy the specified location list 'from_qfl' to 'to_qfl'. -static int copy_loclist(const qf_list_T *from_qfl, - qf_list_T *to_qfl, - qf_info_T *to_qi) +static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { // Some of the fields are populated by qf_add_entry() @@ -1949,8 +1943,9 @@ static int copy_loclist(const qf_list_T *from_qfl, } else { to_qfl->qf_ctx = NULL; } + if (from_qfl->qf_count) { - if (copy_loclist_entries(from_qfl, to_qfl, to_qi) == FAIL) { + if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) { return FAIL; } } @@ -1999,8 +1994,8 @@ void copy_loclist_stack(win_T *from, win_T *to) for (int idx = 0; idx < qi->qf_listcount; idx++) { to->w_llist->qf_curlist = idx; - if (copy_loclist(&qi->qf_lists[idx], &to->w_llist->qf_lists[idx], - to->w_llist) == FAIL) { + if (copy_loclist(qf_get_list(qi, idx), + qf_get_list(to->w_llist, idx)) == FAIL) { qf_free_all(to); return; } @@ -2011,10 +2006,8 @@ void copy_loclist_stack(win_T *from, win_T *to) // Get buffer number for file "directory/fname". // Also sets the b_has_qf_entry flag. -static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, - char_u *fname) +static int qf_get_fnum(qf_list_T *qfl, char_u *directory, char_u *fname ) { - qf_list_T *qfl = &qi->qf_lists[qf_idx]; char_u *ptr = NULL; char_u *bufname; buf_T *buf; @@ -2534,7 +2527,7 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, // If the location list for the window is not set, then set it // to the location list from the location window - if (win->w_llist == NULL) { + if (win->w_llist == NULL && ll_ref != NULL) { // The new window should use the location list from the // location list window win_set_loclist(win, ll_ref); @@ -2781,7 +2774,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) if (qi == NULL) qi = &ql_info; - if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -3005,7 +2998,7 @@ void qf_list(exarg_T *eap) } } - if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -3177,7 +3170,7 @@ void qf_history(exarg_T *eap) if (is_loclist_cmd(eap->cmdidx)) { qi = GET_LOC_LIST(curwin); } - if (qf_stack_empty(qi) || qf_list_empty(qi, qi->qf_curlist)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { MSG(_("No entries")); } else { for (i = 0; i < qi->qf_listcount; i++) { @@ -3268,9 +3261,11 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, } for (idx = 0; idx < qi->qf_listcount; idx++) { - if (!qf_list_empty(qi, idx)) { - for (i = 0, qfp = qi->qf_lists[idx].qf_start; - i < qi->qf_lists[idx].qf_count && qfp != NULL; + qf_list_T *qfl = qf_get_list(qi, idx); + + if (!qf_list_empty(qfl)) { + for (i = 0, qfp = qfl->qf_start; + i < qfl->qf_count && qfp != NULL; i++, qfp = qfp->qf_next) { if (qfp->qf_fnum == curbuf->b_fnum) { found_one = true; @@ -3344,7 +3339,7 @@ void qf_view_result(bool split) if (IS_LL_WINDOW(curwin)) { qi = GET_LOC_LIST(curwin); } - if (qf_list_empty(qi, qi->qf_curlist)) { + if (qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -3394,7 +3389,7 @@ void ex_cwindow(exarg_T *eap) */ if (qf_stack_empty(qi) || qfl->qf_nonevalid - || qf_list_empty(qi, qi->qf_curlist)) { + || qf_list_empty(qf_get_curlist(qi))) { if (win != NULL) { ex_cclose(eap); } @@ -3965,7 +3960,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) return; } // Autocommands might have cleared the list, check for that - if (!qf_list_empty(qi, qi->qf_curlist)) { + if (!qf_list_empty(qf_get_curlist(qi))) { qf_jump(qi, 0, 0, forceit); } } @@ -4613,8 +4608,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, // Pass the buffer number so that it gets used even for a // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. - if (qf_add_entry(qi, - qi->qf_curlist, + if (qf_add_entry(qf_get_curlist(qi), NULL, // dir fname, NULL, @@ -4893,7 +4887,7 @@ void ex_vimgrep(exarg_T *eap) } // Jump to first match. - if (!qf_list_empty(qi, qi->qf_curlist)) { + if (!qf_list_empty(qf_get_curlist(qi))) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir); @@ -5084,6 +5078,7 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { const qf_info_T *qi = qi_arg; + qf_list_T *qfl; char_u buf[2]; qfline_T *qfp; int i; @@ -5103,13 +5098,17 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) qf_idx = qi->qf_curlist; } - if (qf_idx >= qi->qf_listcount - || qf_list_empty(qi, qf_idx)) { + if (qf_idx >= qi->qf_listcount) { return FAIL; } - qfp = qi->qf_lists[qf_idx].qf_start; - for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; i++) { + qfl = qf_get_list(qi, qf_idx); + if (qf_list_empty(qfl)) { + return FAIL; + } + + qfp = qfl->qf_start; + for (i = 1; !got_int && i <= qfl->qf_count; i++) { // Handle entries with a non-existing buffer number. bufnum = qfp->qf_fnum; if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) @@ -5418,10 +5417,10 @@ static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict) } /// Return the current quickfix list index as 'idx' in retdict -static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) +static int qf_getprop_idx(qf_list_T *qfl, dict_T *retdict) { - int curidx = qi->qf_lists[qf_idx].qf_index; - if (qf_list_empty(qi, qf_idx)) { + int curidx = qfl->qf_index; + if (qf_list_empty(qfl)) { // For empty lists, current index is set to 0 curidx = 0; } @@ -5458,7 +5457,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) return qf_getprop_defaults(qi, flags, wp != NULL, retdict); } - qfl = &qi->qf_lists[qf_idx]; + qfl = qf_get_list(qi, qf_idx); if (flags & QF_GETLIST_TITLE) { status = qf_getprop_title(qfl, retdict); @@ -5479,7 +5478,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id); } if ((status == OK) && (flags & QF_GETLIST_IDX)) { - status = qf_getprop_idx(qi, qf_idx, retdict); + status = qf_getprop_idx(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_SIZE)) { status = tv_dict_add_nr(retdict, S_LEN("size"), @@ -5499,8 +5498,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) // Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the // items in the dict 'd'. static int qf_add_entry_from_dict( - qf_info_T *qi, - int qf_idx, + qf_list_T *qfl, const dict_T *d, bool first_entry) FUNC_ATTR_NONNULL_ALL @@ -5546,17 +5544,16 @@ static int qf_add_entry_from_dict( valid = tv_dict_get_number(d, "valid"); } - const int status = qf_add_entry(qi, - qf_idx, - NULL, // dir + const int status = qf_add_entry(qfl, + NULL, // dir (char_u *)filename, (char_u *)module, bufnum, (char_u *)text, lnum, col, - vcol, // vis_col - (char_u *)pattern, // search pattern + vcol, // vis_col + (char_u *)pattern, // search pattern nr, (char_u)(type == NULL ? NUL : *type), valid); @@ -5574,7 +5571,7 @@ static int qf_add_entry_from_dict( static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char_u *title, int action) { - qf_list_T *qfl = &qi->qf_lists[qf_idx]; + qf_list_T *qfl = qf_get_list(qi, qf_idx); qfline_T *old_last = NULL; int retval = OK; @@ -5582,8 +5579,8 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, // make place for a new list qf_new_list(qi, title); qf_idx = qi->qf_curlist; - qfl = &qi->qf_lists[qf_idx]; - } else if (action == 'a' && !qf_list_empty(qi, qf_idx)) { + qfl = qf_get_list(qi, qf_idx); + } else if (action == 'a' && !qf_list_empty(qfl)) { // Adding to existing list, use last entry. old_last = qfl->qf_last; } else if (action == 'r') { @@ -5601,7 +5598,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, continue; } - retval = qf_add_entry_from_dict(qi, qf_idx, d, li == tv_list_first(list)); + retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list)); if (retval == FAIL) { break; } @@ -5615,7 +5612,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, } if (action != 'a') { qfl->qf_ptr = qfl->qf_start; - if (!qf_list_empty(qi, qf_idx)) { + if (!qf_list_empty(qfl)) { qfl->qf_index = 1; } } @@ -5686,7 +5683,7 @@ static int qf_setprop_title(qf_info_T *qi, int qf_idx, const dict_T *what, const dictitem_T *di) FUNC_ATTR_NONNULL_ALL { - qf_list_T *qfl = &qi->qf_lists[qf_idx]; + qf_list_T *qfl = qf_get_list(qi, qf_idx); if (di->di_tv.v_type != VAR_STRING) { return FAIL; } @@ -5790,7 +5787,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, qf_idx = qi->qf_curlist; } - qfl = &qi->qf_lists[qf_idx]; + qfl = qf_get_list(qi, qf_idx); if ((di = tv_dict_find(what, S_LEN("title"))) != NULL) { retval = qf_setprop_title(qi, qf_idx, what, di); } @@ -6205,8 +6202,7 @@ static void hgr_search_file( line[--l] = NUL; } - if (qf_add_entry(qi, - qi->qf_curlist, + if (qf_add_entry(qf_get_curlist(qi), NULL, // dir fname, NULL, @@ -6356,7 +6352,7 @@ void ex_helpgrep(exarg_T *eap) } // Jump to first match. - if (!qf_list_empty(qi, qi->qf_curlist)) { + if (!qf_list_empty(qf_get_curlist(qi))) { qf_jump(qi, 0, 0, false); } else { EMSG2(_(e_nomatch2), eap->arg); -- cgit From e55eac92c60552d5fc7693fd614d5949e6ff88a6 Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Fri, 13 Sep 2019 23:49:01 -0400 Subject: vim-patch:8.1.1062: quickfix code is repeated Problem: Quickfix code is repeated. Solution: Define FOR_ALL_QFL_ITEMS(). Move some code to separate functions. (Yegappan Lakshmanan, closes vim/vim#4166) https://github.com/vim/vim/commit/a16123a666b4656543614cb5bdaa69ea69f35d30 --- src/nvim/quickfix.c | 362 ++++++++++++++++++++++++++++------------------------ 1 file changed, 195 insertions(+), 167 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index a03d73ea1d..fc504f5aba 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -232,6 +232,12 @@ typedef struct { // #define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +#define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ + for (i = 0, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && i < qfl->qf_count && qfp != NULL; \ + i++, qfp = qfp->qf_next) + + // Looking up a buffer can be slow if there are many. Remember the last one // to make this a lot faster if there are multiple matches in the same file. static char_u *qf_last_bufname = NULL; @@ -1885,9 +1891,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qfline_T *from_qfp; // copy all the location entries in this list - for (i = 0, from_qfp = from_qfl->qf_start; - i < from_qfl->qf_count && from_qfp != NULL; - i++, from_qfp = from_qfp->qf_next) { + FOR_ALL_QFL_ITEMS(from_qfl, from_qfp, i) { if (qf_add_entry(to_qfl, NULL, NULL, @@ -2245,8 +2249,8 @@ static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr) int i; // Search for the entry in the current list - for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; i++, qfp = qfp->qf_next) { - if (qfp == NULL || qfp == qf_ptr) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { + if (qfp == qf_ptr) { break; } } @@ -3262,11 +3266,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, for (idx = 0; idx < qi->qf_listcount; idx++) { qf_list_T *qfl = qf_get_list(qi, idx); - if (!qf_list_empty(qfl)) { - for (i = 0, qfp = qfl->qf_start; - i < qfl->qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (qfp->qf_fnum == curbuf->b_fnum) { found_one = true; if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) { @@ -4171,8 +4172,7 @@ size_t qf_get_size(exarg_T *eap) size_t i; assert(qf_get_curlist(qi)->qf_count >= 0); qfl = qf_get_curlist(qi); - for (i = 0, qfp = qfl->qf_start; i < (size_t)qfl->qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (!qfp->qf_valid) { continue; } @@ -4417,6 +4417,21 @@ void ex_cnext(exarg_T *eap) qf_jump(qi, dir, errornr, eap->forceit); } +// Return the autocmd name for the :cfile Ex commands +static char_u * cfile_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cfile: return (char_u *)"cfile"; + case CMD_cgetfile: return (char_u *)"cgetfile"; + case CMD_caddfile: return (char_u *)"caddfile"; + case CMD_lfile: return (char_u *)"lfile"; + case CMD_lgetfile: return (char_u *)"lgetfile"; + case CMD_laddfile: return (char_u *)"laddfile"; + default: return NULL; + } +} + + /* * ":cfile"/":cgetfile"/":caddfile" commands. * ":lfile"/":lgetfile"/":laddfile" commands. @@ -4427,15 +4442,7 @@ void ex_cfile(exarg_T *eap) qf_info_T *qi = &ql_info; char_u *au_name = NULL; - switch (eap->cmdidx) { - case CMD_cfile: au_name = (char_u *)"cfile"; break; - case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break; - case CMD_caddfile: au_name = (char_u *)"caddfile"; break; - case CMD_lfile: au_name = (char_u *)"lfile"; break; - case CMD_lgetfile: au_name = (char_u *)"lgetfile"; break; - case CMD_laddfile: au_name = (char_u *)"laddfile"; break; - default: break; - } + au_name = cfile_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, false, curbuf)) { if (aborting()) { @@ -5073,16 +5080,62 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) } } +// Copy the specified quickfix entry items into a new dict and appened the dict +// to 'list'. Returns OK on success. +static int get_qfline_items(qfline_T *qfp, list_T *list) +{ + char_u buf[2]; + int bufnum; + + // Handle entries with a non-existing buffer number. + bufnum = qfp->qf_fnum; + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { + bufnum = 0; + } + + dict_T *const dict = tv_dict_alloc(); + tv_list_append_dict(list, dict); + + buf[0] = qfp->qf_type; + buf[1] = NUL; + if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL + || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) + == FAIL) + || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) == FAIL) + || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol) + == FAIL) + || (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL) + || (tv_dict_add_str( + dict, S_LEN("module"), + (qfp->qf_module == NULL ? "" : (const char *)qfp->qf_module)) + == FAIL) + || (tv_dict_add_str( + dict, S_LEN("pattern"), + (qfp->qf_pattern == NULL ? "" : (const char *)qfp->qf_pattern)) + == FAIL) + || (tv_dict_add_str( + dict, S_LEN("text"), + (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text)) + == FAIL) + || (tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL) + || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) + == FAIL)) { + // tv_dict_add* fail only if key already exist, but this is a newly + // allocated dictionary which is thus guaranteed to have no existing keys. + assert(false); + } + + return OK; +} + /// Add each quickfix error to list "list" as a dictionary. /// If qf_idx is -1, use the current list. Otherwise, use the specified list. int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { const qf_info_T *qi = qi_arg; qf_list_T *qfl; - char_u buf[2]; qfline_T *qfp; int i; - int bufnum; if (qi == NULL) { qi = &ql_info; @@ -5107,51 +5160,10 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) return FAIL; } - qfp = qfl->qf_start; - for (i = 1; !got_int && i <= qfl->qf_count; i++) { - // Handle entries with a non-existing buffer number. - bufnum = qfp->qf_fnum; - if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) - bufnum = 0; - - dict_T *const dict = tv_dict_alloc(); - tv_list_append_dict(list, dict); - - buf[0] = qfp->qf_type; - buf[1] = NUL; - if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL - || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) - == FAIL) - || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) - == FAIL) - || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol) - == FAIL) - || (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL) - || tv_dict_add_str(dict, S_LEN("module"), - (qfp->qf_module == NULL - ? "" - : (const char *)qfp->qf_module)) == FAIL - || tv_dict_add_str(dict, S_LEN("pattern"), - (qfp->qf_pattern == NULL - ? "" - : (const char *)qfp->qf_pattern)) == FAIL - || tv_dict_add_str(dict, S_LEN("text"), - (qfp->qf_text == NULL - ? "" - : (const char *)qfp->qf_text)) == FAIL - || tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL - || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) - == FAIL)) { - // tv_dict_add* fail only if key already exist, but this is a newly - // allocated dictionary which is thus guaranteed to have no existing keys. - assert(false); - } - - qfp = qfp->qf_next; - if (qfp == NULL) { - break; - } + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { + get_qfline_items(qfp, list); } + return OK; } @@ -5948,6 +5960,62 @@ bool set_ref_in_quickfix(int copyID) return abort; } +// Return the autocmd name for the :cbuffer Ex commands +static const char_u * cbuffer_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cbuffer: return "cbuffer"; + case CMD_cgetbuffer: return "cgetbuffer"; + case CMD_caddbuffer: return "caddbuffer"; + case CMD_lbuffer: return "lbuffer"; + case CMD_lgetbuffer: return "lgetbuffer"; + case CMD_laddbuffer: return "laddbuffer"; + default: return NULL; + } +} + +// Process and validate the arguments passed to the :cbuffer, :caddbuffer, +// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands. +static int cbuffer_process_args(exarg_T *eap, + buf_T **bufp, + linenr_T *line1, + linenr_T *line2) +{ + buf_T *buf = NULL; + + if (*eap->arg == NUL) + buf = curbuf; + else if (*skipwhite(skipdigits(eap->arg)) == NUL) + buf = buflist_findnr(atoi((char *)eap->arg)); + + if (buf == NULL) { + emsg(_(e_invarg)); + return FAIL; + } + + if (buf->b_ml.ml_mfp == NULL) { + emsg(_("E681: Buffer is not loaded")); + return FAIL; + } + + if (eap->addr_count == 0) { + eap->line1 = 1; + eap->line2 = buf->b_ml.ml_line_count; + } + + if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count + || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) { + emsg(_(e_invrange)); + return FAIL; + } + + *line1 = eap->line1; + *line2 = eap->line2; + *bufp = buf; + + return OK; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. @@ -5962,30 +6030,11 @@ void ex_cbuffer(exarg_T *eap) qf_info_T *qi = &ql_info; const char *au_name = NULL; win_T *wp = NULL; + char_u *qf_title; + linenr_T line1; + linenr_T line2; - switch (eap->cmdidx) { - case CMD_cbuffer: - au_name = "cbuffer"; - break; - case CMD_cgetbuffer: - au_name = "cgetbuffer"; - break; - case CMD_caddbuffer: - au_name = "caddbuffer"; - break; - case CMD_lbuffer: - au_name = "lbuffer"; - break; - case CMD_lgetbuffer: - au_name = "lgetbuffer"; - break; - case CMD_laddbuffer: - au_name = "laddbuffer"; - break; - default: - break; - } - + au_name = cbuffer_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { @@ -5999,67 +6048,67 @@ void ex_cbuffer(exarg_T *eap) wp = curwin; } - if (*eap->arg == NUL) - buf = curbuf; - else if (*skipwhite(skipdigits(eap->arg)) == NUL) - buf = buflist_findnr(atoi((char *)eap->arg)); - if (buf == NULL) - EMSG(_(e_invarg)); - else if (buf->b_ml.ml_mfp == NULL) - EMSG(_("E681: Buffer is not loaded")); - else { - if (eap->addr_count == 0) { - eap->line1 = 1; - eap->line2 = buf->b_ml.ml_line_count; - } - if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count - || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) { - EMSG(_(e_invrange)); - } else { - char_u *qf_title = qf_cmdtitle(*eap->cmdlinep); + if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) { + return; + } - if (buf->b_sfname) { - vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", - (char *)qf_title, (char *)buf->b_sfname); - qf_title = IObuff; - } + qf_title = qf_cmdtitle(*eap->cmdlinep); - incr_quickfix_busy(); + if (buf->b_sfname) { + vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", + (char *)qf_title, (char *)buf->b_sfname); + qf_title = IObuff; + } - int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, - (eap->cmdidx != CMD_caddbuffer - && eap->cmdidx != CMD_laddbuffer), - eap->line1, eap->line2, qf_title, NULL); - if (qf_stack_empty(qi)) { - decr_quickfix_busy(); - return; - } - if (res >= 0) { - qf_list_changed(qf_get_curlist(qi)); - } - // Remember the current quickfix list identifier, so that we can - // check for autocommands changing the current quickfix list. - unsigned save_qfid = qf_get_curlist(qi)->qf_id; - if (au_name != NULL) { - const buf_T *const curbuf_old = curbuf; - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, - curbuf->b_fname, true, curbuf); - if (curbuf != curbuf_old) { - // Autocommands changed buffer, don't jump now, "qi" may - // be invalid. - res = 0; - } - } - // Jump to the first error for new list and if autocmds didn't - // free the list. - if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) - && qflist_valid(wp, save_qfid)) { - // display the first error - qf_jump_first(qi, save_qfid, eap->forceit); - } - decr_quickfix_busy(); + incr_quickfix_busy(); + + int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm, + (eap->cmdidx != CMD_caddbuffer + && eap->cmdidx != CMD_laddbuffer), + eap->line1, eap->line2, qf_title, NULL); + if (qf_stack_empty(qi)) { + decr_quickfix_busy(); + return; + } + if (res >= 0) { + qf_list_changed(qf_get_curlist(qi)); + } + // Remember the current quickfix list identifier, so that we can + // check for autocommands changing the current quickfix list. + unsigned save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) { + const buf_T *const curbuf_old = curbuf; + apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + curbuf->b_fname, true, curbuf); + if (curbuf != curbuf_old) { + // Autocommands changed buffer, don't jump now, "qi" may + // be invalid. + res = 0; } } + // Jump to the first error for new list and if autocmds didn't + // free the list. + if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) + && qflist_valid(wp, save_qfid)) { + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + } + + decr_quickfix_busy(); +} + +// Return the autocmd name for the :cexpr Ex commands. +static const char_u * cexpr_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cexpr: return "cexpr"; + case CMD_cgetexpr: return "cgetexpr"; + case CMD_caddexpr: return "caddexpr"; + case CMD_lexpr: return "lexpr"; + case CMD_lgetexpr: return "lgetexpr"; + case CMD_laddexpr: return "laddexpr"; + default: return NULL; + } } /* @@ -6072,28 +6121,7 @@ void ex_cexpr(exarg_T *eap) const char *au_name = NULL; win_T *wp = NULL; - switch (eap->cmdidx) { - case CMD_cexpr: - au_name = "cexpr"; - break; - case CMD_cgetexpr: - au_name = "cgetexpr"; - break; - case CMD_caddexpr: - au_name = "caddexpr"; - break; - case CMD_lexpr: - au_name = "lexpr"; - break; - case CMD_lgetexpr: - au_name = "lgetexpr"; - break; - case CMD_laddexpr: - au_name = "laddexpr"; - break; - default: - break; - } + au_name = cexpr_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { -- cgit From c222f3e0060f168c7c7ab3e3926efd95d3def39f Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Fri, 27 Sep 2019 22:10:35 -0400 Subject: vim-patch:8.1.1098: quickfix code duplication Problem: Quickfix code duplication. Solution: Refactor the qf_init_ext() function. (Yegappan Lakshmanan, closes vim/vim#4193) https://github.com/vim/vim/commit/95946f1209ad088bfe55c83256c299156c11d8e0 --- src/nvim/quickfix.c | 162 +++++++++++++++++++++++++++------------------------- 1 file changed, 83 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index fc504f5aba..c45d60cb3f 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -232,9 +232,11 @@ typedef struct { // #define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +// Macro to loop through all the items in a quickfix list +// Quickfix item index starts from 1, so i below starts at 1 #define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ - for (i = 0, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ - !got_int && i < qfl->qf_count && qfp != NULL; \ + for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && i <= qfl->qf_count && qfp != NULL; \ i++, qfp = qfp->qf_next) @@ -250,6 +252,45 @@ static char *e_loc_list_changed = N_("E926: Current location list was changed"); static int quickfix_busy = 0; static qf_delq_T *qf_delq_head = NULL; +// Process the next line from a file/buffer/list/string and add it +// to the quickfix list 'qfl'. +static int qf_init_process_nextline(qf_list_T *qfl, + efm_T *fmt_first, + qfstate_T *state, + qffields_T *fields) +{ + int status; + + // Get the next line from a file/buffer/list/string + status = qf_get_nextline(state); + if (status != QF_OK) { + return status; + } + + status = qf_parse_line(qfl, state->linebuf, state->linelen, + fmt_first, fields); + if (status != QF_OK) { + return status; + } + + return qf_add_entry(qfl, + qfl->qf_directory, + (*fields->namebuf || qfl->qf_directory != NULL) + ? fields->namebuf + : ((qfl->qf_currfile != NULL && fields->valid) + ? qfl->qf_currfile : (char_u *)NULL), + fields->module, + 0, + fields->errmsg, + fields->lnum, + fields->col, + fields->use_viscol, + fields->pattern, + fields->enr, + fields->type, + fields->valid); +} + /// Read the errorfile "efile" into memory, line by line, building the error /// list. Set the error list's title to qf_title. /// @@ -1115,38 +1156,14 @@ qf_init_ext( * Try to recognize one of the error formats in each line. */ while (!got_int) { - // Get the next line from a file/buffer/list/string - status = qf_get_nextline(&state); + status = qf_init_process_nextline(qfl, fmt_first, &state, &fields); if (status == QF_END_OF_INPUT) { // end of input break; } - - status = qf_parse_line(qfl, state.linebuf, state.linelen, - fmt_first, &fields); if (status == QF_FAIL) { goto error2; } - if (status == QF_IGNORE_LINE) { - continue; - } - if (qf_add_entry(qfl, - qfl->qf_directory, - (*fields.namebuf || qfl->qf_directory) - ? fields.namebuf : ((qfl->qf_currfile && fields.valid) - ? qfl->qf_currfile : (char_u *)NULL), - fields.module, - 0, - fields.errmsg, - fields.lnum, - fields.col, - fields.use_viscol, - fields.pattern, - fields.enr, - fields.type, - fields.valid) == FAIL) { - goto error2; - } line_breakcheck(); } if (state.fd == NULL || !ferror(state.fd)) { @@ -1785,7 +1802,7 @@ void check_quickfix_busy(void) /// @param type type character /// @param valid valid entry /// -/// @returns OK or FAIL. +/// @returns QF_OK or QF_FAIL. static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, char_u *module, int bufnum, char_u *mesg, long lnum, int col, char_u vis_col, char_u *pattern, int nr, @@ -1848,7 +1865,7 @@ static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, qfl->qf_ptr = qfp; } - return OK; + return QF_OK; } // Allocate a new quickfix/location list stack @@ -1904,7 +1921,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, from_qfp->qf_pattern, from_qfp->qf_nr, 0, - from_qfp->qf_valid) == FAIL) { + from_qfp->qf_valid) == QF_FAIL) { return FAIL; } @@ -2255,7 +2272,7 @@ static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr) } } - if (i == qfl->qf_count) { // Entry is not found + if (i > qfl->qf_count) { // Entry is not found return false; } @@ -3052,21 +3069,10 @@ void qf_list(exarg_T *eap) if (qfl->qf_nonevalid) { all = true; } - qfp = qfl->qf_start; - for (i = 1; !got_int && i <= qfl->qf_count; ) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { - if (got_int) { - break; - } - qf_list_entry(qfp, i, i == qfl->qf_index); } - - qfp = qfp->qf_next; - if (qfp == NULL) { - break; - } - i++; os_breakcheck(); } } @@ -4169,7 +4175,7 @@ size_t qf_get_size(exarg_T *eap) int prev_fnum = 0; size_t sz = 0; qfline_T *qfp; - size_t i; + int i; assert(qf_get_curlist(qi)->qf_count >= 0); qfl = qf_get_curlist(qi); FOR_ALL_QFL_ITEMS(qfl, qfp, i) { @@ -4273,12 +4279,10 @@ static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) int prev_fnum = 0; size_t eidx = 0; - size_t i; + int i; qfline_T *qfp; assert(qfl->qf_count >= 0); - for (i = 1, qfp = qfl->qf_start; - i <= (size_t)qfl->qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (qfp->qf_valid) { if (fdo) { if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) { @@ -4296,7 +4300,7 @@ static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) } } - return i <= (size_t)qfl->qf_count ? i : 1; + return i <= qfl->qf_count ? (size_t)i : 1; } /* @@ -4628,8 +4632,8 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, NULL, // search pattern 0, // nr 0, // type - true // valid - ) == FAIL) { + true) // valid + == QF_FAIL) { got_int = true; break; } @@ -5130,9 +5134,9 @@ static int get_qfline_items(qfline_T *qfp, list_T *list) /// Add each quickfix error to list "list" as a dictionary. /// If qf_idx is -1, use the current list. Otherwise, use the specified list. -int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) +int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { - const qf_info_T *qi = qi_arg; + qf_info_T *qi = qi_arg; qf_list_T *qfl; qfline_T *qfp; int i; @@ -5611,7 +5615,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, } retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list)); - if (retval == FAIL) { + if (retval == QF_FAIL) { break; } }); @@ -5961,15 +5965,15 @@ bool set_ref_in_quickfix(int copyID) } // Return the autocmd name for the :cbuffer Ex commands -static const char_u * cbuffer_get_auname(cmdidx_T cmdidx) +static char_u * cbuffer_get_auname(cmdidx_T cmdidx) { switch (cmdidx) { - case CMD_cbuffer: return "cbuffer"; - case CMD_cgetbuffer: return "cgetbuffer"; - case CMD_caddbuffer: return "caddbuffer"; - case CMD_lbuffer: return "lbuffer"; - case CMD_lgetbuffer: return "lgetbuffer"; - case CMD_laddbuffer: return "laddbuffer"; + case CMD_cbuffer: return (char_u *)"cbuffer"; + case CMD_cgetbuffer: return (char_u *)"cgetbuffer"; + case CMD_caddbuffer: return (char_u *)"caddbuffer"; + case CMD_lbuffer: return (char_u *)"lbuffer"; + case CMD_lgetbuffer: return (char_u *)"lgetbuffer"; + case CMD_laddbuffer: return (char_u *)"laddbuffer"; default: return NULL; } } @@ -5989,12 +5993,12 @@ static int cbuffer_process_args(exarg_T *eap, buf = buflist_findnr(atoi((char *)eap->arg)); if (buf == NULL) { - emsg(_(e_invarg)); + EMSG(_(e_invarg)); return FAIL; } if (buf->b_ml.ml_mfp == NULL) { - emsg(_("E681: Buffer is not loaded")); + EMSG(_("E681: Buffer is not loaded")); return FAIL; } @@ -6005,7 +6009,7 @@ static int cbuffer_process_args(exarg_T *eap, if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) { - emsg(_(e_invrange)); + EMSG(_(e_invrange)); return FAIL; } @@ -6028,14 +6032,14 @@ void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; qf_info_T *qi = &ql_info; - const char *au_name = NULL; + char_u *au_name = NULL; win_T *wp = NULL; char_u *qf_title; linenr_T line1; linenr_T line2; au_name = cbuffer_get_auname(eap->cmdidx); - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { return; @@ -6078,7 +6082,7 @@ void ex_cbuffer(exarg_T *eap) unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { const buf_T *const curbuf_old = curbuf; - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); if (curbuf != curbuf_old) { // Autocommands changed buffer, don't jump now, "qi" may @@ -6098,15 +6102,15 @@ void ex_cbuffer(exarg_T *eap) } // Return the autocmd name for the :cexpr Ex commands. -static const char_u * cexpr_get_auname(cmdidx_T cmdidx) +static char_u * cexpr_get_auname(cmdidx_T cmdidx) { switch (cmdidx) { - case CMD_cexpr: return "cexpr"; - case CMD_cgetexpr: return "cgetexpr"; - case CMD_caddexpr: return "caddexpr"; - case CMD_lexpr: return "lexpr"; - case CMD_lgetexpr: return "lgetexpr"; - case CMD_laddexpr: return "laddexpr"; + case CMD_cexpr: return (char_u *)"cexpr"; + case CMD_cgetexpr: return (char_u *)"cgetexpr"; + case CMD_caddexpr: return (char_u *)"caddexpr"; + case CMD_lexpr: return (char_u *)"lexpr"; + case CMD_lgetexpr: return (char_u *)"lgetexpr"; + case CMD_laddexpr: return (char_u *)"laddexpr"; default: return NULL; } } @@ -6118,11 +6122,11 @@ static const char_u * cexpr_get_auname(cmdidx_T cmdidx) void ex_cexpr(exarg_T *eap) { qf_info_T *qi = &ql_info; - const char *au_name = NULL; + char_u *au_name = NULL; win_T *wp = NULL; au_name = cexpr_get_auname(eap->cmdidx); - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { return; @@ -6157,7 +6161,7 @@ void ex_cexpr(exarg_T *eap) // check for autocommands changing the current quickfix list. unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); } // Jump to the first error for a new list and if autocmds didn't @@ -6242,8 +6246,8 @@ static void hgr_search_file( NULL, // search pattern 0, // nr 1, // type - true // valid - ) == FAIL) { + true) // valid + == QF_FAIL) { got_int = true; if (line != IObuff) { xfree(line); -- cgit From cf7c34dea1080db403464f7b7fd50341597fd42c Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Tue, 17 Sep 2019 17:01:52 -0400 Subject: vim-patch:8.1.1112: duplicate code in quickfix file Problem: Duplicate code in quickfix file. Solution: Move code into functions. (Yegappan Lakshmanan, closes vim/vim#4207) https://github.com/vim/vim/commit/87f59b09ea4b9af2712598374a6044f5fa1b54a4 --- src/nvim/quickfix.c | 182 ++++++++++++++++++------------------- src/nvim/testdir/test_quickfix.vim | 6 ++ 2 files changed, 96 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c45d60cb3f..731817a2b4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1899,9 +1899,49 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) return wp->w_llist; } +// Get the quickfix/location list stack to use for the specified Ex command. +// For a location list command, returns the stack for the current window. If +// the location list is not found, then returns NULL and prints an error +// message if 'print_emsg' is TRUE. +static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg) +{ + qf_info_T *qi = &ql_info; + + if (is_loclist_cmd(eap->cmdidx)) { + qi = GET_LOC_LIST(curwin); + if (qi == NULL) { + if (print_emsg) { + EMSG(_(e_loclist)); + } + return NULL; + } + } + + return qi; +} + +// Get the quickfix/location list stack to use for the specified Ex command. +// For a location list command, returns the stack for the current window. +// If the location list is not present, then allocates a new one. +// Returns NULL if the allocation fails. For a location list command, sets +// 'pwinp' to curwin. +static qf_info_T * qf_cmd_get_or_alloc_stack(exarg_T *eap, win_T **pwinp) +{ + qf_info_T *qi = &ql_info; + + if (is_loclist_cmd(eap->cmdidx)) { + qi = ll_get_or_alloc_list(curwin); + if (qi == NULL) { + return NULL; + } + *pwinp = curwin; + } + + return qi; +} + // Copy location list entries from 'from_qfl' to 'to_qfl'. -static int copy_loclist_entries(const qf_list_T *from_qfl, - qf_list_T *to_qfl) +static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { int i; @@ -3009,14 +3049,10 @@ void qf_list(exarg_T *eap) char_u *arg = eap->arg; int all = eap->forceit; // if not :cl!, only show // recognised errors - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { @@ -3135,15 +3171,11 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) */ void qf_age(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; int count; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } if (eap->addr_count != 0) { @@ -3174,12 +3206,9 @@ void qf_age(exarg_T *eap) /// Display the information about all the quickfix/location lists in the stack. void qf_history(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = qf_cmd_get_stack(eap, false); int i; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { MSG(_("No entries")); } else { @@ -3374,14 +3403,12 @@ void qf_view_result(bool split) */ void ex_cwindow(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; qf_list_T *qfl; win_T *win; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) - return; + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } qfl = qf_get_curlist(qi); @@ -3411,13 +3438,11 @@ void ex_cwindow(exarg_T *eap) */ void ex_cclose(exarg_T *eap) { - win_T *win = NULL; - qf_info_T *qi = &ql_info; + win_T *win = NULL; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) - return; + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return; } /* Find existing quickfix window and close it. */ @@ -3533,18 +3558,14 @@ static void qf_set_title_var(qf_list_T *qfl) */ void ex_copen(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; qf_list_T *qfl; int height; int status = FAIL; int lnum; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } incr_quickfix_busy(); @@ -3607,14 +3628,10 @@ static void qf_win_goto(win_T *win, linenr_T lnum) // :cbottom/:lbottom command. void ex_cbottom(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } win_T *win = qf_find_win(qi); @@ -4162,14 +4179,11 @@ static char_u *get_mef_name(void) size_t qf_get_size(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; qf_list_T *qfl; - if (is_loclist_cmd(eap->cmdidx)) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 0; - } + + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 0; } int prev_fnum = 0; @@ -4201,14 +4215,10 @@ size_t qf_get_size(exarg_T *eap) size_t qf_get_cur_idx(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 0; - } + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 0; } assert(qf_get_curlist(qi)->qf_index >= 0); @@ -4221,14 +4231,10 @@ size_t qf_get_cur_idx(exarg_T *eap) int qf_get_cur_valid_idx(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 1; - } + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 1; } qf_list_T *qfl = qf_get_curlist(qi); @@ -4310,14 +4316,10 @@ static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) */ void ex_cc(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } int errornr; @@ -4369,14 +4371,10 @@ void ex_cc(exarg_T *eap) */ void ex_cnext(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (is_loclist_cmd(eap->cmdidx)) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } int errornr; @@ -4695,7 +4693,7 @@ void ex_vimgrep(exarg_T *eap) char_u *s; char_u *p; int fi; - qf_info_T *qi = &ql_info; + qf_info_T *qi; qf_list_T *qfl; win_T *wp = NULL; buf_T *buf; @@ -4721,9 +4719,9 @@ void ex_vimgrep(exarg_T *eap) } } - if (is_loclist_cmd(eap->cmdidx)) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) { + return; } if (eap->addr_count > 0) @@ -6031,7 +6029,7 @@ static int cbuffer_process_args(exarg_T *eap, void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; - qf_info_T *qi = &ql_info; + qf_info_T *qi; char_u *au_name = NULL; win_T *wp = NULL; char_u *qf_title; @@ -6047,9 +6045,9 @@ void ex_cbuffer(exarg_T *eap) } // Must come after autocommands. - if (is_loclist_cmd(eap->cmdidx)) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) { + return; } if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) { @@ -6121,7 +6119,7 @@ static char_u * cexpr_get_auname(cmdidx_T cmdidx) */ void ex_cexpr(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; char_u *au_name = NULL; win_T *wp = NULL; @@ -6133,9 +6131,9 @@ void ex_cexpr(exarg_T *eap) } } - if (is_loclist_cmd(eap->cmdidx)) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qi = qf_cmd_get_or_alloc_stack(eap, &wp); + if (qi == NULL) { + return; } /* Evaluate the expression. When the result is a string or a list we can diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 26b43d9818..131e9d72e3 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -163,6 +163,12 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + " No location list for the current window + call assert_fails('lolder', 'E776:') + call assert_fails('lnewer', 'E776:') + endif + let list = [{'bufnr': bufnr('%'), 'lnum': 1}] call g:Xsetlist(list) -- cgit From a4d48d37c15604a96be13eb4deaedbb132be6e10 Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Fri, 13 Sep 2019 22:16:12 -0400 Subject: vim-patch:8.1.1256: cannot navigate through errors relative to the cursor Problem: Cannot navigate through errors relative to the cursor. Solution: Add :cabove, :cbelow, :labove and :lbelow. (Yegappan Lakshmanan, closes vim/vim#4316) https://github.com/vim/vim/commit/3ff33114d70fc0f7e9c3187c5fec9028f6499cf3 --- src/nvim/ex_cmds.lua | 24 +++ src/nvim/quickfix.c | 293 ++++++++++++++++++++++++++++++++++++- src/nvim/testdir/test_quickfix.vim | 110 ++++++++++++++ 3 files changed, 423 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 6317ec77ff..f7aa8a994a 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -322,6 +322,12 @@ return { addr_type=ADDR_LINES, func='ex_abclear', }, + { + command='cabove', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, { command='caddbuffer', flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR), @@ -358,6 +364,12 @@ return { addr_type=ADDR_LINES, func='ex_cbuffer', }, + { + command='cbelow', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, { command='cbottom', flags=bit.bor(TRLBAR), @@ -1272,6 +1284,12 @@ return { addr_type=ADDR_LINES, func='ex_last', }, + { + command='labove', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, { command='language', flags=bit.bor(EXTRA, TRLBAR, CMDWIN), @@ -1308,6 +1326,12 @@ return { addr_type=ADDR_LINES, func='ex_cbuffer', }, + { + command='lbelow', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, { command='lbottom', flags=bit.bor(TRLBAR), diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 731817a2b4..d6cc9d83fe 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -215,7 +215,10 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.c.generated.h" #endif -/* Quickfix window check helper macro */ + +static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); + +// Quickfix window check helper macro #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) /* Location list window check helper macro */ #define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) @@ -898,6 +901,13 @@ static bool qf_list_empty(qf_list_T *qfl) return qfl == NULL || qfl->qf_count <= 0; } +// Returns TRUE if the specified quickfix/location list is not empty and +// has valid entries. +static int qf_list_has_valid_entries(qf_list_T *qfl) +{ + return !qf_list_empty(qfl) && !qfl->qf_nonevalid; +} + // Return a pointer to a list in the specified quickfix stack static qf_list_T * qf_get_list(qf_info_T *qi, int idx) { @@ -2370,7 +2380,6 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr, { qfline_T *prev_qf_ptr; int prev_index; - static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); char_u *err = e_no_more_items; while (errornr--) { @@ -4240,7 +4249,7 @@ int qf_get_cur_valid_idx(exarg_T *eap) qf_list_T *qfl = qf_get_curlist(qi); // Check if the list has valid errors. - if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { + if (!qf_list_has_valid_entries(qfl)) { return 1; } @@ -4279,7 +4288,7 @@ static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) FUNC_ATTR_NONNULL_ALL { // Check if the list has valid errors. - if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { + if (!qf_list_has_valid_entries(qfl)) { return 1; } @@ -4419,6 +4428,282 @@ void ex_cnext(exarg_T *eap) qf_jump(qi, dir, errornr, eap->forceit); } +// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'. +// The index of the entry is stored in 'errornr'. +// Returns NULL if an entry is not found. +static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl, + int bnr, + int *errornr) +{ + qfline_T *qfp = NULL; + int idx = 0; + + // Find the first entry in this file + FOR_ALL_QFL_ITEMS(qfl, qfp, idx) { + if (qfp->qf_fnum == bnr) { + break; + } + } + + *errornr = idx; + return qfp; +} + +// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr' +// with the error number for the first entry. Assumes the entries are sorted in +// the quickfix list by line number. +static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr) +{ + while (!got_int + && entry->qf_prev != NULL + && entry->qf_fnum == entry->qf_prev->qf_fnum + && entry->qf_lnum == entry->qf_prev->qf_lnum) { + entry = entry->qf_prev; + (*errornr)--; + } + + return entry; +} + +// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr' +// with the error number for the last entry. Assumes the entries are sorted in +// the quickfix list by line number. +static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr) +{ + while (!got_int + && entry->qf_next != NULL + && entry->qf_fnum == entry->qf_next->qf_fnum + && entry->qf_lnum == entry->qf_next->qf_lnum) { + entry = entry->qf_next; + (*errornr)++; + } + + return entry; +} + +// Find the first quickfix entry below line 'lnum' in buffer 'bnr'. +// 'qfp' points to the very first entry in the buffer and 'errornr' is the +// index of the very first entry in the quickfix list. +// Returns NULL if an entry is not found after 'lnum'. +static qfline_T *qf_find_entry_on_next_line(int bnr, + linenr_T lnum, + qfline_T *qfp, + int *errornr) +{ + if (qfp->qf_lnum > lnum) { + // First entry is after line 'lnum' + return qfp; + } + + // Find the entry just before or at the line 'lnum' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qfp->qf_next->qf_lnum <= lnum) { + qfp = qfp->qf_next; + (*errornr)++; + } + + if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr) { + // No entries found after 'lnum' + return NULL; + } + + // Use the entry just after line 'lnum' + qfp = qfp->qf_next; + (*errornr)++; + + return qfp; +} + +// Find the first quickfix entry before line 'lnum' in buffer 'bnr'. +// 'qfp' points to the very first entry in the buffer and 'errornr' is the +// index of the very first entry in the quickfix list. +// Returns NULL if an entry is not found before 'lnum'. +static qfline_T *qf_find_entry_on_prev_line(int bnr, + linenr_T lnum, + qfline_T *qfp, + int *errornr) +{ + // Find the entry just before the line 'lnum' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qfp->qf_next->qf_lnum < lnum) { + qfp = qfp->qf_next; + (*errornr)++; + } + + if (qfp->qf_lnum >= lnum) { // entry is after 'lnum' + return NULL; + } + + // If multiple entries are on the same line, then use the first entry + qfp = qf_find_first_entry_on_line(qfp, errornr); + + return qfp; +} + +// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in +// the direction 'dir'. +static qfline_T *qf_find_closest_entry(qf_list_T *qfl, + int bnr, + linenr_T lnum, + int dir, + int *errornr) +{ + qfline_T *qfp; + + *errornr = 0; + + // Find the first entry in this file + qfp = qf_find_first_entry_in_buf(qfl, bnr, errornr); + if (qfp == NULL) { + return NULL; // no entry in this file + } + + if (dir == FORWARD) { + qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr); + } else { + qfp = qf_find_entry_on_prev_line(bnr, lnum, qfp, errornr); + } + + return qfp; +} + +// Get the nth quickfix entry below the specified entry treating multiple +// entries on a single line as one. Searches forward in the list. +static qfline_T *qf_get_nth_below_entry(qfline_T *entry, + int *errornr, + linenr_T n) +{ + while (n-- > 0 && !got_int) { + qfline_T *first_entry = entry; + int first_errornr = *errornr; + + // Treat all the entries on the same line in this file as one + entry = qf_find_last_entry_on_line(entry, errornr); + + if (entry->qf_next == NULL + || entry->qf_next->qf_fnum != entry->qf_fnum) { + // If multiple entries are on the same line, then use the first + // entry + entry = first_entry; + *errornr = first_errornr; + break; + } + + entry = entry->qf_next; + (*errornr)++; + } + + return entry; +} + +// Get the nth quickfix entry above the specified entry treating multiple +// entries on a single line as one. Searches backwards in the list. +static qfline_T *qf_get_nth_above_entry(qfline_T *entry, + int *errornr, + linenr_T n) +{ + while (n-- > 0 && !got_int) { + if (entry->qf_prev == NULL + || entry->qf_prev->qf_fnum != entry->qf_fnum) { + break; + } + + entry = entry->qf_prev; + (*errornr)--; + + // If multiple entries are on the same line, then use the first entry + entry = qf_find_first_entry_on_line(entry, errornr); + } + + return entry; +} + +// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the +// specified direction. +// Returns the error number in the quickfix list or 0 if an entry is not found. +static int qf_find_nth_adj_entry(qf_list_T *qfl, + int bnr, + linenr_T lnum, + linenr_T n, + int dir) +{ + qfline_T *adj_entry; + int errornr; + + // Find an entry closest to the specified line + adj_entry = qf_find_closest_entry(qfl, bnr, lnum, dir, &errornr); + if (adj_entry == NULL) { + return 0; + } + + if (--n > 0) { + // Go to the n'th entry in the current buffer + if (dir == FORWARD) { + adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n); + } else { + adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n); + } + } + + return errornr; +} + +// Jump to a quickfix entry in the current file nearest to the current line. +// ":cabove", ":cbelow", ":labove" and ":lbelow" commands +void ex_cbelow(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + int dir; + int buf_has_flag; + int errornr = 0; + + if (eap->addr_count > 0 && eap->line2 <= 0) { + EMSG(_(e_invrange)); + return; + } + + // Check whether the current buffer has any quickfix entries + if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow) { + buf_has_flag = BUF_HAS_QF_ENTRY; + } else { + buf_has_flag = BUF_HAS_LL_ENTRY; + } + if (!(curbuf->b_has_qf_entry & buf_has_flag)) { + EMSG(_(e_quickfix)); + return; + } + + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; + } + + qfl = qf_get_curlist(qi); + // check if the list has valid errors + if (!qf_list_has_valid_entries(qfl)) { + EMSG(_(e_quickfix)); + return; + } + + if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow) { + dir = FORWARD; + } else { + dir = BACKWARD; + } + + errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, curwin->w_cursor.lnum, + eap->addr_count > 0 ? eap->line2 : 0, dir); + + if (errornr > 0) { + qf_jump(qi, 0, errornr, false); + } else { + EMSG(_(e_no_more_items)); + } +} + + // Return the autocmd name for the :cfile Ex commands static char_u * cfile_get_auname(cmdidx_T cmdidx) { diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 131e9d72e3..77fba0b9af 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -37,6 +37,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgrepadd grepadd command! -nargs=* Xhelpgrep helpgrep command! -nargs=0 -count Xcc cc + command! -count=1 -nargs=0 Xbelow cbelow + command! -count=1 -nargs=0 Xabove cabove let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') call setqflist([], 'f') @@ -70,6 +72,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgrepadd lgrepadd command! -nargs=* Xhelpgrep lhelpgrep command! -nargs=0 -count Xcc ll + command! -count=1 -nargs=0 Xbelow lbelow + command! -count=1 -nargs=0 Xabove labove let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) call setloclist(0, [], 'f') @@ -3817,4 +3821,110 @@ func Test_viscol() call delete('Xfile1') endfunc +" Test for the :cbelow, :cabove, :lbelow and :labove commands. +func Xtest_below(cchar) + call s:setup_commands(a:cchar) + + " No quickfix/location list + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + + " Empty quickfix/location list + call g:Xsetlist([]) + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + + call s:create_test_file('X1') + call s:create_test_file('X2') + call s:create_test_file('X3') + call s:create_test_file('X4') + + " Invalid entries + edit X1 + call g:Xsetlist(["E1", "E2"]) + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + call assert_fails('3Xbelow', 'E42:') + call assert_fails('4Xabove', 'E42:') + + " Test the commands with various arguments + Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"] + edit +7 X2 + Xabove + call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_fails('Xabove', 'E553:') + normal 2j + Xbelow + call assert_equal(['X2', 10], [bufname(''), line('.')]) + " Last error in this file + Xbelow 99 + call assert_equal(['X2', 15], [bufname(''), line('.')]) + call assert_fails('Xbelow', 'E553:') + " First error in this file + Xabove 99 + call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_fails('Xabove', 'E553:') + normal gg + Xbelow 2 + call assert_equal(['X2', 10], [bufname(''), line('.')]) + normal G + Xabove 2 + call assert_equal(['X2', 10], [bufname(''), line('.')]) + edit X4 + call assert_fails('Xabove', 'E42:') + call assert_fails('Xbelow', 'E42:') + if a:cchar == 'l' + " If a buffer has location list entries from some other window but not + " from the current window, then the commands should fail. + edit X1 | split | call setloclist(0, [], 'f') + call assert_fails('Xabove', 'E776:') + call assert_fails('Xbelow', 'E776:') + close + endif + + " Test for lines with multiple quickfix entries + Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3", + \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3", + \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"] + edit +1 X2 + Xbelow 2 + call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + normal gg + Xbelow 99 + call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + normal G + Xabove 2 + call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + normal G + Xabove 99 + call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + normal 10G + Xabove + call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + normal 10G + Xbelow + call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + + " Invalid range + if a:cchar == 'c' + call assert_fails('-2cbelow', 'E553:') + " TODO: should go to first error in the current line? + 0cabove + else + call assert_fails('-2lbelow', 'E553:') + " TODO: should go to first error in the current line? + 0labove + endif + + call delete('X1') + call delete('X2') + call delete('X3') + call delete('X4') +endfunc + +func Test_cbelow() + call Xtest_below('c') + call Xtest_below('l') +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From efaf4732e26e2f0fbfab947296141376223b30d7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 27 Oct 2019 15:26:32 -0700 Subject: lua/executor.c: use TRY_WRAP --- src/nvim/api/private/helpers.h | 14 ++++++++++++++ src/nvim/api/vim.c | 14 -------------- src/nvim/lua/executor.c | 10 +++------- 3 files changed, 17 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 0ea7667428..8930f252f6 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -102,6 +102,20 @@ typedef struct { int did_emsg; } TryState; +// `msg_list` controls the collection of abort-causing non-exception errors, +// which would otherwise be ignored. This pattern is from do_cmdline(). +// +// TODO(bfredl): prepare error-handling at "top level" (nv_event). +#define TRY_WRAP(code) \ + do { \ + struct msglist **saved_msg_list = msg_list; \ + struct msglist *private_msg_list; \ + msg_list = &private_msg_list; \ + private_msg_list = NULL; \ + code \ + msg_list = saved_msg_list; /* Restore the exception context. */ \ + } while (0) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" #endif diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 602733fd31..59761c13e7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -53,20 +53,6 @@ # include "api/vim.c.generated.h" #endif -// `msg_list` controls the collection of abort-causing non-exception errors, -// which would otherwise be ignored. This pattern is from do_cmdline(). -// -// TODO(bfredl): prepare error-handling at "top level" (nv_event). -#define TRY_WRAP(code) \ - do { \ - struct msglist **saved_msg_list = msg_list; \ - struct msglist *private_msg_list; \ - msg_list = &private_msg_list; \ - private_msg_list = NULL; \ - code \ - msg_list = saved_msg_list; /* Restore the exception context. */ \ - } while (0) - void api_vim_init(void) FUNC_API_NOEXPORT { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index b7c1d57964..c7ff163f83 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -563,11 +563,8 @@ int nlua_call(lua_State *lstate) } } + TRY_WRAP({ // TODO(bfredl): this should be simplified in error handling refactor - struct msglist **saved_msg_list = msg_list; - struct msglist *private_msg_list = NULL; - msg_list = &private_msg_list; - force_abort = false; suppress_errthrow = false; current_exception = NULL; @@ -577,7 +574,7 @@ int nlua_call(lua_State *lstate) typval_T rettv; int dummy; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (see above) to capture abort-causing non-exception errors. + // (TRY_WRAP) to capture abort-causing non-exception errors. (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, NULL, NULL); @@ -585,8 +582,7 @@ int nlua_call(lua_State *lstate) nlua_push_typval(lstate, &rettv, false); } tv_clear(&rettv); - - msg_list = saved_msg_list; + }); free_vim_args: while (i > 0) { -- cgit From 3b3d21775478cc97acf391a30d844eb3a81e96f6 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 8 Mar 2019 22:49:03 +0000 Subject: Factor out skip_colon_white() --- src/nvim/ex_docmd.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index da0184aa45..22c4fd264d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1201,6 +1201,19 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) } } +static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) +{ + if (skipleadingwhite) { + p = skipwhite(p); + } + + while (*p == ':') { + p = skipwhite(p + 1); + } + + return (char_u *)p; +} + /* * Execute one Ex command. * @@ -1705,9 +1718,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * Skip ':' and any white space */ - ea.cmd = skipwhite(ea.cmd); - while (*ea.cmd == ':') - ea.cmd = skipwhite(ea.cmd + 1); + ea.cmd = skip_colon_white(ea.cmd, true); /* * If we got a line, but no command, then go to the line. @@ -3580,9 +3591,8 @@ char_u *skip_range( ++cmd; } - /* Skip ":" and white space. */ - while (*cmd == ':') - cmd = skipwhite(cmd + 1); + // Skip ":" and white space. + cmd = skip_colon_white(cmd, false); return (char_u *)cmd; } -- cgit From 1c7aa1131297b4b3686d66aae06090e1398da56c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Fri, 8 Mar 2019 22:49:48 +0000 Subject: Allow multiple leading colons before and after modifiers for 'inccommand' --- src/nvim/ex_docmd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 22c4fd264d..48d29caf60 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -10216,10 +10216,13 @@ bool cmd_can_preview(char_u *cmd) return false; } + // Ignore additional colons at the start... + cmd = skip_colon_white(cmd, true); + // Ignore any leading modifiers (:keeppatterns, :verbose, etc.) for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) { cmd += len; - cmd = skipwhite(cmd); + cmd = skip_colon_white(cmd, true); } exarg_T ea; -- cgit From 1f6ce1f97d60f9fd1cc75dab4b604a72d390bf1e Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 29 Oct 2019 22:31:06 +0000 Subject: Only apply 'icm' substitutions when preview was successfully opened --- src/nvim/ex_cmds.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2e8bd79c81..c1ef08593d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5563,7 +5563,12 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, linenr_T highest_num_line = 0; int col_width = 0; + // if we fail to split the window, we don't want to modify orig_buf + bool split_success = false; + if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) { + split_success = true; + buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]"); buf_clear(); preview_buf = curbuf; @@ -5593,7 +5598,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) { SubResult match = lines.subresults.items[matchidx]; - if (split && preview_buf) { + if (split_success && preview_buf) { lpos_T p_start = { 0, match.start.col }; // match starts here in preview lpos_T p_end = { 0, match.end.col }; // ... and ends here -- cgit From d52d7823898c7fee0121c4d6da730a0530bf8c50 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 29 Oct 2019 22:31:40 +0000 Subject: Prevent :topleft, etc modifying the inccommand preview window --- src/nvim/ex_cmds.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index c1ef08593d..a71b249a92 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5535,6 +5535,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, // We keep a special-purpose buffer around, but don't assume it exists. buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0; + // disable :leftabove/botright modifiers + // (especially ones that conflict with our win_split() call below) + cmdmod.split = 0; cmdmod.tab = 0; // disable :tab modifier cmdmod.noswapfile = true; // disable swap for preview buffer // disable file info message -- cgit From d04ab11f24521e60278a0daed9a7d5abeeaf6f4f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 29 Oct 2019 22:32:25 +0000 Subject: Prevent prompts during inccommand previews For example, "Backwards range given, OK to swap (y/n)?" on each keypress. --- src/nvim/ex_getln.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index f2665ca0b5..9e2671ca5e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1927,7 +1927,9 @@ static int command_line_changed(CommandLineState *s) // - Immediately undo the effects. State |= CMDPREVIEW; emsg_silent++; // Block error reporting as the command may be incomplete + msg_silent++; // Block messages, namely ones that prompt do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT); + msg_silent--; // Unblock messages emsg_silent--; // Unblock error reporting // Restore the window "view". -- cgit From 36b4191e41788192c7fc5cef2f7b11d3bcd13880 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 31 Oct 2019 23:20:33 +0000 Subject: Document skip_colon_white() --- src/nvim/ex_docmd.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 48d29caf60..d3e2120721 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1201,6 +1201,10 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) } } +/// Skip colons and trailing whitespace, returning a pointer to the first +/// non-colon, non-whitespace character. +// +/// @param skipleadingwhite Skip leading whitespace too static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) { if (skipleadingwhite) { -- cgit From 98d7c7db1269bdbc210beb0b3d08259f255252fb Mon Sep 17 00:00:00 2001 From: Shane Smith Date: Thu, 31 Oct 2019 22:49:52 -0400 Subject: Minor updates and comment format fixes - cursel argument for qf_list_entry() changed from int to bool - Return type of qf_list_has_valid_entries() changed from int to bool - Unnecessary size_t casting in qf_new_list removed - Use Doxygen-style comment blocks for functions --- src/nvim/quickfix.c | 370 +++++++++++++++++++++++++--------------------------- 1 file changed, 179 insertions(+), 191 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index d6cc9d83fe..aa934bab43 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -80,12 +80,13 @@ struct qfline_S { #define LISTCOUNT 10 #define INVALID_QFIDX (-1) -// Quickfix list type. +/// Quickfix list type. typedef enum { - QFLT_QUICKFIX, // Quickfix list - global list - QFLT_LOCATION, // Location list - per window list - QFLT_INTERNAL // Internal - Temporary list used by getqflist()/getloclist() + QFLT_QUICKFIX, ///< Quickfix list - global list + QFLT_LOCATION, ///< Location list - per window list + QFLT_INTERNAL ///< Internal - Temporary list used by + // getqflist()/getloclist() } qfltype_T; /// Quickfix/Location list definition @@ -164,8 +165,8 @@ struct efm_S { int conthere; /* %> used */ }; -// List of location lists to be deleted. -// Used to delay the deletion of locations lists by autocmds. +/// List of location lists to be deleted. +/// Used to delay the deletion of locations lists by autocmds. typedef struct qf_delq_S { struct qf_delq_S *next; qf_info_T *qi; @@ -180,8 +181,8 @@ enum { QF_MULTISCAN = 5, }; -// State information used to parse lines and add entries to a quickfix/location -// list. +/// State information used to parse lines and add entries to a quickfix/location +/// list. typedef struct { char_u *linebuf; size_t linelen; @@ -255,8 +256,8 @@ static char *e_loc_list_changed = N_("E926: Current location list was changed"); static int quickfix_busy = 0; static qf_delq_T *qf_delq_head = NULL; -// Process the next line from a file/buffer/list/string and add it -// to the quickfix list 'qfl'. +/// Process the next line from a file/buffer/list/string and add it +/// to the quickfix list 'qfl'. static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T *state, @@ -341,10 +342,10 @@ static struct fmtpattern { 'o', ".\\+" } }; -// Convert an errorformat pattern to a regular expression pattern. -// See fmt_pat definition above for the list of supported patterns. The -// pattern specifier is supplied in "efmpat". The converted pattern is stored -// in "regpat". Returns a pointer to the location after the pattern. +/// Convert an errorformat pattern to a regular expression pattern. +/// See fmt_pat definition above for the list of supported patterns. The +/// pattern specifier is supplied in "efmpat". The converted pattern is stored +/// in "regpat". Returns a pointer to the location after the pattern. static char_u * efmpat_to_regpat( const char_u *efmpat, char_u *regpat, @@ -410,8 +411,8 @@ static char_u * efmpat_to_regpat( return regpat; } -// Convert a scanf like format in 'errorformat' to a regular expression. -// Returns a pointer to the location after the pattern. +/// Convert a scanf like format in 'errorformat' to a regular expression. +/// Returns a pointer to the location after the pattern. static char_u * scanf_fmt_to_regpat( const char_u **pefmp, const char_u *efm, @@ -455,7 +456,7 @@ static char_u * scanf_fmt_to_regpat( return regpat; } -// Analyze/parse an errorformat prefix. +/// Analyze/parse an errorformat prefix. static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL @@ -556,8 +557,8 @@ static void free_efm_list(efm_T **efm_first) fmt_start = NULL; } -// Compute the size of the buffer used to convert a 'errorformat' pattern into -// a regular expression pattern. +/// Compute the size of the buffer used to convert a 'errorformat' pattern into +/// a regular expression pattern. static size_t efm_regpat_bufsz(char_u *efm) { size_t sz; @@ -575,7 +576,7 @@ static size_t efm_regpat_bufsz(char_u *efm) return sz; } -// Return the length of a 'errorformat' option part (separated by ","). +/// Return the length of a 'errorformat' option part (separated by ","). static int efm_option_part_len(char_u *efm) { int len; @@ -589,9 +590,9 @@ static int efm_option_part_len(char_u *efm) return len; } -// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option -// are parsed and converted to regular expressions. Returns information about -// the parsed 'errorformat' option. +/// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option +/// are parsed and converted to regular expressions. Returns information about +/// the parsed 'errorformat' option. static efm_T * parse_efm_option(char_u *efm) { efm_T *fmt_ptr = NULL; @@ -645,7 +646,7 @@ parse_efm_end: return fmt_first; } -// Allocate more memory for the line buffer used for parsing lines. +/// Allocate more memory for the line buffer used for parsing lines. static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz) { // If the line exceeds LINE_MAXLEN exclude the last @@ -887,28 +888,28 @@ static int qf_get_nextline(qfstate_T *state) return QF_OK; } -// Returns true if the specified quickfix/location stack is empty +/// Returns true if the specified quickfix/location stack is empty static bool qf_stack_empty(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return qi == NULL || qi->qf_listcount <= 0; } -// Returns true if the specified quickfix/location list is empty. +/// Returns true if the specified quickfix/location list is empty. static bool qf_list_empty(qf_list_T *qfl) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return qfl == NULL || qfl->qf_count <= 0; } -// Returns TRUE if the specified quickfix/location list is not empty and -// has valid entries. -static int qf_list_has_valid_entries(qf_list_T *qfl) +/// Returns true if the specified quickfix/location list is not empty and +/// has valid entries. +static bool qf_list_has_valid_entries(qf_list_T *qfl) { return !qf_list_empty(qfl) && !qfl->qf_nonevalid; } -// Return a pointer to a list in the specified quickfix stack +/// Return a pointer to a list in the specified quickfix stack static qf_list_T * qf_get_list(qf_info_T *qi, int idx) { return &qi->qf_lists[idx]; @@ -1241,15 +1242,15 @@ static char_u * qf_cmdtitle(char_u *cmd) return qftitle_str; } -// Return a pointer to the current list in the specified quickfix stack +/// Return a pointer to the current list in the specified quickfix stack static qf_list_T * qf_get_curlist(qf_info_T *qi) { return qf_get_list(qi, qi->qf_curlist); } -// Prepare for adding a new quickfix list. If the current list is in the -// middle of the stack, then all the following lists are freed and then -// the new list is added. +/// Prepare for adding a new quickfix list. If the current list is in the +/// middle of the stack, then all the following lists are freed and then +/// the new list is added. static void qf_new_list(qf_info_T *qi, const char_u *qf_title) { int i; @@ -1275,14 +1276,14 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) } else qi->qf_curlist = qi->qf_listcount++; qfl = qf_get_curlist(qi); - memset(qfl, 0, (size_t)(sizeof(qf_list_T))); + memset(qfl, 0, sizeof(qf_list_T)); qf_store_title(qfl, qf_title); qfl->qfl_type = qi->qfl_type; qfl->qf_id = ++last_qf_id; } -// Parse the match for filename ('%f') pattern in regmatch. -// Return the matched value in "fields->namebuf". +/// Parse the match for filename ('%f') pattern in regmatch. +/// Return the matched value in "fields->namebuf". static int qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, @@ -1310,8 +1311,8 @@ static int qf_parse_fmt_f(regmatch_T *rmp, return QF_OK; } -// Parse the match for error number ('%n') pattern in regmatch. -// Return the matched value in "fields->enr". +/// Parse the match for error number ('%n') pattern in regmatch. +/// Return the matched value in "fields->enr". static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) { if (rmp->startp[midx] == NULL) { @@ -1321,8 +1322,8 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// Parse the match for line number (%l') pattern in regmatch. -// Return the matched value in "fields->lnum". +/// Parse the match for line number (%l') pattern in regmatch. +/// Return the matched value in "fields->lnum". static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) { if (rmp->startp[midx] == NULL) { @@ -1332,8 +1333,8 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// Parse the match for column number ('%c') pattern in regmatch. -// Return the matched value in "fields->col". +/// Parse the match for column number ('%c') pattern in regmatch. +/// Return the matched value in "fields->col". static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) { if (rmp->startp[midx] == NULL) { @@ -1343,8 +1344,8 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// Parse the match for error type ('%t') pattern in regmatch. -// Return the matched value in "fields->type". +/// Parse the match for error type ('%t') pattern in regmatch. +/// Return the matched value in "fields->type". static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) { if (rmp->startp[midx] == NULL) { @@ -1354,8 +1355,8 @@ static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// Parse the match for '%+' format pattern. The whole matching line is included -// in the error string. Return the matched line in "fields->errmsg". +/// Parse the match for '%+' format pattern. The whole matching line is included +/// in the error string. Return the matched line in "fields->errmsg". static int qf_parse_fmt_plus(char_u *linebuf, size_t linelen, qffields_T *fields) @@ -1369,8 +1370,8 @@ static int qf_parse_fmt_plus(char_u *linebuf, return QF_OK; } -// Parse the match for error message ('%m') pattern in regmatch. -// Return the matched value in "fields->errmsg". +/// Parse the match for error message ('%m') pattern in regmatch. +/// Return the matched value in "fields->errmsg". static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) { size_t len; @@ -1388,8 +1389,8 @@ static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// Parse the match for rest of a single-line file message ('%r') pattern. -// Return the matched value in "tail". +/// Parse the match for rest of a single-line file message ('%r') pattern. +/// Return the matched value in "tail". static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail) { if (rmp->startp[midx] == NULL) { @@ -1399,9 +1400,8 @@ static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail) return QF_OK; } - -// Parse the match for the pointer line ('%p') pattern in regmatch. -// Return the matched value in "fields->col". +/// Parse the match for the pointer line ('%p') pattern in regmatch. +/// Return the matched value in "fields->col". static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) { char_u *match_ptr; @@ -1423,9 +1423,8 @@ static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } - -// Parse the match for the virtual column number ('%v') pattern in regmatch. -// Return the matched value in "fields->col". +/// Parse the match for the virtual column number ('%v') pattern in regmatch. +/// Return the matched value in "fields->col". static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) { if (rmp->startp[midx] == NULL) { @@ -1436,9 +1435,8 @@ static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } - -// Parse the match for the search text ('%s') pattern in regmatch. -// Return the matched value in "fields->pattern". +/// Parse the match for the search text ('%s') pattern in regmatch. +/// Return the matched value in "fields->pattern". static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) { size_t len; @@ -1458,8 +1456,8 @@ static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// Parse the match for the module ('%o') pattern in regmatch. -// Return the matched value in "fields->module". +/// Parse the match for the module ('%o') pattern in regmatch. +/// Return the matched value in "fields->module". static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) { size_t len; @@ -1477,9 +1475,9 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) return QF_OK; } -// 'errorformat' format pattern parser functions. -// The '%f' and '%r' formats are parsed differently from other formats. -// See qf_parse_match() for details. +/// 'errorformat' format pattern parser functions. +/// The '%f' and '%r' formats are parsed differently from other formats. +/// See qf_parse_match() for details. static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = { NULL, qf_parse_fmt_n, @@ -1696,7 +1694,7 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) return QF_IGNORE_LINE; } -// Queue location list stack delete request. +/// Queue location list stack delete request. static void locstack_queue_delreq(qf_info_T *qi) { qf_delq_T *q; @@ -1752,16 +1750,16 @@ void qf_free_all(win_T *wp) } } -// Delay freeing of location list stacks when the quickfix code is running. -// Used to avoid problems with autocmds freeing location list stacks when the -// quickfix code is still referencing the stack. -// Must always call decr_quickfix_busy() exactly once after this. +/// Delay freeing of location list stacks when the quickfix code is running. +/// Used to avoid problems with autocmds freeing location list stacks when the +/// quickfix code is still referencing the stack. +/// Must always call decr_quickfix_busy() exactly once after this. static void incr_quickfix_busy(void) { quickfix_busy++; } -// Safe to free location list stacks. Process any delayed delete requests. +/// Safe to free location list stacks. Process any delayed delete requests. static void decr_quickfix_busy(void) { quickfix_busy--; @@ -1878,7 +1876,7 @@ static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, return QF_OK; } -// Allocate a new quickfix/location list stack +/// Allocate a new quickfix/location list stack static qf_info_T *qf_alloc_stack(qfltype_T qfltype) FUNC_ATTR_NONNULL_RET { @@ -1889,8 +1887,8 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype) return qi; } -// Return the location list stack for window 'wp'. -// If not present, allocate a location list stack +/// Return the location list stack for window 'wp'. +/// If not present, allocate a location list stack static qf_info_T *ll_get_or_alloc_list(win_T *wp) { if (IS_LL_WINDOW(wp)) @@ -1909,10 +1907,10 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) return wp->w_llist; } -// Get the quickfix/location list stack to use for the specified Ex command. -// For a location list command, returns the stack for the current window. If -// the location list is not found, then returns NULL and prints an error -// message if 'print_emsg' is TRUE. +/// Get the quickfix/location list stack to use for the specified Ex command. +/// For a location list command, returns the stack for the current window. If +/// the location list is not found, then returns NULL and prints an error +/// message if 'print_emsg' is TRUE. static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg) { qf_info_T *qi = &ql_info; @@ -1930,11 +1928,11 @@ static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg) return qi; } -// Get the quickfix/location list stack to use for the specified Ex command. -// For a location list command, returns the stack for the current window. -// If the location list is not present, then allocates a new one. -// Returns NULL if the allocation fails. For a location list command, sets -// 'pwinp' to curwin. +/// Get the quickfix/location list stack to use for the specified Ex command. +/// For a location list command, returns the stack for the current window. +/// If the location list is not present, then allocates a new one. +/// Returns NULL if the allocation fails. For a location list command, sets +/// 'pwinp' to curwin. static qf_info_T * qf_cmd_get_or_alloc_stack(exarg_T *eap, win_T **pwinp) { qf_info_T *qi = &ql_info; @@ -1950,7 +1948,7 @@ static qf_info_T * qf_cmd_get_or_alloc_stack(exarg_T *eap, win_T **pwinp) return qi; } -// Copy location list entries from 'from_qfl' to 'to_qfl'. +/// Copy location list entries from 'from_qfl' to 'to_qfl'. static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { @@ -1989,7 +1987,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) return OK; } -// Copy the specified location list 'from_qfl' to 'to_qfl'. +/// Copy the specified location list 'from_qfl' to 'to_qfl'. static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { @@ -2075,8 +2073,8 @@ void copy_loclist_stack(win_T *from, win_T *to) to->w_llist->qf_curlist = qi->qf_curlist; // current list } -// Get buffer number for file "directory/fname". -// Also sets the b_has_qf_entry flag. +/// Get buffer number for file "directory/fname". +/// Also sets the b_has_qf_entry flag. static int qf_get_fnum(qf_list_T *qfl, char_u *directory, char_u *fname ) { char_u *ptr = NULL; @@ -2230,24 +2228,24 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr) } } -// Check in which directory of the directory stack the given file can be -// found. -// Returns a pointer to the directory name or NULL if not found. -// Cleans up intermediate directory entries. -// -// TODO(vim): How to solve the following problem? -// If we have this directory tree: -// ./ -// ./aa -// ./aa/bb -// ./bb -// ./bb/x.c -// and make says: -// making all in aa -// making all in bb -// x.c:9: Error -// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. -// qf_guess_filepath will return NULL. +/// Check in which directory of the directory stack the given file can be +/// found. +/// Returns a pointer to the directory name or NULL if not found. +/// Cleans up intermediate directory entries. +/// +/// TODO(vim): How to solve the following problem? +/// If we have this directory tree: +/// ./ +/// ./aa +/// ./aa/bb +/// ./bb +/// ./bb/x.c +/// and make says: +/// making all in aa +/// making all in bb +/// x.c:9: Error +/// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. +/// qf_guess_filepath will return NULL. static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *filename) { struct dir_stack_T *ds_ptr; @@ -2444,7 +2442,7 @@ static win_T *qf_find_help_win(void) return NULL; } -// Set the location list for the specified window to 'qi'. +/// Set the location list for the specified window to 'qi'. static void win_set_loclist(win_T *wp, qf_info_T *qi) { wp->w_llist = qi; @@ -2526,7 +2524,7 @@ static win_T *qf_find_win_with_normal_buf(void) return NULL; } -// Go to a window in any tabpage containing the specified file. Returns TRUE +// Go to a window in any tabpage containing the specified file. Returns true // if successfully jumped to the window. Otherwise returns FALSE. static bool qf_goto_tabwin_with_file(int fnum) { @@ -2960,11 +2958,11 @@ static int qfFileAttr; static int qfSepAttr; static int qfLineAttr; -// Display information about a single entry from the quickfix/location list. -// Used by ":clist/:llist" commands. -// 'cursel' will be set to TRUE for the currently selected entry in the -// quickfix list. -static void qf_list_entry(qfline_T *qfp, int qf_idx, int cursel) +/// Display information about a single entry from the quickfix/location list. +/// Used by ":clist/:llist" commands. +/// 'cursel' will be set to true for the currently selected entry in the +/// quickfix list. +static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) { char_u *fname; buf_T *buf; @@ -3172,12 +3170,10 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) msg(buf); } -/* - * ":colder [count]": Up in the quickfix stack. - * ":cnewer [count]": Down in the quickfix stack. - * ":lolder [count]": Up in the location list stack. - * ":lnewer [count]": Down in the location list stack. - */ +/// ":colder [count]": Up in the quickfix stack. +/// ":cnewer [count]": Down in the quickfix stack. +/// ":lolder [count]": Up in the location list stack. +/// ":lnewer [count]": Down in the location list stack. void qf_age(exarg_T *eap) { qf_info_T *qi; @@ -3553,7 +3549,7 @@ static int qf_open_new_cwindow(const qf_info_T *qi, int height) return OK; } -// Set "w:quickfix_title" if "qi" has a title. +/// Set "w:quickfix_title" if "qi" has a title. static void qf_set_title_var(qf_list_T *qfl) { if (qfl->qf_title != NULL) { @@ -3561,10 +3557,8 @@ static void qf_set_title_var(qf_list_T *qfl) } } -/* - * ":copen": open a window that shows the list of errors. - * ":lopen": open a window that shows the location list. - */ +/// ":copen": open a window that shows the list of errors. +/// ":lopen": open a window that shows the location list. void ex_copen(exarg_T *eap) { qf_info_T *qi; @@ -3634,7 +3628,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curbuf = curwin->w_buffer; } -// :cbottom/:lbottom command. +/// :cbottom/:lbottom command. void ex_cbottom(exarg_T *eap) { qf_info_T *qi; @@ -3867,11 +3861,11 @@ static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, return OK; } -// Fill current buffer with quickfix errors, replacing any previous contents. -// curbuf must be the quickfix buffer! -// If "old_last" is not NULL append the items after this one. -// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() -// is used and autocommands will be triggered. +/// Fill current buffer with quickfix errors, replacing any previous contents. +/// curbuf must be the quickfix buffer! +/// If "old_last" is not NULL append the items after this one. +/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() +/// is used and autocommands will be triggered. static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) FUNC_ATTR_NONNULL_ARG(2) { @@ -3966,11 +3960,11 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) return INVALID_QFIDX; } -// If the current list is not "save_qfid" and we can find the list with that ID -// then make it the current list. -// This is used when autocommands may have changed the current list. -// Returns OK if successfully restored the list. Returns FAIL if the list with -// the specified identifier (save_qfid) is not found in the stack. +/// If the current list is not "save_qfid" and we can find the list with that ID +/// then make it the current list. +/// This is used when autocommands may have changed the current list. +/// Returns OK if successfully restored the list. Returns FAIL if the list with +/// the specified identifier (save_qfid) is not found in the stack. static int qf_restore_list(qf_info_T *qi, unsigned save_qfid) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -4318,11 +4312,9 @@ static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) return i <= qfl->qf_count ? (size_t)i : 1; } -/* - * ":cc", ":crewind", ":cfirst" and ":clast". - * ":ll", ":lrewind", ":lfirst" and ":llast". - * ":cdo", ":ldo", ":cfdo" and ":lfdo". - */ +/// ":cc", ":crewind", ":cfirst" and ":clast". +/// ":ll", ":lrewind", ":lfirst" and ":llast". +/// ":cdo", ":ldo", ":cfdo" and ":lfdo". void ex_cc(exarg_T *eap) { qf_info_T *qi; @@ -4373,11 +4365,9 @@ void ex_cc(exarg_T *eap) qf_jump(qi, 0, errornr, eap->forceit); } -/* - * ":cnext", ":cnfile", ":cNext" and ":cprevious". - * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". - * ":cdo", ":ldo", ":cfdo" and ":lfdo". - */ +/// ":cnext", ":cnfile", ":cNext" and ":cprevious". +/// ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". +/// ":cdo", ":ldo", ":cfdo" and ":lfdo". void ex_cnext(exarg_T *eap) { qf_info_T *qi; @@ -4428,9 +4418,9 @@ void ex_cnext(exarg_T *eap) qf_jump(qi, dir, errornr, eap->forceit); } -// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'. -// The index of the entry is stored in 'errornr'. -// Returns NULL if an entry is not found. +/// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'. +/// The index of the entry is stored in 'errornr'. +/// Returns NULL if an entry is not found. static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl, int bnr, int *errornr) @@ -4449,9 +4439,9 @@ static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl, return qfp; } -// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr' -// with the error number for the first entry. Assumes the entries are sorted in -// the quickfix list by line number. +/// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr' +/// with the error number for the first entry. Assumes the entries are sorted in +/// the quickfix list by line number. static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr) { while (!got_int @@ -4465,9 +4455,9 @@ static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr) return entry; } -// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr' -// with the error number for the last entry. Assumes the entries are sorted in -// the quickfix list by line number. +/// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr' +/// with the error number for the last entry. Assumes the entries are sorted in +/// the quickfix list by line number. static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr) { while (!got_int @@ -4481,10 +4471,10 @@ static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr) return entry; } -// Find the first quickfix entry below line 'lnum' in buffer 'bnr'. -// 'qfp' points to the very first entry in the buffer and 'errornr' is the -// index of the very first entry in the quickfix list. -// Returns NULL if an entry is not found after 'lnum'. +/// Find the first quickfix entry below line 'lnum' in buffer 'bnr'. +/// 'qfp' points to the very first entry in the buffer and 'errornr' is the +/// index of the very first entry in the quickfix list. +/// Returns NULL if an entry is not found after 'lnum'. static qfline_T *qf_find_entry_on_next_line(int bnr, linenr_T lnum, qfline_T *qfp, @@ -4515,10 +4505,10 @@ static qfline_T *qf_find_entry_on_next_line(int bnr, return qfp; } -// Find the first quickfix entry before line 'lnum' in buffer 'bnr'. -// 'qfp' points to the very first entry in the buffer and 'errornr' is the -// index of the very first entry in the quickfix list. -// Returns NULL if an entry is not found before 'lnum'. +/// Find the first quickfix entry before line 'lnum' in buffer 'bnr'. +/// 'qfp' points to the very first entry in the buffer and 'errornr' is the +/// index of the very first entry in the quickfix list. +/// Returns NULL if an entry is not found before 'lnum'. static qfline_T *qf_find_entry_on_prev_line(int bnr, linenr_T lnum, qfline_T *qfp, @@ -4542,8 +4532,8 @@ static qfline_T *qf_find_entry_on_prev_line(int bnr, return qfp; } -// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in -// the direction 'dir'. +/// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in +/// the direction 'dir'. static qfline_T *qf_find_closest_entry(qf_list_T *qfl, int bnr, linenr_T lnum, @@ -4569,8 +4559,8 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, return qfp; } -// Get the nth quickfix entry below the specified entry treating multiple -// entries on a single line as one. Searches forward in the list. +/// Get the nth quickfix entry below the specified entry treating multiple +/// entries on a single line as one. Searches forward in the list. static qfline_T *qf_get_nth_below_entry(qfline_T *entry, int *errornr, linenr_T n) @@ -4598,8 +4588,8 @@ static qfline_T *qf_get_nth_below_entry(qfline_T *entry, return entry; } -// Get the nth quickfix entry above the specified entry treating multiple -// entries on a single line as one. Searches backwards in the list. +/// Get the nth quickfix entry above the specified entry treating multiple +/// entries on a single line as one. Searches backwards in the list. static qfline_T *qf_get_nth_above_entry(qfline_T *entry, int *errornr, linenr_T n) @@ -4620,9 +4610,9 @@ static qfline_T *qf_get_nth_above_entry(qfline_T *entry, return entry; } -// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the -// specified direction. -// Returns the error number in the quickfix list or 0 if an entry is not found. +/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the +/// specified direction. +/// Returns the error number in the quickfix list or 0 if an entry is not found. static int qf_find_nth_adj_entry(qf_list_T *qfl, int bnr, linenr_T lnum, @@ -4650,8 +4640,8 @@ static int qf_find_nth_adj_entry(qf_list_T *qfl, return errornr; } -// Jump to a quickfix entry in the current file nearest to the current line. -// ":cabove", ":cbelow", ":labove" and ":lbelow" commands +/// Jump to a quickfix entry in the current file nearest to the current line. +/// ":cabove", ":cbelow", ":labove" and ":lbelow" commands void ex_cbelow(exarg_T *eap) { qf_info_T *qi; @@ -4704,7 +4694,7 @@ void ex_cbelow(exarg_T *eap) } -// Return the autocmd name for the :cfile Ex commands +/// Return the autocmd name for the :cfile Ex commands static char_u * cfile_get_auname(cmdidx_T cmdidx) { switch (cmdidx) { @@ -5367,8 +5357,8 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) } } -// Copy the specified quickfix entry items into a new dict and appened the dict -// to 'list'. Returns OK on success. +/// Copy the specified quickfix entry items into a new dict and appened the dict +/// to 'list'. Returns OK on success. static int get_qfline_items(qfline_T *qfp, list_T *list) { char_u buf[2]; @@ -5794,8 +5784,8 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) return status; } -// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the -// items in the dict 'd'. +/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the +/// items in the dict 'd'. static int qf_add_entry_from_dict( qf_list_T *qfl, const dict_T *d, @@ -5922,7 +5912,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, return retval; } -// Get the quickfix list index from 'nr' or 'id' +/// Get the quickfix list index from 'nr' or 'id' static int qf_setprop_get_qfidx( const qf_info_T *qi, const dict_T *what, @@ -6064,9 +6054,9 @@ static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di) return OK; } -// Set quickfix/location list properties (title, items, context). -// Also used to add items from parsing a list of lines. -// Used by the setqflist() and setloclist() Vim script functions. +/// Set quickfix/location list properties (title, items, context). +/// Also used to add items from parsing a list of lines. +/// Used by the setqflist() and setloclist() Vim script functions. static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char_u *title) FUNC_ATTR_NONNULL_ALL @@ -6107,8 +6097,8 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, return retval; } -// Find the non-location list window with the specified location list stack in -// the current tabpage. +/// Find the non-location list window with the specified location list stack in +/// the current tabpage. static win_T * find_win_with_ll(qf_info_T *qi) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -6247,7 +6237,7 @@ bool set_ref_in_quickfix(int copyID) return abort; } -// Return the autocmd name for the :cbuffer Ex commands +/// Return the autocmd name for the :cbuffer Ex commands static char_u * cbuffer_get_auname(cmdidx_T cmdidx) { switch (cmdidx) { @@ -6261,8 +6251,8 @@ static char_u * cbuffer_get_auname(cmdidx_T cmdidx) } } -// Process and validate the arguments passed to the :cbuffer, :caddbuffer, -// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands. +/// Process and validate the arguments passed to the :cbuffer, :caddbuffer, +/// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands. static int cbuffer_process_args(exarg_T *eap, buf_T **bufp, linenr_T *line1, @@ -6384,7 +6374,7 @@ void ex_cbuffer(exarg_T *eap) decr_quickfix_busy(); } -// Return the autocmd name for the :cexpr Ex commands. +/// Return the autocmd name for the :cexpr Ex commands. static char_u * cexpr_get_auname(cmdidx_T cmdidx) { switch (cmdidx) { @@ -6398,10 +6388,8 @@ static char_u * cexpr_get_auname(cmdidx_T cmdidx) } } -/* - * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. - * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. - */ +/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. +/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. void ex_cexpr(exarg_T *eap) { qf_info_T *qi; -- cgit From 471427d045f74fa9d2e47b2975ef178e719c2ae8 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 1 Nov 2019 00:43:20 -0400 Subject: vim-patch:8.1.2231: introduce gM command #11321 Problem: Not easy to move to the middle of a text line. Solution: Add the gM command. (Yasuhiro Matsumoto, closes vim/vim#2070) https://github.com/vim/vim/commit/8b530c1ff91f07cf6b0289a536992b7dfbc86598 --- src/nvim/normal.c | 16 ++++++++++++++++ src/nvim/testdir/test_normal.vim | 24 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d1c6362931..2ef2c3101f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6749,6 +6749,22 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; + case 'M': + { + const char_u *const ptr = get_cursor_line_ptr(); + + oap->motion_type = kMTCharWise; + oap->inclusive = false; + i = (int)mb_string2cells_len(ptr, STRLEN(ptr)); + if (cap->count0 > 0 && cap->count0 <= 100) { + coladvance((colnr_T)(i * cap->count0 / 100)); + } else { + coladvance((colnr_T)(i / 2)); + } + curwin->w_set_curswant = true; + } + break; + case '_': /* "g_": to the last non-blank character in the line or lines * downward. */ diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 07d250cace..eab638d19a 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1895,6 +1895,7 @@ fun! Test_normal33_g_cmd2() set wrap listchars= sbr= let lineA='abcdefghijklmnopqrstuvwxyz' let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $put =lineA $put =lineB @@ -1928,9 +1929,30 @@ fun! Test_normal33_g_cmd2() call assert_equal(15, col('.')) call assert_equal('l', getreg(0)) + norm! 2ggdd + $put =lineC + + " Test for gM + norm! gMyl + call assert_equal(73, col('.')) + call assert_equal('0', getreg(0)) + " Test for 20gM + norm! 20gMyl + call assert_equal(29, col('.')) + call assert_equal('S', getreg(0)) + " Test for 60gM + norm! 60gMyl + call assert_equal(87, col('.')) + call assert_equal('E', getreg(0)) + + " Test for g Ctrl-G + set ff=unix + let a=execute(":norm! g\") + call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a) + " Test for gI norm! gIfoo - call assert_equal(['', 'fooabcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) + call assert_equal(['', 'foo0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'], getline(1,'$')) " Test for gi wincmd c -- cgit From 63ab994fba2f2e0878be734d20bf8d106ddab67c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 1 Nov 2019 22:26:06 -0400 Subject: vim-patch:8.1.2235: "C" with 'virtualedit' set does not include multi-byte char Problem: "C" with 'virtualedit' set does not include multi-byte char. Solution: Include the whole multi-byte char. (Nobuhiro Takasaki, closes vim/vim#5152) https://github.com/vim/vim/commit/77ccc00340ed2598f7aa09716217e878665964fa --- src/nvim/ops.c | 1 + src/nvim/testdir/test_virtualedit.vim | 9 +++++++++ 2 files changed, 10 insertions(+) (limited to 'src') diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 030782cbcc..fbbdfdcd82 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1562,6 +1562,7 @@ int op_delete(oparg_T *oap) oap->end = curwin->w_cursor; curwin->w_cursor = oap->start; } + mb_adjust_opend(oap); } if (oap->line_count == 1) { /* delete characters within one line */ diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 67adede8d7..1e6b26a057 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -73,3 +73,12 @@ func Test_edit_CTRL_G() bwipe! set virtualedit= endfunc + +func Test_edit_change() + new + set virtualedit=all + call setline(1, "\t⒌") + normal Cx + call assert_equal('x', getline(1)) + bwipe! +endfunc -- cgit From eb509dc7c52cba4e837f4500dc28b0519d1fc11d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 11 Sep 2019 12:26:21 +0200 Subject: tui: simplify branching of rgb vs cterm colors --- src/nvim/tui/tui.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 945b093f32..e984d0fc6e 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -515,20 +515,8 @@ static void update_attrs(UI *ui, int attr_id) } data->print_attr_id = attr_id; HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id); - - int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1); - if (fg == -1) { - fg = ui->rgb ? data->clear_attrs.rgb_fg_color - : (data->clear_attrs.cterm_fg_color - 1); - } - - int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1); - if (bg == -1) { - bg = ui->rgb ? data->clear_attrs.rgb_bg_color - : (data->clear_attrs.cterm_bg_color - 1); - } - int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; + bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; bool reverse = attr & HL_INVERSE; @@ -596,7 +584,10 @@ static void update_attrs(UI *ui, int attr_id) unibi_out_ext(ui, data->unibi_ext.set_underline_color); } } + + int fg, bg; if (ui->rgb) { + fg = (attrs.rgb_fg_color != -1) ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color; if (fg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green @@ -604,6 +595,7 @@ static void update_attrs(UI *ui, int attr_id) unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground); } + bg = (attrs.rgb_bg_color != -1) ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color; if (bg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green @@ -611,11 +603,13 @@ static void update_attrs(UI *ui, int attr_id) unibi_out_ext(ui, data->unibi_ext.set_rgb_background); } } else { + fg = attrs.cterm_fg_color ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1); if (fg != -1) { UNIBI_SET_NUM_VAR(data->params[0], fg); unibi_out(ui, unibi_set_a_foreground); } + bg = attrs.cterm_bg_color ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1); if (bg != -1) { UNIBI_SET_NUM_VAR(data->params[0], bg); unibi_out(ui, unibi_set_a_background); -- cgit From 0cee3001947340c0d74d32a17a2c93147e13cb7f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Sat, 2 Nov 2019 09:31:48 +0000 Subject: Simplify split_success logic --- src/nvim/ex_cmds.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index a71b249a92..8cdf971b9e 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5566,12 +5566,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, linenr_T highest_num_line = 0; int col_width = 0; - // if we fail to split the window, we don't want to modify orig_buf - bool split_success = false; - if (split && win_split((int)p_cwh, WSP_BOT) != FAIL) { - split_success = true; - buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]"); buf_clear(); preview_buf = curbuf; @@ -5589,6 +5584,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, highest_num_line = kv_last(lines.subresults).end.lnum; col_width = log10(highest_num_line) + 1 + 3; } + } else { + // if we fail to split the window, we don't want to modify the preview buffer + preview_buf = NULL; } char *str = NULL; // construct the line to show in here @@ -5601,7 +5599,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) { SubResult match = lines.subresults.items[matchidx]; - if (split_success && preview_buf) { + if (preview_buf) { lpos_T p_start = { 0, match.start.col }; // match starts here in preview lpos_T p_end = { 0, match.end.col }; // ... and ends here -- cgit From 08fe10010ab438451d976e8fb412c3034d9ffeed Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 11 Sep 2019 12:30:29 +0200 Subject: terminal: enable pass through indexed colors to TUI --- src/nvim/highlight.c | 8 ++++++++ src/nvim/highlight_defs.h | 2 ++ src/nvim/terminal.c | 11 +++++++---- src/nvim/tui/tui.c | 27 +++++++++++++++++---------- 4 files changed, 34 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 83ee89b2a1..8564737ea2 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -611,6 +611,14 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) } if (use_rgb) { + if (mask & HL_FG_INDEXED) { + PUT(hl, "fg_indexed", BOOLEAN_OBJ(true)); + } + + if (mask & HL_BG_INDEXED) { + PUT(hl, "bg_indexed", BOOLEAN_OBJ(true)); + } + if (ae.rgb_fg_color != -1) { PUT(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color)); } diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 255699c8e0..36f3181674 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -19,6 +19,8 @@ typedef enum { HL_STANDOUT = 0x20, HL_STRIKETHROUGH = 0x40, HL_NOCOMBINE = 0x80, + HL_BG_INDEXED = 0x0100, + HL_FG_INDEXED = 0x0200, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 7609006906..8cbd418ed4 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -598,16 +598,19 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int vt_fg = fg_default ? -1 : get_rgb(state, cell.fg); int vt_bg = bg_default ? -1 : get_rgb(state, cell.bg); - int vt_fg_idx = ((!fg_default && VTERM_COLOR_IS_INDEXED(&cell.fg)) - ? cell.fg.indexed.idx + 1 : 0); - int vt_bg_idx = ((!bg_default && VTERM_COLOR_IS_INDEXED(&cell.bg)) - ? cell.bg.indexed.idx + 1 : 0); + bool fg_indexed = VTERM_COLOR_IS_INDEXED(&cell.fg); + bool bg_indexed = VTERM_COLOR_IS_INDEXED(&cell.bg); + + int vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0); + int vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0); int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0) | (cell.attrs.italic ? HL_ITALIC : 0) | (cell.attrs.reverse ? HL_INVERSE : 0) | (cell.attrs.underline ? HL_UNDERLINE : 0) | (cell.attrs.strike ? HL_STRIKETHROUGH: 0); + | (fg_indexed ? HL_FG_INDEXED : 0) + | (bg_indexed ? HL_BG_INDEXED : 0); int attr_id = 0; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e984d0fc6e..11746441aa 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -586,16 +586,27 @@ static void update_attrs(UI *ui, int attr_id) } int fg, bg; - if (ui->rgb) { - fg = (attrs.rgb_fg_color != -1) ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color; + if (ui->rgb && !(attr & HL_FG_INDEXED)) { + fg = ((attrs.rgb_fg_color != -1) + ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color); if (fg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground); } + } else { + fg = (attrs.cterm_fg_color + ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1)); + if (fg != -1) { + UNIBI_SET_NUM_VAR(data->params[0], fg); + unibi_out(ui, unibi_set_a_foreground); + } + } - bg = (attrs.rgb_bg_color != -1) ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color; + if (ui->rgb && !(attr & HL_BG_INDEXED)) { + bg = ((attrs.rgb_bg_color != -1) + ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color); if (bg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green @@ -603,19 +614,15 @@ static void update_attrs(UI *ui, int attr_id) unibi_out_ext(ui, data->unibi_ext.set_rgb_background); } } else { - fg = attrs.cterm_fg_color ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1); - if (fg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], fg); - unibi_out(ui, unibi_set_a_foreground); - } - - bg = attrs.cterm_bg_color ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1); + bg = (attrs.cterm_bg_color + ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1)); if (bg != -1) { UNIBI_SET_NUM_VAR(data->params[0], bg); unibi_out(ui, unibi_set_a_background); } } + data->default_attr = fg == -1 && bg == -1 && !bold && !italic && !underline && !undercurl && !reverse && !standout && !strikethrough; -- cgit From 31137e9bc70f2514b065cc4b9beed42399ab2904 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 11 Sep 2019 19:11:18 +0200 Subject: highlight: correctly disable index attribute with combine/blend --- src/nvim/highlight.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 8564737ea2..c96f07ed89 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -321,18 +321,26 @@ int hl_combine_attr(int char_attr, int prim_attr) if (spell_aep.cterm_fg_color > 0) { new_en.cterm_fg_color = spell_aep.cterm_fg_color; + new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); } if (spell_aep.cterm_bg_color > 0) { new_en.cterm_bg_color = spell_aep.cterm_bg_color; + new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); } if (spell_aep.rgb_fg_color >= 0) { new_en.rgb_fg_color = spell_aep.rgb_fg_color; + new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); } if (spell_aep.rgb_bg_color >= 0) { new_en.rgb_bg_color = spell_aep.rgb_bg_color; + new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); } if (spell_aep.rgb_sp_color >= 0) { @@ -422,6 +430,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) cattrs.cterm_bg_color = fattrs.cterm_bg_color; cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color, fattrs.cterm_bg_color); + cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED); } else { cattrs = fattrs; if (ratio >= 50) { @@ -435,6 +444,8 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) } else { cattrs.rgb_sp_color = -1; } + + cattrs.rgb_ae_attr &= ~HL_BG_INDEXED; } cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color, fattrs.rgb_bg_color); -- cgit From cbaba190018a65d9de268702fc9616176bb2be0f Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 10 Oct 2019 22:16:55 +0200 Subject: terminal: preserve support for g:terminal_color_X = "#1234ab" --- src/nvim/terminal.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 8cbd418ed4..c5e756905a 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -138,6 +138,8 @@ struct terminal { int pressed_button; // which mouse button is pressed bool pending_resize; // pending width/height + bool color_set[16]; + size_t refcount; // reference count }; @@ -241,6 +243,7 @@ Terminal *terminal_open(TerminalOptions opts) (uint8_t)((color_val >> 8) & 0xFF), (uint8_t)((color_val >> 0) & 0xFF)); vterm_state_set_palette_color(state, i, &color); + rv->color_set[i] = true; } } } @@ -604,13 +607,16 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0); int vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0); + bool fg_set = vt_fg_idx && vt_fg_idx <= 16 && term->color_set[vt_fg_idx-1]; + bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx-1]; + int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0) | (cell.attrs.italic ? HL_ITALIC : 0) | (cell.attrs.reverse ? HL_INVERSE : 0) | (cell.attrs.underline ? HL_UNDERLINE : 0) - | (cell.attrs.strike ? HL_STRIKETHROUGH: 0); - | (fg_indexed ? HL_FG_INDEXED : 0) - | (bg_indexed ? HL_BG_INDEXED : 0); + | (cell.attrs.strike ? HL_STRIKETHROUGH: 0) + | ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0) + | ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0); int attr_id = 0; -- cgit From 4a5adae95084e2dbaea59f0cffb9087ec4a6415e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 2 Nov 2019 11:32:39 -0400 Subject: vim-patch:8.1.2236: ml_get error if pattern matches beyond last line Problem: Ml_get error if pattern matches beyond last line. Solution: Adjust position if needed. (Christian Brabandt, closes ) https://github.com/vim/vim/commit/bb26596242fa7db477e2cd706dd99f9a426b5f71 --- src/nvim/ex_cmds.c | 5 +++++ src/nvim/testdir/test_substitute.vim | 10 ++++++++++ 2 files changed, 15 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2e8bd79c81..04aa8f7ef6 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3524,6 +3524,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, // Note: If not first match on a line, column can't be known here current_match.start.lnum = sub_firstlnum; + // Match might be after the last line for "\n\zs" matching at + // the end of the last line. + if (lnum > curbuf->b_ml.ml_line_count) { + break; + } if (sub_firstline == NULL) { sub_firstline = vim_strsave(ml_get(sub_firstlnum)); } diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index b29b678129..e209310a05 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -149,6 +149,7 @@ func Run_SubCmd_Tests(tests) for t in a:tests let start = line('.') + 1 let end = start + len(t[2]) - 1 + " TODO: why is there a one second delay the first time we get here? exe "normal o" . t[0] call cursor(start, 1) exe t[1] @@ -717,3 +718,12 @@ one two close! endfunc + +func Test_sub_beyond_end() + new + call setline(1, '#') + let @/ = '^#\n\zs' + s///e + call assert_equal('#', getline(1)) + bwipe! +endfunc -- cgit From 6c5772f7daf0017b0a3cc9e67055b084f2779ad7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 2 Nov 2019 14:26:31 -0400 Subject: quickfix: fix pvs/v547 error --- src/nvim/quickfix.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 4ca9ca2a3e..a9e0c22a76 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1693,9 +1693,7 @@ static int copy_loclist(const qf_list_T *from_qfl, } if (from_qfl->qf_ctx != NULL) { to_qfl->qf_ctx = xcalloc(1, sizeof(*to_qfl->qf_ctx)); - if (to_qfl->qf_ctx != NULL) { - tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); - } + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); } else { to_qfl->qf_ctx = NULL; } -- cgit From 1c43fb1d51cd76ccca142e73d72af56f6fb0461d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 2 Nov 2019 14:36:00 -0400 Subject: syntax: zero-init local structs Fix https://neovim.io/doc/reports/clang/report-ee5dbd.html#EndPath --- src/nvim/syntax.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ac8ace9fff..bdbc09a87a 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -2460,11 +2460,8 @@ update_si_end( int force /* when TRUE overrule a previous end */ ) { - lpos_T startpos; - lpos_T endpos; lpos_T hl_endpos; lpos_T end_endpos; - int end_idx; /* return quickly for a keyword */ if (sip->si_idx < 0) @@ -2480,9 +2477,12 @@ update_si_end( * We need to find the end of the region. It may continue in the next * line. */ - end_idx = 0; - startpos.lnum = current_lnum; - startpos.col = startcol; + int end_idx = 0; + lpos_T startpos = { + .lnum = current_lnum, + .col = startcol, + }; + lpos_T endpos = { 0 }; find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos, &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch); -- cgit From e2b54fda7282c9237952b769afd2a313564cba79 Mon Sep 17 00:00:00 2001 From: lacygoill Date: Mon, 4 Nov 2019 08:18:06 +0100 Subject: autocmd: Fix event name casing #11332 Affects getcompletion() --- src/nvim/auevents.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index c223679596..a52789c795 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -21,9 +21,9 @@ return { 'BufWritePre', -- before writing a buffer 'ChanInfo', -- info was received about channel 'ChanOpen', -- channel was opened - 'CmdLineChanged', -- command line was modified - 'CmdLineEnter', -- after entering cmdline mode - 'CmdLineLeave', -- before leaving cmdline mode + 'CmdlineChanged', -- command line was modified + '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 -- cgit From 4f124702c079fba4502b077c8c832538c0f0b386 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 5 Nov 2019 18:56:54 +0000 Subject: Simplify + inline/align comment --- src/nvim/ex_cmds.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8cdf971b9e..712ce711d0 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5535,9 +5535,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, // We keep a special-purpose buffer around, but don't assume it exists. buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0; - // disable :leftabove/botright modifiers - // (especially ones that conflict with our win_split() call below) - cmdmod.split = 0; + cmdmod.split = 0; // disable :leftabove/botright modifiers cmdmod.tab = 0; // disable :tab modifier cmdmod.noswapfile = true; // disable swap for preview buffer // disable file info message -- cgit From 9ef16a1628722958b6e14fe9274006e50ed6682d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 27 Oct 2019 15:05:59 -0700 Subject: doc: vim.fn, vim.call(), vim.api [ci skip] --- src/nvim/api/buffer.c | 49 ++++++++++++++++++++++++++++++++----------------- src/nvim/api/vim.c | 5 ++--- src/nvim/lua/vim.lua | 14 ++++++++++++++ 3 files changed, 48 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 3e1209d1b1..cb74c4227b 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -101,25 +101,39 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) return rv; } -/// Activates buffer-update events on a channel, or as lua callbacks. +/// Activates buffer-update events on a channel, or as Lua callbacks. +/// +/// @see |nvim_buf_detach()| +/// @see |api-buffer-updates-lua| /// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer -/// @param send_buffer Set to true if the initial notification should contain -/// the whole buffer. If so, the first notification will be a -/// `nvim_buf_lines_event`. Otherwise, the first notification will be -/// a `nvim_buf_changedtick_event`. Not used for lua callbacks. +/// @param send_buffer True if the initial notification should contain the +/// whole buffer: first notification will be `nvim_buf_lines_event`. +/// Else the first notification will be `nvim_buf_changedtick_event`. +/// Not for Lua callbacks. /// @param opts Optional parameters. -/// - `on_lines`: lua callback received on change. -/// - `on_changedtick`: lua callback received on changedtick -/// increment without text change. -/// - `utf_sizes`: include UTF-32 and UTF-16 size of -/// the replaced region. -/// See |api-buffer-updates-lua| for more information +/// - on_lines: Lua callback invoked on change. +/// Return `true` to detach. Args: +/// - buffer handle +/// - b:changedtick +/// - first line that changed (zero-indexed) +/// - last line that was changed +/// - last line in the updated range +/// - byte count of previous contents +/// - deleted_codepoints (if `utf_sizes` is true) +/// - deleted_codeunits (if `utf_sizes` is true) +/// - on_changedtick: Lua callback invoked on changedtick +/// increment without text change. Args: +/// - buffer handle +/// - b:changedtick +/// - on_detach: Lua callback invoked on detach. Args: +/// - buffer handle +/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced +/// region, as args to `on_lines`. /// @param[out] err Error details, if any -/// @return False when updates couldn't be enabled because the buffer isn't -/// loaded or `opts` contained an invalid key; otherwise True. -/// TODO: LUA_API_NO_EVAL +/// @return False if attach failed (invalid parameter, or buffer isn't loaded); +/// otherwise True. TODO: LUA_API_NO_EVAL Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, @@ -183,13 +197,14 @@ error: /// Deactivates buffer-update events on the channel. /// -/// For Lua callbacks see |api-lua-detach|. +/// @see |nvim_buf_attach()| +/// @see |api-lua-detach| for detaching Lua callbacks /// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any -/// @return False when updates couldn't be disabled because the buffer -/// isn't loaded; otherwise True. +/// @return False if detach failed (because the buffer isn't loaded); +/// otherwise True. Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 59761c13e7..0e64658f36 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1040,10 +1040,9 @@ fail: /// @param enter Enter the window (make it the current window) /// @param config Map defining the window configuration. Keys: /// - `relative`: Sets the window layout to "floating", placed at (row,col) -/// coordinates relative to one of: +/// coordinates relative to: /// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window by -/// default. +/// - "win" Window given by the `win` field, or current window. /// - "cursor" Cursor position in current window. /// - `win`: |window-ID| for relative="win". /// - `anchor`: Decides which corner of the float to place at (row,col): diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 5514819a02..adb90084db 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -165,6 +165,20 @@ end --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. --- +--- Example: To remove ANSI color codes when pasting: +---
+--- vim.paste = (function()
+---   local overridden = vim.paste
+---   return function(lines, phase)
+---     for i,line in ipairs(lines) do
+---       -- Scrub ANSI color codes from paste input.
+---       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+---     end
+---     overridden(lines, phase)
+---   end
+--- end)()
+--- 
+--- --@see |paste| --- --@param lines |readfile()|-style list of lines to paste. |channel-lines| -- cgit From b8a56e0986c92f09b5a98003065630fd4c1bb48d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 6 Nov 2019 00:21:26 -0500 Subject: vim-patch:8.1.2244: 'wrapscan' is not used for "gn" Problem: 'wrapscan' is not used for "gn". Solution: Only reset 'wrapscan' for the first search round. (closes vim/vim#5164) https://github.com/vim/vim/commit/82cf7f6df751505da285815a791463a049587849 --- src/nvim/search.c | 16 ++++++++-------- src/nvim/testdir/test_gn.vim | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index c4c8633ed9..d396e7551b 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -4037,9 +4037,6 @@ current_search( bool old_p_ws = p_ws; pos_T save_VIsual = VIsual; - /* wrapping should not occur */ - p_ws = false; - /* Correct cursor when 'selection' is exclusive */ if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) dec_cursor(); @@ -4063,8 +4060,7 @@ current_search( int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, FORWARD); if (zero_width == -1) { - p_ws = old_p_ws; - return FAIL; /* pattern not found */ + return FAIL; // pattern not found } /* @@ -4081,11 +4077,18 @@ current_search( } end_pos = pos; + // wrapping should not occur in the first round + if (i == 0) { + p_ws = false; + } + result = searchit(curwin, curbuf, &pos, &end_pos, (dir ? FORWARD : BACKWARD), spats[last_idx].pat, i ? count : 1, SEARCH_KEEP | flags, RE_SEARCH, NULL); + p_ws = old_p_ws; + // First search may fail, but then start searching from the // beginning of the file (cursor might be on the search match) // except when Visual mode is active, so that extending the visual @@ -4094,7 +4097,6 @@ current_search( curwin->w_cursor = orig_pos; if (VIsual_active) VIsual = save_VIsual; - p_ws = old_p_ws; return FAIL; } else if (i == 0 && !result) { if (forward) { // try again from start of buffer @@ -4110,8 +4112,6 @@ current_search( pos_T start_pos = pos; - p_ws = old_p_ws; - if (!VIsual_active) { VIsual = start_pos; } diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 834397126f..d41675be0c 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -136,8 +136,9 @@ func Test_gn_command() call assert_equal(['ABCDEFGHi'], getline(1,'$')) call setline('.', ['abcdefghi']) let @/ = 'b' + " this gn wraps around the end of the file exe "norm! 0fhvhhgngU" - call assert_equal(['abcdefghi'], getline(1,'$')) + call assert_equal(['aBCDEFGHi'], getline(1,'$')) sil! %d _ call setline('.', ['abcdefghi']) let @/ = 'f' -- cgit From 697b0d73a768da578b53d890300091fabceeeee0 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 6 Nov 2019 00:41:49 -0500 Subject: vim-patch:8.1.2258: may get hit-enter prompt after entering a number Problem: May get hit-enter prompt after entering a number. (Malcolm Rowe) Solution: Put back accidentally deleted lines. (closes vim/vim#5176) https://github.com/vim/vim/commit/dc968e7a45c672a81148628b755c2a440a228ad7 --- src/nvim/misc1.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 1db8a1fa11..c1de7ab9a4 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -792,6 +792,8 @@ int prompt_for_number(int *mouse_used) cmdline_row = msg_row - 1; } need_wait_return = false; + msg_didany = false; + msg_didout = false; } else { cmdline_row = save_cmdline_row; } -- cgit From c3cb54b5ff20c1a126cbbe9b964ceebff0c60ded Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 6 Nov 2019 22:01:58 -0500 Subject: vim-patch:8.1.1091: MS-Windows: cannot use multi-byte chars in environment var Problem: MS-Windows: cannot use multi-byte chars in environment var. Solution: Use the wide API. (Ken Takata, closes vim/vim#4008) https://github.com/vim/vim/commit/f0908e6fe18943ad4453d7d6772fa43049aff4bc --- src/nvim/testdir/test_let.vim | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 1fce3d6937..988d248544 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -141,6 +141,11 @@ func Test_let_varg_fail() call s:set_varg8([0]) endfunction +func Test_let_utf8_environment() + let $a = 'ĀĒĪŌŪあいうえお' + call assert_equal('ĀĒĪŌŪあいうえお', $a) +endfunc + func Test_let_heredoc_fails() call assert_fails('let v =<< marker', 'E991:') -- cgit From 3e2f7baf2138c204a9d94e2dc2cae7900b0b6122 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 6 Nov 2019 22:32:54 -0500 Subject: vim-patch:8.1.2262: unpack assignment in function not recognized Problem: Unpack assignment in function not recognized. Solution: Skip over "[a, b]". (closes vim/vim#5051) https://github.com/vim/vim/commit/1e673b9eb686459bd0e7fc3f2199dd077546a18e --- src/nvim/eval.c | 37 +++++++++++++++++++++++-------------- src/nvim/testdir/test_let.vim | 8 ++++++++ 2 files changed, 31 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index fd83bc846b..e08e129656 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21742,22 +21742,31 @@ void ex_function(exarg_T *eap) } // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" arg = skipwhite(skiptowhite(p)); - arg = skipwhite(skiptowhite(arg)); - if (arg[0] == '=' && arg[1] == '<' && arg[2] =='<' - && ((p[0] == 'l' && p[1] == 'e' - && (!ASCII_ISALNUM(p[2]) - || (p[2] == 't' && !ASCII_ISALNUM(p[3])))))) { - p = skipwhite(arg + 3); - if (STRNCMP(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = vim_strnsave(theline, - (int)(skipwhite(theline) - theline)); + if (*arg == '[') { + arg = vim_strchr(arg, ']'); + } + if (arg != NULL) { + arg = skipwhite(skiptowhite(arg)); + if (arg[0] == '=' + && arg[1] == '<' + && arg[2] =='<' + && (p[0] == 'l' + && p[1] == 'e' + && (!ASCII_ISALNUM(p[2]) + || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + p = skipwhite(arg + 3); + if (STRNCMP(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = + vim_strnsave(theline, (int)(skipwhite(theline) - theline)); + } + skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; } - skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; } } diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 988d248544..3c0fefbd25 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -289,4 +289,12 @@ E END endif call assert_equal([], check) + + " unpack assignment + let [a, b, c] =<< END + x + \y + z +END + call assert_equal([' x', ' \y', ' z'], [a, b, c]) endfunc -- cgit From 805a577f71f385c783e5ae7604aac41f650c2de6 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 7 Nov 2019 23:00:27 -0500 Subject: vim-patch:8.1.2268: spell file flag zero is not recognized Problem: Spell file flag zero is not recognized. Solution: Use -1 as an error value, so that zero can be used as a valid flag number. https://github.com/vim/vim/commit/3d2a47c7823b934e1a85d773b68758c87c3ddc90 --- src/nvim/spellfile.c | 17 ++++++++++++++--- src/nvim/testdir/test_spell.vim | 9 ++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index eeec5be120..4fac001bc5 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -265,6 +265,8 @@ // follow; never used in prefix tree #define BY_SPECIAL BY_FLAGS2 // highest special byte value +#define ZERO_FLAG 65009 // used when flag is zero: "0" + // Flags used in .spl file for soundsalike flags. #define SAL_F0LLOWUP 1 #define SAL_COLLAPSE 2 @@ -2783,6 +2785,7 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum } // Get one affix name from "*pp" and advance the pointer. +// Returns ZERO_FLAG for "0". // Returns zero for an error, still advances the pointer then. static unsigned get_affitem(int flagtype, char_u **pp) { @@ -2794,6 +2797,9 @@ static unsigned get_affitem(int flagtype, char_u **pp) return 0; } res = getdigits_int(pp, true, 0); + if (res == 0) { + res = ZERO_FLAG; + } } else { res = mb_ptr2char_adv((const char_u **)pp); if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG @@ -2915,10 +2921,15 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) int digits = getdigits_int(&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; - if (n == flag) + if (n == 0) { + n = ZERO_FLAG; + } + if (n == flag) { return true; - if (*p != NUL) // skip over comma - ++p; + } + if (*p != NUL) { // skip over comma + p++; + } } break; } diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e49b5542fa..9dce87774b 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -283,9 +283,9 @@ func Test_zz_affix() \ ]) call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) - call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + call RunGoodBad("meea1 meezero meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", - \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"], + \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "meezero", "prebar", "prebarmeat", "tail"], \ [ \ ["bad", ["bar", "lead", "tail"]], \ ["mee", ["meea1", "meea\xE9", "bar"]], @@ -713,6 +713,9 @@ let g:test_data_aff7 = [ \"SFX 61003 Y 1", \"SFX 61003 0 meat .", \"", + \"SFX 0 Y 1", + \"SFX 0 0 zero .", + \"", \"SFX 391 Y 1", \"SFX 391 0 a1 .", \"", @@ -724,7 +727,7 @@ let g:test_data_aff7 = [ \ ] let g:test_data_dic7 = [ \"1234", - \"mee/391,111,9999", + \"mee/0,391,111,9999", \"bar/17,61003,123", \"lead/2", \"tail/123", -- cgit From 2a59ae0f1da613629c08ab16e062cb6fda6ee778 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 8 Nov 2019 21:59:31 -0500 Subject: vim-patch:8.1.2270: "gf" is not tested in Visual mode Problem: "gf" is not tested in Visual mode. Solution: Add Visual mode test and test errors. (Dominique Pelle, closes vim/vim#5197) https://github.com/vim/vim/commit/0208b6b771161d1a668b3568f71dc2bde3614933 --- src/nvim/testdir/test_gf.vim | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index accd21e9a3..d301874891 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -99,3 +99,28 @@ func Test_gf() call delete('Xtest1') call delete('Xtestgf') endfunc + +func Test_gf_visual() + call writefile([], "Xtest_gf_visual") + new + call setline(1, 'XXXtest_gf_visualXXX') + set hidden + + " Visually select Xtest_gf_visual and use gf to go to that file + norm! ttvtXgf + call assert_equal('Xtest_gf_visual', bufname('%')) + + bwipe! + call delete('Xtest_gf_visual') + set hidden& +endfunc + +func Test_gf_error() + new + call assert_fails('normal gf', 'E446:') + call assert_fails('normal gF', 'E446:') + call setline(1, '/doesnotexist') + call assert_fails('normal gf', 'E447:') + call assert_fails('normal gF', 'E447:') + bwipe! +endfunc -- cgit From 2ed23af9b2442ca61d2419d024b5e1a568398ef2 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 8 Nov 2019 22:02:08 -0500 Subject: vim-patch:8.1.2272: test may hang at more prompt Problem: Test may hang at more prompt. Solution: Reset 'more' after resetting 'compatible'. (Michael Soyka) https://github.com/vim/vim/commit/34059e7b67ae8a58dc2471b309afe05d9dde760f --- src/nvim/testdir/test_vimscript.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 3fcba4134e..d2f13ff072 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1284,7 +1284,7 @@ func s:DoNothing() endfunc func Test_script_local_func() - set nocp viminfo+=nviminfo + set nocp nomore viminfo+=nviminfo new nnoremap _x :call DoNothing()call DoLast()delfunc DoNothingdelfunc DoLast -- cgit From 6e42eb4dc9d2c1f3b114651673e6f2699f2416a0 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 9 Nov 2019 09:56:36 -0500 Subject: vim-patch:8.1.0324: off-by-one error in cmdidx check Problem: Off-by-one error in cmdidx check. (Coverity) Solution: Use ">=" instead of ">". https://github.com/vim/vim/commit/74c8be2c6803eda3a57991b8867c5c65259b73d6 Fix pvs/v557. --- src/nvim/ex_docmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d3e2120721..7d02623d67 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -10156,7 +10156,7 @@ static void ex_folddo(exarg_T *eap) bool is_loclist_cmd(int cmdidx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (cmdidx < 0 || cmdidx > CMD_SIZE) { + if (cmdidx < 0 || cmdidx >= CMD_SIZE) { return false; } return cmdnames[cmdidx].cmd_name[0] == 'l'; -- cgit From 099c38efed6cf5ca87dbff5c1d90a274991da4cf Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 9 Nov 2019 10:11:56 -0500 Subject: quickfix: fix pvs/v547 --- src/nvim/quickfix.c | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index cf5194d16f..da315252b5 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1890,6 +1890,7 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype) /// Return the location list stack for window 'wp'. /// If not present, allocate a location list stack static qf_info_T *ll_get_or_alloc_list(win_T *wp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { if (IS_LL_WINDOW(wp)) /* For a location list window, use the referenced location list */ @@ -1931,17 +1932,14 @@ static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg) /// Get the quickfix/location list stack to use for the specified Ex command. /// For a location list command, returns the stack for the current window. /// If the location list is not present, then allocates a new one. -/// Returns NULL if the allocation fails. For a location list command, sets -/// 'pwinp' to curwin. -static qf_info_T * qf_cmd_get_or_alloc_stack(exarg_T *eap, win_T **pwinp) +/// For a location list command, sets 'pwinp' to curwin. +static qf_info_T *qf_cmd_get_or_alloc_stack(const exarg_T *eap, win_T **pwinp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { qf_info_T *qi = &ql_info; if (is_loclist_cmd(eap->cmdidx)) { qi = ll_get_or_alloc_list(curwin); - if (qi == NULL) { - return NULL; - } *pwinp = curwin; } @@ -4968,7 +4966,6 @@ void ex_vimgrep(exarg_T *eap) char_u *s; char_u *p; int fi; - qf_info_T *qi; qf_list_T *qfl; win_T *wp = NULL; buf_T *buf; @@ -4994,10 +4991,7 @@ void ex_vimgrep(exarg_T *eap) } } - qi = qf_cmd_get_or_alloc_stack(eap, &wp); - if (qi == NULL) { - return; - } + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); if (eap->addr_count > 0) tomatch = eap->line2; @@ -6304,7 +6298,6 @@ static int cbuffer_process_args(exarg_T *eap, void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; - qf_info_T *qi; char_u *au_name = NULL; win_T *wp = NULL; char_u *qf_title; @@ -6320,10 +6313,7 @@ void ex_cbuffer(exarg_T *eap) } // Must come after autocommands. - qi = qf_cmd_get_or_alloc_stack(eap, &wp); - if (qi == NULL) { - return; - } + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) { return; @@ -6392,7 +6382,6 @@ static char_u * cexpr_get_auname(cmdidx_T cmdidx) /// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. void ex_cexpr(exarg_T *eap) { - qf_info_T *qi; char_u *au_name = NULL; win_T *wp = NULL; @@ -6404,10 +6393,7 @@ void ex_cexpr(exarg_T *eap) } } - qi = qf_cmd_get_or_alloc_stack(eap, &wp); - if (qi == NULL) { - return; - } + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); /* Evaluate the expression. When the result is a string or a list we can * use it to fill the errorlist. */ -- cgit From 7a23b67d3594ffb8b6d8629fd9ca1ef8147596db Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 9 Nov 2019 21:18:51 -0800 Subject: paste: Select-mode, Visual-mode #11360 fix #11344 --- src/nvim/api/vim.c | 4 ++-- src/nvim/lua/vim.lua | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0e64658f36..e587df5384 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1251,7 +1251,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) draining = true; goto theend; } - if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) { + if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) { ResetRedobuff(); AppendCharToRedobuff('a'); // Dot-repeat. } @@ -1269,7 +1269,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) } } } - if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) { + if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) { AppendCharToRedobuff(ESC); // Dot-repeat. } theend: diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index adb90084db..1ebdde99d5 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -206,8 +206,11 @@ paste = (function() local line1 = lines[1]:gsub('<', ''):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks. - if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then + elseif mode ~= 'c' then + if phase < 2 and mode:find('^[vV\22sS\19]') then + vim.api.nvim_command([[exe "normal! \"]]) + vim.api.nvim_put(lines, 'c', false, true) + elseif phase < 2 and not mode:find('^[iRt]') then vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') -- cgit From 474d0bcbf724c7eed740f60391a0ed35d651e1d3 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 27 Oct 2019 20:49:03 +0100 Subject: lua: vim.rpcrequest, vim.rpcnotify, vim.NIL --- src/nvim/lua/converter.c | 40 +++++++++++++++++++++-- src/nvim/lua/executor.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/lua/executor.h | 2 ++ src/nvim/main.c | 1 + 4 files changed, 124 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 844232c64a..44fe60e9c8 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -377,6 +377,19 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) nlua_pop_typval_table_processing_end: break; } + case LUA_TUSERDATA: { + nlua_pushref(lstate, nlua_nil_ref); + bool is_nil = lua_rawequal(lstate, -2, -1); + lua_pop(lstate, 1); + if (is_nil) { + cur.tv->v_type = VAR_SPECIAL; + cur.tv->vval.v_special = kSpecialVarNull; + } else { + EMSG(_("E5101: Cannot convert given lua type")); + ret = false; + } + break; + } default: { EMSG(_("E5101: Cannot convert given lua type")); ret = false; @@ -406,7 +419,13 @@ static bool typval_conv_special = false; #define TYPVAL_ENCODE_ALLOW_SPECIALS true #define TYPVAL_ENCODE_CONV_NIL(tv) \ - lua_pushnil(lstate) + do { \ + if (typval_conv_special) { \ + lua_pushnil(lstate); \ + } else { \ + nlua_pushref(lstate, nlua_nil_ref); \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ lua_pushboolean(lstate, (bool)(num)) @@ -718,7 +737,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) { switch (obj.type) { case kObjectTypeNil: { - lua_pushnil(lstate); + if (special) { + lua_pushnil(lstate); + } else { + nlua_pushref(lstate, nlua_nil_ref); + } break; } case kObjectTypeLuaRef: { @@ -1152,6 +1175,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) break; } + case LUA_TUSERDATA: { + nlua_pushref(lstate, nlua_nil_ref); + bool is_nil = lua_rawequal(lstate, -2, -1); + lua_pop(lstate, 1); + if (is_nil) { + *cur.obj = NIL; + } else { + api_set_error(err, kErrorTypeValidation, + "Cannot convert userdata"); + } + break; + } + default: { type_error: api_set_error(err, kErrorTypeValidation, diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index c7ff163f83..5e5cb0cea5 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -12,6 +12,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" @@ -299,6 +300,14 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_call); lua_setfield(lstate, -2, "call"); + // rpcrequest + lua_pushcfunction(lstate, &nlua_rpcrequest); + lua_setfield(lstate, -2, "rpcrequest"); + + // rpcnotify + lua_pushcfunction(lstate, &nlua_rpcnotify); + lua_setfield(lstate, -2, "rpcnotify"); + // vim.loop luv_set_loop(lstate, &main_loop.uv); luv_set_callback(lstate, nlua_luv_cfpcall); @@ -314,6 +323,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "luv"); lua_pop(lstate, 3); + // vim.NIL + lua_newuserdata(lstate, 0); + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_nil_tostring); + lua_setfield(lstate, -2, "__tostring"); + lua_setmetatable(lstate, -2); + nlua_nil_ref = nlua_ref(lstate, -1); + lua_setfield(lstate, -2, "NIL"); + // internal vim._treesitter... API nlua_add_treesitter(lstate); @@ -547,6 +565,10 @@ int nlua_call(lua_State *lstate) Error err = ERROR_INIT; size_t name_len; const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len); + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "vimL function"); + } + int nargs = lua_gettop(lstate)-1; if (nargs > MAX_FUNC_ARGS) { return luaL_error(lstate, "Function called with too many arguments"); @@ -596,6 +618,67 @@ free_vim_args: return 1; } +static int nlua_rpcrequest(lua_State *lstate) +{ + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "rpcrequest"); + } + return nlua_rpc(lstate, true); +} + +static int nlua_rpcnotify(lua_State *lstate) +{ + return nlua_rpc(lstate, false); +} + +static int nlua_rpc(lua_State *lstate, bool request) +{ + size_t name_len; + uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1); + const char *name = luaL_checklstring(lstate, 2, &name_len); + int nargs = lua_gettop(lstate)-2; + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + + for (int i = 0; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+3); + ADD(args, nlua_pop_Object(lstate, false, &err)); + if (ERROR_SET(&err)) { + api_free_array(args); + goto check_err; + } + } + + if (request) { + Object result = rpc_send_call(chan_id, name, args, &err); + if (!ERROR_SET(&err)) { + nlua_push_Object(lstate, result, false); + api_free_object(result); + } + } else { + if (!rpc_send_event(chan_id, name, args)) { + api_set_error(&err, kErrorTypeValidation, + "Invalid channel: %"PRIu64, chan_id); + } + } + +check_err: + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + + return request ? 1 : 0; +} + +static int nlua_nil_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "vim.NIL"); + return 1; +} + + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 8d356a5600..32f66b629c 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -12,6 +12,8 @@ // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; +EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); + #define set_api_error(s, err) \ do { \ Error *err_ = (err); \ diff --git a/src/nvim/main.c b/src/nvim/main.c index e0a1e60fc0..e39eec4038 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -27,6 +27,7 @@ #include "nvim/highlight.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" +#include "nvim/lua/executor.h" #ifdef HAVE_LOCALE_H # include #endif -- cgit From 1cb4674547828a315b7aef5b6c635726b3bc12e5 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Sun, 10 Nov 2019 16:38:04 +0100 Subject: api: add nvim_buf_get_virtual_text() (#11354) This adds the missing partner function of nvim_buf_set_virtual_text(). --- src/nvim/api/buffer.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index cb74c4227b..10250be044 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1207,7 +1207,57 @@ free_exit: return 0; } -Dictionary nvim__buf_stats(Buffer buffer, Error *err) +/// Get the virtual text (annotation) for a buffer line. +/// +/// The virtual text is returned as list of lists, whereas the inner lists have +/// either one or two elements. The first element is the actual text, the +/// optional second element is the highlight group. +/// +/// The format is exactly the same as given to nvim_buf_set_virtual_text(). +/// +/// If there is no virtual text associated with the given line, an empty list +/// is returned. +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param line Line to get the virtual text from (zero-indexed) +/// @param[out] err Error details, if any +/// @return List of virtual text chunks +Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) + FUNC_API_SINCE(7) +{ + Array chunks = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return chunks; + } + + if (lnum < 0 || lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line number outside range"); + return chunks; + } + + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, (linenr_T)(lnum + 1), + false); + if (!lineinfo) { + return chunks; + } + + for (size_t i = 0; i < lineinfo->virt_text.size; i++) { + Array chunk = ARRAY_DICT_INIT; + VirtTextChunk *vtc = &lineinfo->virt_text.items[i]; + ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); + if (vtc->hl_id > 0) { + ADD(chunk, STRING_OBJ(cstr_to_string( + (const char *)syn_id2name(vtc->hl_id)))); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + + return chunks; +} + +Dictionary nvim__uf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; -- cgit From 7a3d3257db623e1fe7a9dee9944bdecedf615931 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Nov 2019 22:21:05 -0800 Subject: fix nvim__buf_stats Accidentally renamed in 1cb467454782. --- src/nvim/api/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 10250be044..257e02ec4f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1257,7 +1257,7 @@ Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) return chunks; } -Dictionary nvim__uf_stats(Buffer buffer, Error *err) +Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; -- cgit From f59e1f58a2ed24198fb1653edeffb6ab5ec2bbdb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Nov 2019 23:07:36 -0800 Subject: Lua: mark some functions as "private" Problem: scripts/gen_vimdoc.py gets confused and tries to generate docs for `fn_index` and `func`. Solution: Rename them to be private. --- src/nvim/lua/vim.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 1ebdde99d5..2b4c5486c7 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -261,14 +261,14 @@ end -- vim.fn.{func}(...) -local function fn_index(t, key) - local function func(...) +local function _fn_index(t, key) + local function _fn(...) return vim.call(key, ...) end - t[key] = func - return func + t[key] = _fn + return _fn end -local fn = setmetatable({}, {__index=fn_index}) +local fn = setmetatable({}, {__index=_fn_index}) local module = { _update_package_paths = _update_package_paths, -- cgit From 181486d7e614c1a417ec0f555cdfd25716cb5e38 Mon Sep 17 00:00:00 2001 From: Marco Hinz Date: Mon, 11 Nov 2019 19:25:10 +0100 Subject: api: fix typo in debug function name --- src/nvim/api/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 10250be044..257e02ec4f 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1257,7 +1257,7 @@ Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) return chunks; } -Dictionary nvim__uf_stats(Buffer buffer, Error *err) +Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; -- cgit From e757f4d5363125e242f1e680d1f5bb53e8014b06 Mon Sep 17 00:00:00 2001 From: Timothy C Eichler Date: Sun, 15 Oct 2017 21:51:12 +0200 Subject: namespace: add ns_initialized func --- src/nvim/api/private/helpers.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 2056cb07e3..b54fc9207e 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -10,6 +10,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/handle.h" +#include "nvim/api/vim.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/lua/executor.h" #include "nvim/ascii.h" @@ -1505,3 +1506,13 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) return mappings; } + + +// Is the Namespace in use? +bool ns_initialized(uint64_t ns) +{ + if (ns < 1) { + return false; + } + return ns < (uint64_t)next_namespace_id; +} -- cgit From a9065a50518ef59351f9d0d32041a991a751653f Mon Sep 17 00:00:00 2001 From: timeyyy Date: Wed, 18 Jan 2017 13:20:07 +0100 Subject: nsmarks: initial commit --- src/nvim/api/buffer.c | 217 ++++++- src/nvim/api/private/helpers.c | 117 ++++ src/nvim/api/vim.c | 1 + src/nvim/buffer.c | 3 + src/nvim/buffer_defs.h | 7 + src/nvim/change.c | 18 +- src/nvim/diff.c | 3 +- src/nvim/edit.c | 42 +- src/nvim/ex_cmds.c | 297 +++++++++- src/nvim/lib/kbtree.h | 6 + src/nvim/map.c | 1 + src/nvim/map.h | 1 + src/nvim/mark.c | 25 +- src/nvim/mark_extended.c | 1262 ++++++++++++++++++++++++++++++++++++++++ src/nvim/mark_extended.h | 267 +++++++++ src/nvim/mark_extended_defs.h | 54 ++ src/nvim/misc1.c | 1 - src/nvim/ops.c | 185 +++++- src/nvim/pos.h | 4 + src/nvim/undo.c | 53 +- src/nvim/undo_defs.h | 16 +- 21 files changed, 2523 insertions(+), 57 deletions(-) create mode 100644 src/nvim/mark_extended.c create mode 100644 src/nvim/mark_extended.h create mode 100644 src/nvim/mark_extended_defs.h (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 10250be044..a77c33e891 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -23,7 +23,10 @@ #include "nvim/memory.h" #include "nvim/misc1.h" #include "nvim/ex_cmds.h" +#include "nvim/map_defs.h" +#include "nvim/map.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/fileio.h" #include "nvim/move.h" #include "nvim/syntax.h" @@ -544,7 +547,8 @@ void nvim_buf_set_lines(uint64_t channel_id, (linenr_T)(end - 1), MAXLNUM, (long)extra, - false); + false, + kExtmarkUndo); changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); @@ -999,6 +1003,213 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } +/// Returns position info for a given extmark id +/// +/// @param buffer The buffer handle +/// @param namespace a identifier returned previously with nvim_create_namespace +/// @param id the extmark id +/// @param[out] err Details of an error that may have occurred +/// @return (row, col) tuple or empty list () if extmark id was absent +ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer namespace, + Integer id, Error *err) + FUNC_API_SINCE(6) +{ + Array rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return rv; + } + + if (!ns_initialized((uint64_t)namespace)) { + api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + return rv; + } + + ExtendedMark *extmark = extmark_from_id(buf, + (uint64_t)namespace, + (uint64_t)id); + if (!extmark) { + return rv; + } + ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1)); + ADD(rv, INTEGER_OBJ((Integer)extmark->col-1)); + return rv; +} + +/// List extmarks in a range (inclusive) +/// +/// range ends can be specified as (row, col) tuples, as well as extmark +/// ids in the same namespace. In addition, 0 and -1 works as shorthands +/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be +/// quieried as: +/// +/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1) +/// +/// If end is a lower position than start, then the range will be traversed +/// backwards. This is mostly used with limited amount, to be able to get the +/// first marks prior to a given position. +/// +/// @param buffer The buffer handle +/// @param namespace An id returned previously from nvim_create_namespace +/// @param lower One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param upper One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param amount Maximum number of marks to return or -1 for all marks found +/// /// @param[out] err Details of an error that may have occurred +/// @return [[nsmark_id, row, col], ...] +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, + Object start, Object end, Integer amount, + Error *err) + FUNC_API_SINCE(6) +{ + Array rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return rv; + } + + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + return rv; + } + + if (amount == 0) { + return rv; + } + + + bool reverse = false; + + linenr_T l_lnum; + colnr_T l_col; + if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { + return rv; + } + + linenr_T u_lnum; + colnr_T u_col; + if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { + return rv; + } + + if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) { + reverse = true; + linenr_T tmp_lnum = l_lnum; + l_lnum = u_lnum; + u_lnum = tmp_lnum; + colnr_T tmp_col = l_col; + l_col = u_col; + u_col = tmp_col; + } + + + ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, + u_lnum, u_col, (int64_t)amount, + reverse); + + for (size_t i = 0; i < kv_size(marks); i++) { + Array mark = ARRAY_DICT_INIT; + ExtendedMark *extmark = kv_A(marks, i); + ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id)); + ADD(mark, INTEGER_OBJ(extmark->line->lnum-1)); + ADD(mark, INTEGER_OBJ(extmark->col-1)); + ADD(rv, ARRAY_OBJ(mark)); + } + + kv_destroy(marks); + return rv; +} + +/// Create or update a namespaced mark at a position +/// +/// If an invalid namespace is given, an error will be raised. +/// +/// @param buffer The buffer handle +/// @param ns_id a identifier returned previously with nvim_create_namespace +/// @param id The extmark's id or 0 for next free id +/// @param row The row to set the extmark to. +/// @param col The column to set the extmark to. +/// @param[out] err Details of an error that may have occurred +/// @return the nsmark_id for a new mark, or 0 for an update +Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, + Integer line, Integer col, Error *err) + FUNC_API_SINCE(6) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + return 0; + } + + size_t len = 0; + if (line < 0 || line > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, "line value outside range"); + return 0; + } else if (line < buf->b_ml.ml_line_count) { + len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false)); + } + + if (col == -1) { + col = (Integer)len; + } else if (col < -1 || col > (Integer)len) { + api_set_error(err, kErrorTypeValidation, "col value outside range"); + return 0; + } + + uint64_t id_num; + if (id == 0) { + id_num = extmark_free_id_get(buf, (uint64_t)ns_id); + } else if (id > 0) { + id_num = (uint64_t)id; + } else { + api_set_error(err, kErrorTypeValidation, _("Invalid mark id")); + return 0; + } + + bool new = extmark_set(buf, (uint64_t)ns_id, id_num, + (linenr_T)line+1, + (colnr_T)col+1, + kExtmarkUndo); + + if (new) { + return (Integer)id_num; + } else { + return 0; + } +} + +/// Remove an extmark +/// +/// @param buffer The buffer handle +/// @param ns_id a identifier returned previously with nvim_create_namespace +/// @param id The extmarks's id +/// @param[out] err Details of an error that may have occurred +/// @return true on success, false if no extmarks found +Boolean nvim_buf_del_extmark(Buffer buffer, + Integer ns_id, + Integer id, + Error *err) + FUNC_API_SINCE(6) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return false; + } + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + return false; + } + + return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo); +} + /// Adds a highlight to buffer. /// /// Useful for plugins that dynamically generate highlights to a buffer @@ -1097,6 +1308,10 @@ void nvim_buf_clear_namespace(Buffer buffer, } bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end); + extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id, + (linenr_T)line_start+1, + (linenr_T)line_end, + kExtmarkUndo); } /// Clears highlights and virtual text from namespace and range of lines diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index b54fc9207e..5e9a572a78 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -24,6 +24,7 @@ #include "nvim/eval/typval.h" #include "nvim/map_defs.h" #include "nvim/map.h" +#include "nvim/mark_extended.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" @@ -1507,6 +1508,63 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) return mappings; } +// Returns an extmark given an id or a positional index +// If throw == true then an error will be raised if nothing +// was found +// Returns NULL if something went wrong +ExtendedMark *extmark_from_id_or_pos(Buffer buffer, + Integer namespace, + Object id, + Error *err, + bool throw) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return NULL; + } + + ExtendedMark *extmark = NULL; + if (id.type == kObjectTypeArray) { + if (id.data.array.size != 2) { + api_set_error(err, kErrorTypeValidation, + _("Position must have 2 elements")); + return NULL; + } + linenr_T row = (linenr_T)id.data.array.items[0].data.integer; + colnr_T col = (colnr_T)id.data.array.items[1].data.integer; + if (row < 1 || col < 1) { + if (throw) { + api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0")); + } + return NULL; + } + extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col); + } else if (id.type != kObjectTypeInteger) { + if (throw) { + api_set_error(err, kErrorTypeValidation, + _("Mark id must be an int or [row, col]")); + } + return NULL; + } else if (id.data.integer < 0) { + if (throw) { + api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); + } + return NULL; + } else { + extmark = extmark_from_id(buf, + (uint64_t)namespace, + (uint64_t)id.data.integer); + } + + if (!extmark) { + if (throw) { + api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist")); + } + return NULL; + } + return extmark; +} // Is the Namespace in use? bool ns_initialized(uint64_t ns) @@ -1516,3 +1574,62 @@ bool ns_initialized(uint64_t ns) } return ns < (uint64_t)next_namespace_id; } + +// Extmarks may be queried from position or name or even special names +// in the future such as "cursor". This macro sets the line and col +// to make the extmark functions recognize what's required +// +// *lnum: linenr_T, lnum to be set +// *col: colnr_T, col to be set +bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, + Object obj, linenr_T *lnum, colnr_T *colnr, + Error *err) +{ + // Check if it is mark id + if (obj.type == kObjectTypeInteger) { + Integer id = obj.data.integer; + if (id == 0) { + *lnum = 1; + *colnr = 1; + return true; + } else if (id == -1) { + *lnum = MAXLNUM; + *colnr = MAXCOL; + return true; + } else if (id < 0) { + api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); + return false; + } + + ExtendedMark *extmark = extmark_from_id(buf, (uint64_t)namespace, + (uint64_t)id); + if (extmark) { + *lnum = extmark->line->lnum; + *colnr = extmark->col; + return true; + } else { + api_set_error(err, kErrorTypeValidation, _("No mark with requested id")); + return false; + } + + // Check if it is a position + } else if (obj.type == kObjectTypeArray) { + Array pos = obj.data.array; + if (pos.size != 2 + || pos.items[0].type != kObjectTypeInteger + || pos.items[1].type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + _("Position must have 2 integer elements")); + return false; + } + Integer line = pos.items[0].data.integer; + Integer col = pos.items[1].data.integer; + *lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM); + *colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL); + return true; + } else { + api_set_error(err, kErrorTypeValidation, + _("Position must be a mark id Integer or position Array")); + return false; + } +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e587df5384..10f7dd1a7b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -39,6 +39,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" +#include "nvim/mark_extended.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 1d5aa8ba9b..79f339b3aa 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -53,6 +53,7 @@ #include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -816,6 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) } uc_clear(&buf->b_ucmds); // clear local user commands buf_delete_signs(buf, (char_u *)"*"); // delete any signs + extmark_free_all(buf); // delete any extmarks bufhl_clear_all(buf); // delete any highligts map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs @@ -5496,6 +5498,7 @@ void bufhl_clear_line_range(buf_T *buf, linenr_T line_start, linenr_T line_end) { + // TODO(bfredl): implement kb_itr_interval to jump directly to the first line kbitr_t(bufhl) itr; BufhlLine *l, t = BUFHLLINE_INIT(line_start); if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ca740dea21..2d65d0e160 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -115,6 +115,9 @@ typedef uint16_t disptick_T; // display tick type #include "nvim/os/fs_defs.h" // for FileID #include "nvim/terminal.h" // for Terminal +#include "nvim/lib/kbtree.h" +#include "nvim/mark_extended.h" + /* * The taggy struct is used to store the information about a :tag command. */ @@ -805,6 +808,10 @@ struct file_buffer { kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights + PMap(uint64_t) *b_extmark_ns; // extmark namespaces + kbtree_t(extlines) b_extlines; // extmarks + kvec_t(ExtMarkLine *) b_extmark_move_space; // temp space for extmarks + // array of channel_id:s which have asked to receive updates for this // buffer. kvec_t(uint64_t) update_channels; diff --git a/src/nvim/change.c b/src/nvim/change.c index ba80e71ae6..7558055696 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -17,6 +17,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/memline.h" #include "nvim/misc1.h" #include "nvim/move.h" @@ -372,7 +373,7 @@ void appended_lines_mark(linenr_T lnum, long count) // Skip mark_adjust when adding a line after the last one, there can't // be marks there. But it's still needed in diff mode. if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo); } changed_lines(lnum + 1, 0, lnum + 1, count, true); } @@ -390,7 +391,8 @@ void deleted_lines(linenr_T lnum, long count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false); + mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false, + kExtmarkUndo); changed_lines(lnum, 0, lnum + count, -count, true); } @@ -951,6 +953,9 @@ int open_line( bool did_append; // appended a new line int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting + linenr_T lnum = curwin->w_cursor.lnum; + colnr_T mincol = curwin->w_cursor.col + 1; + // make a copy of the current line so we can mess with it char_u *saved_line = vim_strsave(get_cursor_line_ptr()); @@ -1574,7 +1579,8 @@ int open_line( // be marks there. But still needed in diff mode. if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false, + kExtmarkUndo); } did_append = true; } else { @@ -1663,8 +1669,12 @@ int open_line( if (flags & OPENLINE_MARKFIX) { mark_col_adjust(curwin->w_cursor.lnum, curwin->w_cursor.col + less_cols_off, - 1L, (long)-less_cols, 0); + 1L, (long)-less_cols, 0, kExtmarkNOOP); } + // Always move extmarks - Here we move only the line where the + // cursor is, the previous mark_adjust takes care of the lines after + extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols, + kExtmarkUndo); } else { changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); } diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 31552929dc..313f77474d 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2690,7 +2690,8 @@ void ex_diffgetput(exarg_T *eap) // Adjust marks. This will change the following entries! if (added != 0) { - mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false); + mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false, + kExtmarkUndo); if (curwin->w_cursor.lnum >= lnum) { // Adjust the cursor position if it's in/after the changed // lines. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 93f13c8d3f..25b6502b19 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -28,6 +28,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -1837,6 +1838,13 @@ change_indent ( xfree(new_line); } + + // change_indent seems to bec called twice, this combination only triggers + // once for both calls + if (new_cursor_col - vcol != 0) { + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount, + kExtmarkUndo); + } } /* @@ -5587,6 +5595,9 @@ insertchar ( do_digraph(buf[i-1]); /* may be the start of a digraph */ buf[i] = NUL; ins_str(buf); + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + (colnr_T)(curwin->w_cursor.col + 1), 0, + (long)STRLEN(buf), kExtmarkUndo); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); i = 1; @@ -5597,6 +5608,9 @@ insertchar ( } else { int cc; + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + (colnr_T)(curwin->w_cursor.col + 1), 0, + 1, kExtmarkUndo); if ((cc = utf_char2len(c)) > 1) { char_u buf[MB_MAXBYTES + 1]; @@ -5606,10 +5620,11 @@ insertchar ( AppendCharToRedobuff(c); } else { ins_char(c); - if (flags & INSCHAR_CTRLV) + if (flags & INSCHAR_CTRLV) { redo_literal(c); - else + } else { AppendCharToRedobuff(c); + } } } } @@ -6891,8 +6906,9 @@ static void mb_replace_pop_ins(int cc) for (i = 1; i < n; ++i) buf[i] = replace_pop(); ins_bytes_len(buf, n); - } else + } else { ins_char(cc); + } if (enc_utf8) /* Handle composing chars. */ @@ -8002,9 +8018,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) Insstart_orig.col = curwin->w_cursor.col; } - if (State & VREPLACE_FLAG) + if (State & VREPLACE_FLAG) { ins_char(' '); - else { + } else { ins_str((char_u *)" "); if ((State & REPLACE_FLAG)) replace_push(NUL); @@ -8482,8 +8498,17 @@ static bool ins_tab(void) } else { // otherwise use "tabstop" temp = (int)curbuf->b_p_ts; } + temp -= get_nolist_virtcol() % temp; + // Move extmarks + extmark_col_adjust(curbuf, + curwin->w_cursor.lnum, + curwin->w_cursor.col, + 0, + temp, + kExtmarkUndo); + /* * Insert the first space with ins_char(). It will delete one char in * replace mode. Insert the rest with ins_str(); it will not delete any @@ -8491,12 +8516,13 @@ static bool ins_tab(void) */ ins_char(' '); while (--temp > 0) { - if (State & VREPLACE_FLAG) + if (State & VREPLACE_FLAG) { ins_char(' '); - else { + } else { ins_str((char_u *)" "); - if (State & REPLACE_FLAG) /* no char replaced */ + if (State & REPLACE_FLAG) { // no char replaced replace_push(NUL); + } } } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 1b6d9b50e9..fb97c46117 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -39,6 +39,7 @@ #include "nvim/buffer_updates.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" @@ -105,6 +106,8 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds.c.generated.h" +#include "mark_extended.h" + #endif /// ":ascii" and "ga" implementation @@ -658,10 +661,10 @@ void ex_sort(exarg_T *eap) deleted = (long)(count - (lnum - eap->line2)); if (deleted > 0) { mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted, - false); + false, kExtmarkUndo); msgmore(-deleted); } else if (deleted < 0) { - mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false); + mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo); } if (change_occurred || deleted != 0) { changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true); @@ -874,10 +877,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) * their final destination at the new text position -- webb */ last_line = curbuf->b_ml.ml_line_count; - mark_adjust_nofold(line1, line2, last_line - line2, 0L, true); + mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo); + extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo, + true); changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false); if (dest >= line2) { - mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false); + mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { foldMoveRange(&win->w_folds, line1, line2, dest); @@ -886,7 +891,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) curbuf->b_op_start.lnum = dest - num_lines + 1; curbuf->b_op_end.lnum = dest; } else { - mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false); + mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false, + kExtmarkNoUndo); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); @@ -897,7 +903,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) } curbuf->b_op_start.col = curbuf->b_op_end.col = 0; mark_adjust_nofold(last_line - num_lines + 1, last_line, - -(last_line - dest - extra), 0L, true); + -(last_line - dest - extra), 0L, true, kExtmarkNoUndo); + + u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra); changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); // send update regarding the new lines that were added @@ -1281,12 +1289,14 @@ static void do_filter( if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) { if (read_linecount >= linecount) { // move all marks from old lines to new lines - mark_adjust(line1, line2, linecount, 0L, false); + mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo); } else { // move marks from old lines to new lines, delete marks // that are in deleted lines - mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false); - mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false); + mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false, + kExtmarkUndo); + mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false, + kExtmarkUndo); } } @@ -3214,6 +3224,189 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, return cmd; } +static void extmark_move_regmatch_single(lpos_T startpos, + lpos_T endpos, + linenr_T lnum, + int sublen) +{ + colnr_T mincol; + colnr_T endcol; + colnr_T col_amount; + + mincol = startpos.col + 1; + endcol = endpos.col + 1; + + // There are cases such as :s/^/x/ where this happens + // a delete is simply not required. + if (mincol + 1 <= endcol) { + extmark_col_adjust_delete(curbuf, + lnum, mincol + 1, endcol, kExtmarkUndo, 0); + } + + // Insert, sublen seems to be the value we need but + 1... + col_amount = sublen - 1; + extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo); +} + +static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i) +{ + colnr_T mincol; + linenr_T u_lnum; + mincol = s.startpos.col + 1; + + linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; + colnr_T n_after_newline_in_pat = s.endpos.col; + colnr_T n_before_newline_in_pat = mincol - s.cm_start.col; + long n_after_newline_in_sub; + if (!s.newline_in_sub) { + n_after_newline_in_sub = s.cm_end.col - s.cm_start.col; + } else { + n_after_newline_in_sub = s.cm_end.col; + } + + if (s.newline_in_pat && !s.newline_in_sub) { + // -- Delete Pattern -- + // 1. Move marks in the pattern + mincol = s.startpos.col + 1; + u_lnum = n_u_lnum; + assert(n_u_lnum == u_lnum); + extmark_copy_and_place(curbuf, + s.lnum, mincol, + u_lnum, n_after_newline_in_pat, + s.lnum, mincol, + kExtmarkUndo, true, NULL); + // 2. Move marks on last newline + mincol = mincol - n_before_newline_in_pat; + extmark_col_adjust(curbuf, + u_lnum, + n_after_newline_in_pat + 1, + -s.newline_in_pat, + mincol - n_after_newline_in_pat, + kExtmarkUndo); + // Take care of the lines after + extmark_adjust(curbuf, + u_lnum, + u_lnum, + MAXLNUM, + -s.newline_in_pat, + kExtmarkUndo, + false); + // 1. first insert the text in the substitutaion + extmark_col_adjust(curbuf, + s.lnum, + mincol + 1, + s.newline_in_sub, + n_after_newline_in_sub, + kExtmarkUndo); + + } else { + // The data in sub_obj is as if the substituons above had already taken + // place. For our extmarks they haven't as we work from the bottom of the + // buffer up. Readjust the data. + n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; + n_u_lnum = n_u_lnum - s.lnum_added; + + // adjusted = L - (i-1)N + // where L = lnum value, N= lnum_added and i = iteration + linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added); + linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum; + assert(s.startpos.lnum == 0); + + mincol = s.startpos.col + 1; + u_lnum = n_u_lnum; + + if (!s.newline_in_pat && s.newline_in_sub) { + // -- Delete Pattern -- + // 1. Move marks in the pattern + extmark_col_adjust_delete(curbuf, + a_l_lnum, + mincol + 1, + s.endpos.col + 1, + kExtmarkUndo, + s.eol); + + extmark_adjust(curbuf, + a_u_lnum + 1, + MAXLNUM, + (long)s.newline_in_sub, + 0, + kExtmarkUndo, + false); + // 3. Insert + extmark_col_adjust(curbuf, + a_l_lnum, + mincol, + s.newline_in_sub, + (long)-mincol + 1 + n_after_newline_in_sub, + kExtmarkUndo); + } else if (s.newline_in_pat && s.newline_in_sub) { + if (s.lnum_added >= 0) { + linenr_T u_col = n_after_newline_in_pat == 0 + ? 1 : n_after_newline_in_pat; + extmark_copy_and_place(curbuf, + a_l_lnum, mincol, + a_u_lnum, u_col, + a_l_lnum, mincol, + kExtmarkUndo, true, NULL); + // 2. Move marks on last newline + mincol = mincol - (colnr_T)n_before_newline_in_pat; + extmark_col_adjust(curbuf, + a_u_lnum, + (colnr_T)(n_after_newline_in_pat + 1), + -s.newline_in_pat, + mincol - n_after_newline_in_pat, + kExtmarkUndo); + // TODO(timeyyy): nothing to do here if lnum_added = 0 + extmark_adjust(curbuf, + a_u_lnum + 1, + MAXLNUM, + (long)s.lnum_added, + 0, + kExtmarkUndo, + false); + + extmark_col_adjust(curbuf, + a_l_lnum, + mincol + 1, + s.newline_in_sub, + (long)-mincol + n_after_newline_in_sub, + kExtmarkUndo); + } else { + mincol = s.startpos.col + 1; + a_l_lnum = s.startpos.lnum + 1; + a_u_lnum = s.endpos.lnum + 1; + extmark_copy_and_place(curbuf, + a_l_lnum, mincol, + a_u_lnum, n_after_newline_in_pat, + a_l_lnum, mincol, + kExtmarkUndo, true, NULL); + // 2. Move marks on last newline + mincol = mincol - (colnr_T)n_before_newline_in_pat; + extmark_col_adjust(curbuf, + a_u_lnum, + (colnr_T)(n_after_newline_in_pat + 1), + -s.newline_in_pat, + mincol - n_after_newline_in_pat, + kExtmarkUndo); + extmark_adjust(curbuf, + a_u_lnum, + a_u_lnum, + MAXLNUM, + s.lnum_added, + kExtmarkUndo, + false); + // 3. Insert + extmark_col_adjust(curbuf, + a_l_lnum, + mincol + 1, + s.newline_in_sub, + (long)-mincol + n_after_newline_in_sub, + kExtmarkUndo); + } + } + } +} + /// Perform a substitution from line eap->line1 to line eap->line2 using the /// command pointed to by eap->arg which should be of the form: /// @@ -3260,6 +3453,17 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int save_ma = 0; int save_b_changed = curbuf->b_changed; bool preview = (State & CMDPREVIEW); + extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE; + extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE; + linenr_T no_of_lines_changed = 0; + linenr_T newline_in_pat = 0; + linenr_T newline_in_sub = 0; + + // inccomand tests fail without this check + if (!preview) { + // Requried for Undo to work for nsmarks, + u_save_cursor(); + } if (!global_busy) { sub_nsubs = 0; @@ -3418,6 +3622,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, // Check for a match on each line. // If preview: limit to max('cmdwinheight', viewport). linenr_T line2 = eap->line2; + for (linenr_T lnum = eap->line1; lnum <= line2 && !got_quit && !aborting() && (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh @@ -3876,6 +4081,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, ADJUST_SUB_FIRSTLNUM(); + // Now the trick is to replace CTRL-M chars with a real line // break. This would make it impossible to insert a CTRL-M in // the text. The line break can be avoided by preceding the @@ -3890,7 +4096,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, *p1 = NUL; // truncate up to the CR ml_append(lnum - 1, new_start, (colnr_T)(p1 - new_start + 1), false); - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false, + kExtmarkNOOP); + if (subflags.do_ask) { appended_lines(lnum - 1, 1L); } else { @@ -3917,6 +4125,44 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, current_match.end.lnum = lnum; } + // Adjust extmarks, by delete and then insert + if (!preview) { + newline_in_pat = (regmatch.endpos[0].lnum + - regmatch.startpos[0].lnum); + newline_in_sub = current_match.end.lnum - current_match.start.lnum; + if (newline_in_pat || newline_in_sub) { + ExtmarkSubMulti sub_multi; + no_of_lines_changed = newline_in_sub - newline_in_pat; + + sub_multi.newline_in_pat = newline_in_pat; + sub_multi.newline_in_sub = newline_in_sub; + sub_multi.lnum = lnum; + sub_multi.lnum_added = no_of_lines_changed; + sub_multi.cm_start = current_match.start; + sub_multi.cm_end = current_match.end; + + sub_multi.startpos = regmatch.startpos[0]; + sub_multi.endpos = regmatch.endpos[0]; + sub_multi.eol = extmark_eol_col(curbuf, lnum); + + kv_push(extmark_sub_multi, sub_multi); + // Collect information required for moving extmarks WITHOUT \n, \r + } else { + no_of_lines_changed = 0; + + if (regmatch.startpos[0].col != -1) { + ExtmarkSubSingle sub_single; + sub_single.sublen = sublen; + sub_single.lnum = lnum; + sub_single.startpos = regmatch.startpos[0]; + sub_single.endpos = regmatch.endpos[0]; + + kv_push(extmark_sub_single, sub_single); + } + } + } + + // 4. If subflags.do_all is set, find next match. // Prevent endless loop with patterns that match empty // strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g. @@ -3983,7 +4229,7 @@ skip: ml_delete(lnum, false); } mark_adjust(lnum, lnum + nmatch_tl - 1, - (long)MAXLNUM, -nmatch_tl, false); + (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP); if (subflags.do_ask) { deleted_lines(lnum, nmatch_tl); } @@ -4159,6 +4405,35 @@ skip: } } } + if (newline_in_pat || newline_in_sub) { + long n = (long)kv_size(extmark_sub_multi); + ExtmarkSubMulti sub_multi; + if (no_of_lines_changed < 0) { + for (i = 0; i < n; i++) { + sub_multi = kv_A(extmark_sub_multi, i); + extmark_move_regmatch_multi(sub_multi, i); + } + } else { + // Move extmarks in reverse order to avoid moving marks we just moved... + for (i = 0; i < n; i++) { + sub_multi = kv_Z(extmark_sub_multi, i); + extmark_move_regmatch_multi(sub_multi, n - i); + } + } + kv_destroy(extmark_sub_multi); + } else { + long n = (long)kv_size(extmark_sub_single); + ExtmarkSubSingle sub_single; + for (i = 0; i < n; i++) { + sub_single = kv_Z(extmark_sub_single, i); + extmark_move_regmatch_single(sub_single.startpos, + sub_single.endpos, + sub_single.lnum, + sub_single.sublen); + } + + kv_destroy(extmark_sub_single); + } kv_destroy(preview_lines.subresults); diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h index 33aeff1d89..bef37f8ba9 100644 --- a/src/nvim/lib/kbtree.h +++ b/src/nvim/lib/kbtree.h @@ -25,6 +25,12 @@ * SUCH DAMAGE. */ +// Gotchas +// ------- +// +// if you delete from a kbtree while iterating over it you must use +// kb_del_itr and not kb_del otherwise the iterator might point to freed memory. + #ifndef NVIM_LIB_KBTREE_H #define NVIM_LIB_KBTREE_H diff --git a/src/nvim/map.c b/src/nvim/map.c index cdade5ee71..a4cf5304e0 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -184,6 +184,7 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) #define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) +MAP_IMPL(uint64_t, cstr_t, DEFAULT_INITIALIZER) /// Deletes a key:value pair from a string:pointer map, and frees the diff --git a/src/nvim/map.h b/src/nvim/map.h index 75ab64cca4..073be6822a 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -42,6 +42,7 @@ MAP_DECLS(handle_T, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) +MAP_DECLS(uint64_t, cstr_t) #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e8f1651a6e..454e9e46f4 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -905,9 +905,10 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after, - bool end_temp) + bool end_temp, + ExtmarkOp op) { - mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp); + mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op); } // mark_adjust_nofold() does the same as mark_adjust() but without adjusting @@ -916,14 +917,16 @@ void mark_adjust(linenr_T line1, // calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, // for an example of why this may be necessary, see do_move(). void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount, - long amount_after, bool end_temp) + long amount_after, bool end_temp, + ExtmarkOp op) { - mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp); + mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op); } static void mark_adjust_internal(linenr_T line1, linenr_T line2, long amount, long amount_after, - bool adjust_folds, bool end_temp) + bool adjust_folds, bool end_temp, + ExtmarkOp op) { int i; int fnum = curbuf->b_fnum; @@ -979,6 +982,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, sign_mark_adjust(line1, line2, amount, amount_after); bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp); + if (op != kExtmarkNOOP) { + extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp); + } } /* previous context mark */ @@ -1090,7 +1096,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, // cursor is inside them. void mark_col_adjust( linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount, - int spaces_removed) + int spaces_removed, ExtmarkOp op) { int i; int fnum = curbuf->b_fnum; @@ -1110,6 +1116,13 @@ void mark_col_adjust( col_adjust(&(namedfm[i].fmark.mark)); } + // Extmarks + if (op != kExtmarkNOOP) { + // TODO(timeyyy): consider spaces_removed? (behave like a delete) + extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount, + kExtmarkUndo); + } + /* last Insert position */ col_adjust(&(curbuf->b_last_insert.mark)); diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c new file mode 100644 index 0000000000..cb5f45d88c --- /dev/null +++ b/src/nvim/mark_extended.c @@ -0,0 +1,1262 @@ +// 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 + +// Implements extended marks for plugins +// Each Mark exists in a btree of lines containing btrees of columns. +// +// The btree provides efficent range lookups. +// A map of pointers to the marks is used for fast lookup by mark id. +// +// Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or +// extmark_col_adjust_delete which are based on col_adjust and mark_adjust from +// mark.c +// +// Undo/Redo of marks is implemented by storing the call arguments to +// extmark_col_adjust or extmark_adjust. The list of arguments +// is applied in extmark_apply_undo. The only case where we have to +// copy extmarks is for the area being effected by a delete. +// +// Marks live in namespaces that allow plugins/users to segregate marks +// from other users, namespaces have to be initialized before usage +// +// For possible ideas for efficency improvements see: +// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html +// Other implementations exist in gtk and tk toolkits. +// +// Deleting marks only happens explicitly extmark_del, deleteing over a +// range of marks will only move the marks. +// +// deleting on a mark will leave it in that same position unless it is on +// the eol of the line. + +#include +#include "nvim/vim.h" +#include "charset.h" +#include "nvim/mark_extended.h" +#include "nvim/memline.h" +#include "nvim/pos.h" +#include "nvim/globals.h" +#include "nvim/map.h" +#include "nvim/lib/kbtree.h" +#include "nvim/undo.h" +#include "nvim/buffer.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "mark_extended.c.generated.h" +#endif + + +/// Create or update an extmark +/// +/// must not be used during iteration! +/// @returns whether a new mark was created +int extmark_set(buf_T *buf, + uint64_t ns, + uint64_t id, + linenr_T lnum, + colnr_T col, + ExtmarkOp op) +{ + ExtendedMark *extmark = extmark_from_id(buf, ns, id); + if (!extmark) { + extmark_create(buf, ns, id, lnum, col, op); + return true; + } else { + ExtMarkLine *extline = extmark->line; + extmark_update(extmark, buf, ns, id, lnum, col, op, NULL); + if (kb_size(&extline->items) == 0) { + kb_del(extlines, &buf->b_extlines, extline); + extline_free(extline); + } + return false; + } +} + +// Remove an extmark +// Returns 0 on missing id +int extmark_del(buf_T *buf, + uint64_t ns, + uint64_t id, + ExtmarkOp op) +{ + ExtendedMark *extmark = extmark_from_id(buf, ns, id); + if (!extmark) { + return 0; + } + return extmark_delete(extmark, buf, ns, id, op); +} + +// Free extmarks in a ns between lines +// if ns = 0, it means clear all namespaces +void extmark_clear(buf_T *buf, + uint64_t ns, + linenr_T l_lnum, + linenr_T u_lnum, + ExtmarkOp undo) +{ + if (!buf->b_extmark_ns) { + return; + } + + bool marks_cleared = false; + if (undo == kExtmarkUndo) { + // Copy marks that would be effected by clear + u_extmark_copy(buf, ns, l_lnum, 0, u_lnum, MAXCOL); + } + + bool all_ns = ns == 0 ? true : false; + ExtmarkNs *ns_obj; + if (!all_ns) { + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + if (!ns_obj) { + // nothing to do + return; + } + } + + FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { + FOR_ALL_EXTMARKS_IN_LINE(extline->items, 0, MAXCOL, { + if (extmark->ns_id == ns || all_ns) { + marks_cleared = true; + if (all_ns) { + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, extmark->ns_id); + } else { + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + } + pmap_del(uint64_t)(ns_obj->map, extmark->mark_id); + kb_del_itr(markitems, &extline->items, &mitr); + } + }); + if (kb_size(&extline->items) == 0) { + kb_del_itr(extlines, &buf->b_extlines, &itr); + extline_free(extline); + } + }); + + // Record the undo for the actual move + if (marks_cleared && undo == kExtmarkUndo) { + u_extmark_clear(buf, ns, l_lnum, u_lnum); + } +} + +// Returns the position of marks between a range, +// marks found at the start or end index will be included, +// if upper_lnum or upper_col are negative the buffer +// will be searched to the start, or end +// dir can be set to control the order of the array +// amount = amount of marks to find or -1 for all +ExtmarkArray extmark_get(buf_T *buf, + uint64_t ns, + linenr_T l_lnum, + colnr_T l_col, + linenr_T u_lnum, + colnr_T u_col, + int64_t amount, + bool reverse) +{ + ExtmarkArray array = KV_INITIAL_VALUE; + // Find all the marks + if (!reverse) { + FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, { + if (extmark->ns_id == ns) { + kv_push(array, extmark); + if (kv_size(array) == (size_t)amount) { + return array; + } + } + }) + } else { + FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, { + if (extmark->ns_id == ns) { + kv_push(array, extmark); + if (kv_size(array) == (size_t)amount) { + return array; + } + } + }) + } + return array; +} + +static void extmark_create(buf_T *buf, + uint64_t ns, + uint64_t id, + linenr_T lnum, + colnr_T col, + ExtmarkOp op) +{ + if (!buf->b_extmark_ns) { + buf->b_extmark_ns = pmap_new(uint64_t)(); + } + ExtmarkNs *ns_obj = NULL; + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + // Initialize a new namespace for this buffer + if (!ns_obj) { + ns_obj = xmalloc(sizeof(ExtmarkNs)); + ns_obj->map = pmap_new(uint64_t)(); + pmap_put(uint64_t)(buf->b_extmark_ns, ns, ns_obj); + } + + // Create or get a line + ExtMarkLine *extline = extline_ref(buf, lnum, true); + // Create and put mark on the line + extmark_put(col, id, extline, ns); + + // Marks do not have stable address so we have to look them up + // by using the line instead of the mark + pmap_put(uint64_t)(ns_obj->map, id, extline); + if (op != kExtmarkNoUndo) { + u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet); + } + + // Set a free id so extmark_free_id_get works + extmark_free_id_set(ns_obj, id); +} + +// update the position of an extmark +// to update while iterating pass the markitems itr +static void extmark_update(ExtendedMark *extmark, + buf_T *buf, + uint64_t ns, + uint64_t id, + linenr_T lnum, + colnr_T col, + ExtmarkOp op, + kbitr_t(markitems) *mitr) +{ + assert(op != kExtmarkNOOP); + if (op != kExtmarkNoUndo) { + u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col, + lnum, col); + } + ExtMarkLine *old_line = extmark->line; + // Move the mark to a new line and update column + if (old_line->lnum != lnum) { + ExtMarkLine *ref_line = extline_ref(buf, lnum, true); + extmark_put(col, id, ref_line, ns); + // Update the hashmap + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + pmap_put(uint64_t)(ns_obj->map, id, ref_line); + // Delete old mark + if (mitr != NULL) { + kb_del_itr(markitems, &(old_line->items), mitr); + } else { + kb_del(markitems, &old_line->items, *extmark); + } + // Just update the column + } else { + if (mitr != NULL) { + // The btree stays organized during iteration with kbitr_t + extmark->col = col; + } else { + // Keep the btree in order + kb_del(markitems, &old_line->items, *extmark); + extmark_put(col, id, old_line, ns); + } + } +} + +static int extmark_delete(ExtendedMark *extmark, + buf_T *buf, + uint64_t ns, + uint64_t id, + ExtmarkOp op) +{ + if (op != kExtmarkNoUndo) { + u_extmark_set(buf, ns, id, extmark->line->lnum, extmark->col, + kExtmarkDel); + } + + // Remove our key from the namespace + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + pmap_del(uint64_t)(ns_obj->map, id); + + // Remove the mark mark from the line + ExtMarkLine *extline = extmark->line; + kb_del(markitems, &extline->items, *extmark); + // Remove the line if there are no more marks in the line + if (kb_size(&extline->items) == 0) { + kb_del(extlines, &buf->b_extlines, extline); + extline_free(extline); + } + return true; +} + +// Lookup an extmark by id +ExtendedMark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) +{ + if (!buf->b_extmark_ns) { + return NULL; + } + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + if (!ns_obj || !kh_size(ns_obj->map->table)) { + return NULL; + } + ExtMarkLine *extline = pmap_get(uint64_t)(ns_obj->map, id); + if (!extline) { + return NULL; + } + + FOR_ALL_EXTMARKS_IN_LINE(extline->items, 0, MAXCOL, { + if (extmark->ns_id == ns + && extmark->mark_id == id) { + return extmark; + } + }) + return NULL; +} + +// Lookup an extmark by position +ExtendedMark *extmark_from_pos(buf_T *buf, + uint64_t ns, linenr_T lnum, colnr_T col) +{ + if (!buf->b_extmark_ns) { + return NULL; + } + FOR_ALL_EXTMARKS(buf, ns, lnum, col, lnum, col, { + if (extmark->ns_id == ns) { + if (extmark->col == col) { + return extmark; + } + } + }) + return NULL; +} + +// Returns an avaliable id in a namespace +uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns) +{ + if (!buf->b_extmark_ns) { + return 1; + } + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + if (!ns_obj) { + return 1; + } + return ns_obj->free_id; +} + +// Set the next free id in a namesapce +static void extmark_free_id_set(ExtmarkNs *ns_obj, uint64_t id) +{ + // Simply Heurstic, the largest id + 1 + ns_obj->free_id = id + 1; +} + +// free extmarks from the buffer +void extmark_free_all(buf_T *buf) +{ + if (!buf->b_extmark_ns) { + return; + } + + uint64_t ns; + ExtmarkNs *ns_obj; + + FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, { + kb_del_itr(extlines, &buf->b_extlines, &itr); + extline_free(extline); + }) + + map_foreach(buf->b_extmark_ns, ns, ns_obj, { + (void)ns; + pmap_free(uint64_t)(ns_obj->map); + xfree(ns_obj); + }); + + pmap_free(uint64_t)(buf->b_extmark_ns); + + // k?_init called to set pointers to NULL + kb_destroy(extlines, (&buf->b_extlines)); + kb_init(&buf->b_extlines); + + kv_destroy(buf->b_extmark_move_space); + kv_init(buf->b_extmark_move_space); +} + + +// Save info for undo/redo of set marks +static void u_extmark_set(buf_T *buf, + uint64_t ns, + uint64_t id, + linenr_T lnum, + colnr_T col, + UndoObjectType undo_type) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkSet set; + set.ns_id = ns; + set.mark_id = id; + set.lnum = lnum; + set.col = col; + + ExtmarkUndoObject undo = { .type = undo_type, + .data.set = set }; + + kv_push(uhp->uh_extmark, undo); +} + +// Save info for undo/redo of deleted marks +static void u_extmark_update(buf_T *buf, + uint64_t ns, + uint64_t id, + linenr_T old_lnum, + colnr_T old_col, + linenr_T lnum, + colnr_T col) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkUpdate update; + update.ns_id = ns; + update.mark_id = id; + update.old_lnum = old_lnum; + update.old_col = old_col; + update.lnum = lnum; + update.col = col; + + ExtmarkUndoObject undo = { .type = kExtmarkUpdate, + .data.update = update }; + kv_push(uhp->uh_extmark, undo); +} + +// Hueristic works only for when the user is typing in insert mode +// - Instead of 1 undo object for each char inserted, +// we create 1 undo objet for all text inserted before the user hits esc +// Return True if we compacted else False +static bool u_compact_col_adjust(buf_T *buf, + linenr_T lnum, + colnr_T mincol, + long lnum_amount, + long col_amount) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return false; + } + + if (kv_size(uhp->uh_extmark) < 1) { + return false; + } + // Check the last action + ExtmarkUndoObject object = kv_last(uhp->uh_extmark); + + if (object.type != kColAdjust) { + return false; + } + ColAdjust undo = object.data.col_adjust; + bool compactable = false; + + if (!undo.lnum_amount && !lnum_amount) { + if (undo.lnum == lnum) { + if ((undo.mincol + undo.col_amount) >= mincol) { + compactable = true; + } } } + + if (!compactable) { + return false; + } + + undo.col_amount = undo.col_amount + col_amount; + ExtmarkUndoObject new_undo = { .type = kColAdjust, + .data.col_adjust = undo }; + kv_last(uhp->uh_extmark) = new_undo; + return true; +} + +// Save col_adjust info so we can undo/redo +void u_extmark_col_adjust(buf_T *buf, + linenr_T lnum, + colnr_T mincol, + long lnum_amount, + long col_amount) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + if (!u_compact_col_adjust(buf, lnum, mincol, lnum_amount, col_amount)) { + ColAdjust col_adjust; + col_adjust.lnum = lnum; + col_adjust.mincol = mincol; + col_adjust.lnum_amount = lnum_amount; + col_adjust.col_amount = col_amount; + + ExtmarkUndoObject undo = { .type = kColAdjust, + .data.col_adjust = col_adjust }; + + kv_push(uhp->uh_extmark, undo); + } +} + +// Save col_adjust_delete info so we can undo/redo +void u_extmark_col_adjust_delete(buf_T *buf, + linenr_T lnum, + colnr_T mincol, + colnr_T endcol, + int eol) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + ColAdjustDelete col_adjust_delete; + col_adjust_delete.lnum = lnum; + col_adjust_delete.mincol = mincol; + col_adjust_delete.endcol = endcol; + col_adjust_delete.eol = eol; + + ExtmarkUndoObject undo = { .type = kColAdjustDelete, + .data.col_adjust_delete = col_adjust_delete }; + + kv_push(uhp->uh_extmark, undo); +} + +// Save adjust info so we can undo/redo +static void u_extmark_adjust(buf_T * buf, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + Adjust adjust; + adjust.line1 = line1; + adjust.line2 = line2; + adjust.amount = amount; + adjust.amount_after = amount_after; + + ExtmarkUndoObject undo = { .type = kLineAdjust, + .data.adjust = adjust }; + + kv_push(uhp->uh_extmark, undo); +} + +// save info to undo/redo a :move +void u_extmark_move(buf_T *buf, + linenr_T line1, + linenr_T line2, + linenr_T last_line, + linenr_T dest, + linenr_T num_lines, + linenr_T extra) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + AdjustMove move; + move.line1 = line1; + move.line2 = line2; + move.last_line = last_line; + move.dest = dest; + move.num_lines = num_lines; + move.extra = extra; + + ExtmarkUndoObject undo = { .type = kAdjustMove, + .data.move = move }; + + kv_push(uhp->uh_extmark, undo); +} + +// copy extmarks data between range, useful when we cannot simply reverse +// the operation. This will do nothing on redo, enforces correct position when +// undo. +// if ns = 0, it means copy all namespaces +void u_extmark_copy(buf_T *buf, + uint64_t ns, + linenr_T l_lnum, + colnr_T l_col, + linenr_T u_lnum, + colnr_T u_col) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + bool all_ns = ns == 0 ? true : false; + + ExtmarkCopy copy; + ExtmarkUndoObject undo; + FOR_ALL_EXTMARKS(buf, 1, l_lnum, l_col, u_lnum, u_col, { + if (all_ns || extmark->ns_id == ns) { + copy.ns_id = extmark->ns_id; + copy.mark_id = extmark->mark_id; + copy.lnum = extmark->line->lnum; + copy.col = extmark->col; + + undo.data.copy = copy; + undo.type = kExtmarkCopy; + kv_push(uhp->uh_extmark, undo); + } + }); +} + +void u_extmark_copy_place(buf_T *buf, + linenr_T l_lnum, + colnr_T l_col, + linenr_T u_lnum, + colnr_T u_col, + linenr_T p_lnum, + colnr_T p_col) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkCopyPlace copy_place; + copy_place.l_lnum = l_lnum; + copy_place.l_col = l_col; + copy_place.u_lnum = u_lnum; + copy_place.u_col = u_col; + copy_place.p_lnum = p_lnum; + copy_place.p_col = p_col; + + ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace, + .data.copy_place = copy_place }; + + kv_push(uhp->uh_extmark, undo); +} + +// Save info for undo/redo of extmark_clear +static void u_extmark_clear(buf_T *buf, + uint64_t ns, + linenr_T l_lnum, + linenr_T u_lnum) +{ + u_header_T *uhp = force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkClear clear; + clear.ns_id = ns; + clear.l_lnum = l_lnum; + clear.u_lnum = u_lnum; + + ExtmarkUndoObject undo = { .type = kExtmarkClear, + .data.clear = clear }; + kv_push(uhp->uh_extmark, undo); +} + +// undo or redo an extmark operation +void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) +{ + linenr_T lnum; + colnr_T mincol; + long lnum_amount; + long col_amount; + linenr_T line1; + linenr_T line2; + long amount; + long amount_after; + + // use extmark_col_adjust + if (undo_info.type == kColAdjust) { + // Undo + if (undo) { + lnum = (undo_info.data.col_adjust.lnum + + undo_info.data.col_adjust.lnum_amount); + lnum_amount = -undo_info.data.col_adjust.lnum_amount; + col_amount = -undo_info.data.col_adjust.col_amount; + mincol = (undo_info.data.col_adjust.mincol + + (colnr_T)undo_info.data.col_adjust.col_amount); + // Redo + } else { + lnum = undo_info.data.col_adjust.lnum; + col_amount = undo_info.data.col_adjust.col_amount; + lnum_amount = undo_info.data.col_adjust.lnum_amount; + mincol = undo_info.data.col_adjust.mincol; + } + extmark_col_adjust(curbuf, + lnum, mincol, lnum_amount, col_amount, kExtmarkNoUndo); + // use extmark_col_adjust_delete + } else if (undo_info.type == kColAdjustDelete) { + if (undo) { + mincol = undo_info.data.col_adjust_delete.mincol; + col_amount = (undo_info.data.col_adjust_delete.endcol + - undo_info.data.col_adjust_delete.mincol) + 1; + extmark_col_adjust(curbuf, + undo_info.data.col_adjust_delete.lnum, + mincol, + 0, + col_amount, + kExtmarkNoUndo); + // Redo + } else { + extmark_col_adjust_delete(curbuf, + undo_info.data.col_adjust_delete.lnum, + undo_info.data.col_adjust_delete.mincol, + undo_info.data.col_adjust_delete.endcol, + kExtmarkNoUndo, + undo_info.data.col_adjust_delete.eol); + } + // use extmark_adjust + } else if (undo_info.type == kLineAdjust) { + if (undo) { + // Undo - call signature type one - insert now + if (undo_info.data.adjust.amount == MAXLNUM) { + line1 = undo_info.data.adjust.line1; + line2 = MAXLNUM; + amount = -undo_info.data.adjust.amount_after; + amount_after = 0; + // Undo - call singature type two - delete now + } else if (undo_info.data.adjust.line2 == MAXLNUM) { + line1 = undo_info.data.adjust.line1; + line2 = undo_info.data.adjust.line2; + amount = -undo_info.data.adjust.amount; + amount_after = undo_info.data.adjust.amount_after; + // Undo - call signature three - move lines + } else { + line1 = (undo_info.data.adjust.line1 + + undo_info.data.adjust.amount); + line2 = (undo_info.data.adjust.line2 + + undo_info.data.adjust.amount); + amount = -undo_info.data.adjust.amount; + amount_after = -undo_info.data.adjust.amount_after; + } + // redo + } else { + line1 = undo_info.data.adjust.line1; + line2 = undo_info.data.adjust.line2; + amount = undo_info.data.adjust.amount; + amount_after = undo_info.data.adjust.amount_after; + } + extmark_adjust(curbuf, + line1, line2, amount, amount_after, kExtmarkNoUndo, false); + // kExtmarkCopy + } else if (undo_info.type == kExtmarkCopy) { + // Redo should be handled by kColAdjustDelete or kExtmarkCopyPlace + if (undo) { + extmark_set(curbuf, + undo_info.data.copy.ns_id, + undo_info.data.copy.mark_id, + undo_info.data.copy.lnum, + undo_info.data.copy.col, + kExtmarkNoUndo); + } + // uses extmark_copy_and_place + } else if (undo_info.type == kExtmarkCopyPlace) { + // Redo, undo is handle by kExtmarkCopy + if (!undo) { + extmark_copy_and_place(curbuf, + undo_info.data.copy_place.l_lnum, + undo_info.data.copy_place.l_col, + undo_info.data.copy_place.u_lnum, + undo_info.data.copy_place.u_col, + undo_info.data.copy_place.p_lnum, + undo_info.data.copy_place.p_col, + kExtmarkNoUndo, true, NULL); + } + // kExtmarkClear + } else if (undo_info.type == kExtmarkClear) { + // Redo, undo is handle by kExtmarkCopy + if (!undo) { + extmark_clear(curbuf, + undo_info.data.clear.ns_id, + undo_info.data.clear.l_lnum, + undo_info.data.clear.u_lnum, + kExtmarkNoUndo); + } + // kAdjustMove + } else if (undo_info.type == kAdjustMove) { + apply_undo_move(undo_info, undo); + // extmark_set + } else if (undo_info.type == kExtmarkSet) { + if (undo) { + extmark_del(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + kExtmarkNoUndo); + // Redo + } else { + extmark_set(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + undo_info.data.set.lnum, + undo_info.data.set.col, + kExtmarkNoUndo); + } + // extmark_set into update + } else if (undo_info.type == kExtmarkUpdate) { + if (undo) { + extmark_set(curbuf, + undo_info.data.update.ns_id, + undo_info.data.update.mark_id, + undo_info.data.update.old_lnum, + undo_info.data.update.old_col, + kExtmarkNoUndo); + // Redo + } else { + extmark_set(curbuf, + undo_info.data.update.ns_id, + undo_info.data.update.mark_id, + undo_info.data.update.lnum, + undo_info.data.update.col, + kExtmarkNoUndo); + } + // extmark_del + } else if (undo_info.type == kExtmarkDel) { + if (undo) { + extmark_set(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + undo_info.data.set.lnum, + undo_info.data.set.col, + kExtmarkNoUndo); + // Redo + } else { + extmark_del(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + kExtmarkNoUndo); + } + } +} + +// undo/redo an kExtmarkMove operation +static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo) +{ + // 3 calls are required , see comment in function do_move (ex_cmds.c) + linenr_T line1 = undo_info.data.move.line1; + linenr_T line2 = undo_info.data.move.line2; + linenr_T last_line = undo_info.data.move.last_line; + linenr_T dest = undo_info.data.move.dest; + linenr_T num_lines = undo_info.data.move.num_lines; + linenr_T extra = undo_info.data.move.extra; + + if (undo) { + if (dest >= line2) { + extmark_adjust(curbuf, + dest - num_lines + 1, + dest, + last_line - dest + num_lines - 1, + 0L, + kExtmarkNoUndo, + true); + extmark_adjust(curbuf, + dest - line2, + dest - line1, + dest - line2, + 0L, + kExtmarkNoUndo, + false); + } else { + extmark_adjust(curbuf, + line1-num_lines, + line2-num_lines, + last_line - (line1-num_lines), + 0L, + kExtmarkNoUndo, + true); + extmark_adjust(curbuf, + (line1-num_lines) + 1, + (line2-num_lines) + 1, + -num_lines, + 0L, + kExtmarkNoUndo, + false); + } + extmark_adjust(curbuf, + last_line, + last_line + num_lines - 1, + line1 - last_line, + 0L, + kExtmarkNoUndo, + true); + // redo + } else { + extmark_adjust(curbuf, + line1, + line2, + last_line - line2, + 0L, + kExtmarkNoUndo, + true); + if (dest >= line2) { + extmark_adjust(curbuf, + line2 + 1, + dest, + -num_lines, + 0L, + kExtmarkNoUndo, + false); + } else { + extmark_adjust(curbuf, + dest + 1, + line1 - 1, + num_lines, + 0L, + kExtmarkNoUndo, + false); + } + extmark_adjust(curbuf, + last_line - num_lines + 1, + last_line, + -(last_line - dest - extra), + 0L, + kExtmarkNoUndo, + true); + } +} + +// for anything other than deletes +// Return, desired col amount where the adjustment should take place +// (not taking) eol into account +static long update_constantly(colnr_T _, colnr_T __, long col_amount) +{ + return col_amount; +} + +// for deletes, +// Return, desired col amount where the adjustment should take place +// (not taking) eol into account +static long update_variably(colnr_T mincol, colnr_T current, long endcol) +{ + colnr_T start_effected_range = mincol - 1; + long col_amount; + // When mark inside range + if (current < endcol) { + col_amount = -(current - start_effected_range); + // Mark outside of range + } else { + // -1 because a delete of width 0 should still move marks + col_amount = -(endcol - mincol) - 1; + } + return col_amount; +} + + +/// Get the column position for EOL on a line +/// +/// If the lnum doesn't exist, returns 0 +colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum) +{ + if (lnum > buf->b_ml.ml_line_count) { + return 0; + } + return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1; +} + + +// Adjust columns and rows for extmarks +// based off mark_col_adjust in mark.c +// returns true if something was moved otherwise false +static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum, + colnr_T mincol, long lnum_amount, + long (*calc_amount)(colnr_T, colnr_T, long), + long func_arg) +{ + bool marks_exist = false; + colnr_T *cp; + long col_amount; + + ExtMarkLine *extline = extline_ref(buf, lnum, false); + if (!extline) { + return false; + } + + FOR_ALL_EXTMARKS_IN_LINE(extline->items, mincol, MAXCOL, { + marks_exist = true; + cp = &(extmark->col); + + col_amount = (*calc_amount)(mincol, *cp, func_arg); + // No update required for this guy + if (col_amount == 0 && lnum_amount == 0) { + continue; + } + + // Set mark to start of line + if (col_amount < 0 + && *cp <= (colnr_T)-col_amount) { // TODO(timeyyy): does mark.c + // need this line? + extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, + extline->lnum + lnum_amount, + 1, kExtmarkNoUndo, &mitr); + // Update the mark + } else { + // Note: The undo is handled by u_extmark_col_adjust, NoUndo here + extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, + extline->lnum + lnum_amount, + *cp + (colnr_T)col_amount, kExtmarkNoUndo, &mitr); + } + }) + + if (kb_size(&extline->items) == 0) { + kb_del(extlines, &buf->b_extlines, extline); + extline_free(extline); + } + + return marks_exist; +} + +// Adjust columns and rows for extmarks +// +// based off mark_col_adjust in mark.c +// use extmark_col_adjust_impl to move columns by inserting +// Doesn't take the eol into consideration (possible to put marks in invalid +// positions) +void extmark_col_adjust(buf_T *buf, linenr_T lnum, + colnr_T mincol, long lnum_amount, + long col_amount, ExtmarkOp undo) +{ + assert(col_amount > INT_MIN && col_amount <= INT_MAX); + + bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount, + &update_constantly, col_amount); + + if (undo == kExtmarkUndo && marks_moved) { + u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount); + } +} + +// Adjust marks after a delete on a line +// +// Automatically readjusts to take the eol into account +// TODO(timeyyy): change mincol to be for the mark to be copied, not moved +// +// @param mincol First column that needs to be moved (start of delete range) + 1 +// @param endcol Last column which needs to be copied (end of delete range + 1) +void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, + colnr_T mincol, colnr_T endcol, + ExtmarkOp undo, int _eol) +{ + colnr_T start_effected_range = mincol; + // TODO(timeyyy): understand why this has to be uncommented out for the + // tests to pass.. shouldn't this be required? + // why is this even being called if it's not neeed? + + // Some tests in the vim suite were hitting the assertion that was here. + // functional/ui/mouse_spec/ + // We don't know what to do in this case so just bail! + + // if (start_effected_range <= endcol) { + // return; + // } + + bool marks_moved; + if (undo == kExtmarkUndo) { + // Copy marks that would be effected by delete + // -1 because we need to restore if a mark existed at the start pos + u_extmark_copy(buf, 0, lnum, start_effected_range, lnum, endcol); + } + + marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0, + &update_variably, (long)endcol); + + // Deletes at the end of the line have different behaviour than the normal + // case when deleted. + // Cleanup any marks that are floating beyond the end of line. + // we allow this to be passed in as well because the buffer may have already + // been mutated. + int eol = _eol; + if (!eol) { + eol = extmark_eol_col(buf, lnum); + } + FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, { + extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, + extline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr); + }) + + // Record the undo for the actual move + if (marks_moved && undo == kExtmarkUndo) { + u_extmark_col_adjust_delete(buf, lnum, mincol, endcol, eol); + } +} + +// Adjust extmark row for inserted/deleted rows (columns stay fixed). +void extmark_adjust(buf_T *buf, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after, + ExtmarkOp undo, + bool end_temp) +{ + ExtMarkLine *_extline; + + // btree needs to be kept ordered to work, so far only :move requires this + // 2nd call with end_temp = unpack the lines from the temp position + if (end_temp && amount < 0) { + for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) { + _extline = kv_A(buf->b_extmark_move_space, i); + _extline->lnum += amount; + kb_put(extlines, &buf->b_extlines, _extline); + } + kv_size(buf->b_extmark_move_space) = 0; + return; + } + + bool marks_exist = false; + linenr_T *lp; + + linenr_T adj_start = line1; + if (amount == MAXLNUM) { + // Careful! marks from deleted region can end up on en extisting extline + // that is goinig to be adjusted to the target position. + linenr_T join_num = line1 - amount_after; + ExtMarkLine *joinline = join_num > line2 \ + ? extline_ref(buf, join_num, false) : NULL; + // extmark_adjust is already redoable, the copy should only be for undo + marks_exist = extmark_copy_and_place(curbuf, + line1, 1, + line2, MAXCOL, + line1, 1, + kExtmarkUndoNoRedo, true, joinline); + adj_start = line2+1; + } + FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, { + marks_exist = true; + lp = &(extline->lnum); + if (*lp <= line2) { + // 1st call with end_temp = true, store the lines in a temp position + if (end_temp && amount > 0) { + kb_del_itr_extlines(&buf->b_extlines, &itr); + kv_push(buf->b_extmark_move_space, extline); + } + + *lp += amount; + } else if (amount_after && *lp > line2) { + *lp += amount_after; + } + }) + + if (undo == kExtmarkUndo && marks_exist) { + u_extmark_adjust(buf, line1, line2, amount, amount_after); + } +} + +/// Range points to copy +/// +/// if part of a larger iteration we can't delete, then the caller +/// must check for empty lines. +bool extmark_copy_and_place(buf_T *buf, + linenr_T l_lnum, + colnr_T l_col, + linenr_T u_lnum, + colnr_T u_col, + linenr_T p_lnum, + colnr_T p_col, + ExtmarkOp undo, + bool delete, + ExtMarkLine *destline) + +{ + bool marks_moved = false; + if (undo == kExtmarkUndo || undo == kExtmarkUndoNoRedo) { + // Copy marks that would be effected by delete + u_extmark_copy(buf, 0, l_lnum, l_col, u_lnum, u_col); + } + + // Move extmarks to their final position + // Careful: if we move items within the same line, we might change order of + // marks within the same extline. Too keep it simple, first delete all items + // from the extline and put them back in the right order. + FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { + kvec_t(ExtendedMark) temp_space = KV_INITIAL_VALUE; + bool same_line = extline == destline; + FOR_ALL_EXTMARKS_IN_LINE(extline->items, + (extline->lnum > l_lnum) ? 0 : l_col, + (extline->lnum < u_lnum) ? MAXCOL : u_col, { + if (!destline) { + destline = extline_ref(buf, p_lnum, true); + same_line = extline == destline; + } + marks_moved = true; + if (!same_line) { + extmark_put(p_col, extmark->mark_id, destline, extmark->ns_id); + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, + extmark->ns_id); + pmap_put(uint64_t)(ns_obj->map, extmark->mark_id, destline); + } else { + kv_push(temp_space, *extmark); + } + // Delete old mark + kb_del_itr(markitems, &extline->items, &mitr); + }) + if (same_line) { + for (size_t i = 0; i < kv_size(temp_space); i++) { + ExtendedMark mark = kv_A(temp_space, i); + extmark_put(p_col, mark.mark_id, extline, mark.ns_id); + } + kv_destroy(temp_space); + } else if (delete && kb_size(&extline->items) == 0) { + kb_del_itr(extlines, &buf->b_extlines, &itr); + extline_free(extline); + } + }) + + // Record the undo for the actual move + if (marks_moved && undo == kExtmarkUndo) { + u_extmark_copy_place(buf, l_lnum, l_col, u_lnum, u_col, p_lnum, p_col); + } + + return marks_moved; +} + +// Get reference to line in kbtree_t, allocating it if neccessary. +ExtMarkLine *extline_ref(buf_T *buf, linenr_T lnum, bool put) +{ + kbtree_t(extlines) *b = &buf->b_extlines; + ExtMarkLine t, **pp; + t.lnum = lnum; + + pp = kb_get(extlines, b, &t); + if (!pp) { + if (!put) { + return NULL; + } + ExtMarkLine *p = xcalloc(sizeof(ExtMarkLine), 1); + p->lnum = lnum; + // p->items zero initialized + kb_put(extlines, b, p); + return p; + } + // Return existing + return *pp; +} + +void extline_free(ExtMarkLine *extline) +{ + kb_destroy(markitems, (&extline->items)); + xfree(extline); +} + +/// Put an extmark into a line, +/// +/// caller must ensure combination of id and ns_id isn't in use. +void extmark_put(colnr_T col, + uint64_t id, + ExtMarkLine *extline, + uint64_t ns) +{ + ExtendedMark t; + t.col = col; + t.mark_id = id; + t.line = extline; + t.ns_id = ns; + + kbtree_t(markitems) *b = &(extline->items); + // kb_put requries the key to not be there + assert(!kb_getp(markitems, b, &t)); + + kb_put(markitems, b, t); +} + + diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h new file mode 100644 index 0000000000..7f407a683c --- /dev/null +++ b/src/nvim/mark_extended.h @@ -0,0 +1,267 @@ +#ifndef NVIM_MARK_EXTENDED_H +#define NVIM_MARK_EXTENDED_H + +#include "nvim/mark_extended_defs.h" +#include "nvim/buffer_defs.h" // for buf_T + + +// Macro Documentation: FOR_ALL_? +// Search exclusively using the range values given. +// Use MAXCOL/MAXLNUM for the start and end of the line/col. +// The ns parameter: Unless otherwise stated, this is only a starting point +// for the btree to searched in, the results being itterated over will +// still contain extmarks from other namespaces. + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\ + kbitr_t(extlines) itr;\ + ExtMarkLine t;\ + t.lnum = l_lnum;\ + if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \ + kb_itr_next(extlines, &buf->b_extlines, &itr);\ + }\ + ExtMarkLine *extline;\ + for (; kb_itr_valid(&itr); kb_itr_next(extlines, &buf->b_extlines, &itr)) { \ + extline = kb_itr_key(&itr);\ + if (extline->lnum > u_lnum) { \ + break;\ + }\ + code;\ + } + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\ + kbitr_t(extlines) itr;\ + ExtMarkLine t;\ + t.lnum = u_lnum;\ + if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \ + kb_itr_prev(extlines, &buf->b_extlines, &itr);\ + }\ + ExtMarkLine *extline;\ + for (; kb_itr_valid(&itr); kb_itr_prev(extlines, &buf->b_extlines, &itr)) { \ + extline = kb_itr_key(&itr);\ + if (extline->lnum < l_lnum) { \ + break;\ + }\ + code;\ + } + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ + kbitr_t(markitems) mitr;\ + ExtendedMark mt;\ + mt.ns_id = ns;\ + mt.mark_id = 0;\ + mt.line = NULL;\ + FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \ + mt.col = (extline->lnum != l_lnum) ? MINCOL : l_col;\ + if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \ + kb_itr_next(markitems, &extline->items, &mitr);\ + } \ + ExtendedMark *extmark;\ + for (; \ + kb_itr_valid(&mitr); \ + kb_itr_next(markitems, &extline->items, &mitr)) { \ + extmark = &kb_itr_key(&mitr);\ + if (extmark->line->lnum == u_lnum \ + && extmark->col > u_col) { \ + break;\ + }\ + code;\ + }\ + }) + + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ + kbitr_t(markitems) mitr;\ + ExtendedMark mt;\ + mt.mark_id = sizeof(uint64_t);\ + mt.ns_id = ns;\ + FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \ + mt.col = (extline->lnum != u_lnum) ? MAXCOL : u_col;\ + if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \ + kb_itr_prev(markitems, &extline->items, &mitr);\ + } \ + ExtendedMark *extmark;\ + for (; \ + kb_itr_valid(&mitr); \ + kb_itr_prev(markitems, &extline->items, &mitr)) { \ + extmark = &kb_itr_key(&mitr);\ + if (extmark->line->lnum == l_lnum \ + && extmark->col < l_col) { \ + break;\ + }\ + code;\ + }\ + }) + + +#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\ + kbitr_t(markitems) mitr;\ + ExtendedMark mt;\ + mt.ns_id = 0;\ + mt.mark_id = 0;\ + mt.line = NULL;\ + mt.col = l_col;\ + colnr_T extline_u_col = u_col;\ + if (!kb_itr_get(markitems, &items, mt, &mitr)) { \ + kb_itr_next(markitems, &items, &mitr);\ + } \ + ExtendedMark *extmark;\ + for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \ + extmark = &kb_itr_key(&mitr);\ + if (extmark->col > extline_u_col) { \ + break;\ + }\ + code;\ + } + + +typedef struct ExtmarkNs { // For namespacing extmarks + PMap(uint64_t) *map; // For fast lookup + uint64_t free_id; // For automatically assigning id's +} ExtmarkNs; + + +typedef kvec_t(ExtendedMark *) ExtmarkArray; +typedef kvec_t(ExtMarkLine *) ExtlineArray; + + +// Undo/redo extmarks + +typedef enum { + kExtmarkNOOP, // Extmarks shouldn't be moved + kExtmarkUndo, // Operation should be reversable/undoable + kExtmarkNoUndo, // Operation should not be reversable + kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable +} ExtmarkOp; + + +typedef struct { + linenr_T line1; + linenr_T line2; + long amount; + long amount_after; +} Adjust; + +typedef struct { + linenr_T lnum; + colnr_T mincol; + long col_amount; + long lnum_amount; +} ColAdjust; + +typedef struct { + linenr_T lnum; + colnr_T mincol; + colnr_T endcol; + int eol; +} ColAdjustDelete; + +typedef struct { + linenr_T line1; + linenr_T line2; + linenr_T last_line; + linenr_T dest; + linenr_T num_lines; + linenr_T extra; +} AdjustMove; + +typedef struct { + uint64_t ns_id; + uint64_t mark_id; + linenr_T lnum; + colnr_T col; +} ExtmarkSet; + +typedef struct { + uint64_t ns_id; + uint64_t mark_id; + linenr_T old_lnum; + colnr_T old_col; + linenr_T lnum; + colnr_T col; +} ExtmarkUpdate; + +typedef struct { + uint64_t ns_id; + uint64_t mark_id; + linenr_T lnum; + colnr_T col; +} ExtmarkCopy; + +typedef struct { + linenr_T l_lnum; + colnr_T l_col; + linenr_T u_lnum; + colnr_T u_col; + linenr_T p_lnum; + colnr_T p_col; +} ExtmarkCopyPlace; + +typedef struct { + uint64_t ns_id; + linenr_T l_lnum; + linenr_T u_lnum; +} ExtmarkClear; + + +typedef enum { + kLineAdjust, + kColAdjust, + kColAdjustDelete, + kAdjustMove, + kExtmarkSet, + kExtmarkDel, + kExtmarkUpdate, + kExtmarkCopy, + kExtmarkCopyPlace, + kExtmarkClear, +} UndoObjectType; + +struct undo_object { + UndoObjectType type; + union { + Adjust adjust; + ColAdjust col_adjust; + ColAdjustDelete col_adjust_delete; + AdjustMove move; + ExtmarkSet set; + ExtmarkUpdate update; + ExtmarkCopy copy; + ExtmarkCopyPlace copy_place; + ExtmarkClear clear; + } data; +}; + + +// For doing move of extmarks in substitutions +typedef struct { + lpos_T startpos; + lpos_T endpos; + linenr_T lnum; + int sublen; +} ExtmarkSubSingle; + +// For doing move of extmarks in substitutions +typedef struct { + lpos_T startpos; + lpos_T endpos; + linenr_T lnum; + linenr_T newline_in_pat; + linenr_T newline_in_sub; + linenr_T lnum_added; + lpos_T cm_start; // start of the match + lpos_T cm_end; // end of the match + int eol; // end of the match +} ExtmarkSubMulti; + +typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t; +typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "mark_extended.h.generated.h" +#endif + +#endif // NVIM_MARK_EXTENDED_H diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h new file mode 100644 index 0000000000..4da35a05c8 --- /dev/null +++ b/src/nvim/mark_extended_defs.h @@ -0,0 +1,54 @@ +#ifndef NVIM_MARK_EXTENDED_DEFS_H +#define NVIM_MARK_EXTENDED_DEFS_H + +#include "nvim/pos.h" // for colnr_T +#include "nvim/map.h" // for uint64_t +#include "nvim/lib/kbtree.h" +#include "nvim/lib/kvec.h" + +struct ExtMarkLine; + +typedef struct ExtendedMark +{ + uint64_t ns_id; + uint64_t mark_id; + struct ExtMarkLine *line; + colnr_T col; +} ExtendedMark; + + +// We only need to compare columns as rows are stored in a different tree. +// Marks are ordered by: position, namespace, mark_id +// This improves moving marks but slows down all other use cases (searches) +static inline int extmark_cmp(ExtendedMark a, ExtendedMark b) +{ + int cmp = kb_generic_cmp(a.col, b.col); + if (cmp != 0) { + return cmp; + } + cmp = kb_generic_cmp(a.ns_id, b.ns_id); + if (cmp != 0) { + return cmp; + } + return kb_generic_cmp(a.mark_id, b.mark_id); +} + + +#define markitems_cmp(a, b) (extmark_cmp((a), (b))) +KBTREE_INIT(markitems, ExtendedMark, markitems_cmp, 10) + +typedef struct ExtMarkLine +{ + linenr_T lnum; + kbtree_t(markitems) items; +} ExtMarkLine; + +#define extline_cmp(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum)) +KBTREE_INIT(extlines, ExtMarkLine *, extline_cmp, 10) + + +typedef struct undo_object ExtmarkUndoObject; +typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; + + +#endif // NVIM_MARK_EXTENDED_DEFS_H diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index c1de7ab9a4..a871d424c6 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -30,7 +30,6 @@ #include "nvim/indent_c.h" #include "nvim/buffer_updates.h" #include "nvim/main.h" -#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/ops.c b/src/nvim/ops.c index fbbdfdcd82..1631204840 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -49,6 +49,7 @@ #include "nvim/undo.h" #include "nvim/macros.h" #include "nvim/window.h" +#include "nvim/lib/kvec.h" #include "nvim/os/input.h" #include "nvim/os/time.h" @@ -126,6 +127,30 @@ static char opchars[][3] = { Ctrl_X, NUL, false }, // OP_NR_SUB }; +char *nvim_lltoa(int64_t val, int base) +{ + static char buf[64] = { 0 }; + + int i = 62; + int sign = (val < 0); + if (sign) { + val = -val; + } + + if (val == 0) { + return "0"; + } + + for (; val && i ; i--, val /= base) { + buf[i] = "0123456789abcdef"[val % base]; + } + + if (sign) { + buf[i--] = '-'; + } + return &buf[i+1]; +} + /* * Translate a command name into an operator type. * Must only be called with a valid operator name! @@ -306,6 +331,20 @@ void shift_line( change_indent(INDENT_SET, count, false, NUL, call_changed_bytes); } else { (void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0); + + colnr_T col_amount; + colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw; + if (left) { + col_amount = -p_sw; + } else { + col_amount = p_sw; + } + extmark_col_adjust(curbuf, + curwin->w_cursor.lnum, + mincol, + 0, + col_amount, + kExtmarkUndo); } } @@ -479,6 +518,13 @@ static void shift_block(oparg_T *oap, int amount) State = oldstate; curwin->w_cursor.col = oldcol; p_ri = old_p_ri; + + colnr_T col_amount = p_sw; + if (left) { + col_amount = -col_amount; + } + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + curwin->w_cursor.col, 0, col_amount, kExtmarkUndo); } /* @@ -623,10 +669,19 @@ void op_reindent(oparg_T *oap, Indenter how) amount = how(); /* get the indent for this line */ if (amount >= 0 && set_indent(amount, SIN_UNDO)) { - /* did change the indent, call changed_lines() later */ - if (first_changed == 0) + // did change the indent, call changed_lines() later + if (first_changed == 0) { first_changed = curwin->w_cursor.lnum; + } last_changed = curwin->w_cursor.lnum; + + // Adjust extmarks + extmark_col_adjust(curbuf, + curwin->w_cursor.lnum, + 0, // mincol + 0, // lnum_amount + amount, // col_amount + kExtmarkUndo); } } ++curwin->w_cursor.lnum; @@ -1614,13 +1669,15 @@ int op_delete(oparg_T *oap) curpos = curwin->w_cursor; // remember curwin->w_cursor curwin->w_cursor.lnum++; - del_lines(oap->line_count - 2, false); + del_lines(oap->line_count - 2, true); // delete from start of line until op_end n = (oap->end.col + 1 - !oap->inclusive); curwin->w_cursor.col = 0; (void)del_bytes((colnr_T)n, !virtual_op, oap->op_type == OP_DELETE && !oap->is_VIsual); + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + (colnr_T)0, 0L, (long)-n, kExtmarkUndo); curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); } @@ -1632,10 +1689,41 @@ setmarks: if (oap->motion_type == kMTBlockWise) { curbuf->b_op_end.lnum = oap->end.lnum; curbuf->b_op_end.col = oap->start.col; - } else + } else { curbuf->b_op_end = oap->start; + } curbuf->b_op_start = oap->start; + // TODO(timeyyy): refactor: Move extended marks + // + 1 to change to buf mode, + // and + 1 because we only move marks after the deleted col + colnr_T mincol = oap->start.col + 1 + 1; + colnr_T endcol; + if (oap->motion_type == kMTBlockWise) { + // TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ? + endcol = bd.end_vcol + 1; + for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) { + extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, + kExtmarkUndo, 0); + } + + // Delete characters within one line, + // The case with multiple lines is handled by do_join + } else if (oap->motion_type == kMTCharWise && oap->line_count == 1) { + // + 1 to change to buf mode, then plus 1 to fit function requirements + endcol = oap->end.col + 1 + 1; + + lnum = curwin->w_cursor.lnum; + if (oap->is_VIsual == false) { + // for some reason we required this :/ + endcol = endcol - 1; + // for some reason we required this :/ + if (endcol < mincol) { + endcol = mincol; + } + } + extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0); + } return OK; } @@ -2031,8 +2119,8 @@ bool swapchar(int op_type, pos_T *pos) pos_T sp = curwin->w_cursor; curwin->w_cursor = *pos; - /* don't use del_char(), it also removes composing chars */ - del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE); + // don't use del_char(), it also removes composing chars + del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false); ins_char(nc); curwin->w_cursor = sp; } else { @@ -2105,8 +2193,9 @@ void op_insert(oparg_T *oap, long count1) * values in "bd". */ if (u_save_cursor() == FAIL) return; - for (i = 0; i < bd.endspaces; i++) + for (i = 0; i < bd.endspaces; i++) { ins_char(' '); + } bd.textlen += bd.endspaces; } } else { @@ -2224,6 +2313,10 @@ void op_insert(oparg_T *oap, long count1) xfree(ins_text); } } + colnr_T col = oap->start.col; + for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) { + extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo); + } } /* @@ -2694,6 +2787,34 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) } + +// Function length couldn't be over 500 lines.. +static void extmarks_do_put(int dir, + size_t totlen, + MotionType y_type, + linenr_T lnum, + colnr_T col) +{ + // adjust extmarks + colnr_T col_amount; + if (dir == FORWARD) { + col_amount = (colnr_T)(totlen-1); + } else { + col_amount = (colnr_T)totlen; + } + // Move extmark with char put + if (y_type == kMTCharWise) { + extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); + // Move extmark with blockwise put + } else if (y_type == kMTBlockWise) { + for (lnum = curbuf->b_op_start.lnum; + lnum <= curbuf->b_op_end.lnum; + lnum++) { + extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); + } + } +} + /* * Put contents of register "regname" into the text. * Caller must check "regname" to be valid! @@ -2708,8 +2829,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) char_u *oldp; int yanklen; size_t totlen = 0; // init for gcc - linenr_T lnum; - colnr_T col; + linenr_T lnum = 0; + colnr_T col = 0; size_t i; // index in y_array[] MotionType y_type; size_t y_size; @@ -3286,11 +3407,11 @@ error: curbuf->b_op_start.lnum++; } // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. But still needed in diff mode. + // can't be marks there. if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { + < curbuf->b_ml.ml_line_count) { mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), - (linenr_T)MAXLNUM, nr_lines, 0L, false); + (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo); } // note changed text for displaying and folding @@ -3352,6 +3473,8 @@ end: /* If the cursor is past the end of the line put it at the end. */ adjust_cursor_eol(); + + extmarks_do_put(dir, totlen, y_type, lnum, col); } /* @@ -3745,6 +3868,12 @@ int do_join(size_t count, * column. This is not Vi compatible, but Vi deletes the marks, thus that * should not really be a problem. */ + + linenr_T lnum; + colnr_T mincol; + long lnum_amount; + long col_amount; + for (t = (linenr_T)count - 1;; t--) { cend -= currsize; memmove(cend, curr, (size_t)currsize); @@ -3756,12 +3885,18 @@ int do_join(size_t count, // If deleting more spaces than adding, the cursor moves no more than // what is added if it is inside these spaces. const int spaces_removed = (int)((curr - curr_start) - spaces[t]); + lnum = curwin->w_cursor.lnum + t; + mincol = (colnr_T)0; + lnum_amount = (linenr_T)-t; + col_amount = (long)(cend - newp - spaces_removed); + + mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed, + kExtmarkUndo); - mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t, - (long)(cend - newp - spaces_removed), spaces_removed); if (t == 0) { break; } + curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1)); if (remove_comments) curr += comments[t - 1]; @@ -3769,6 +3904,7 @@ int do_join(size_t count, curr = skipwhite(curr); currsize = (int)STRLEN(curr); } + ml_replace(curwin->w_cursor.lnum, newp, false); if (setmark) { @@ -4189,14 +4325,14 @@ format_lines( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0); + (long)-next_leader_len, 0, kExtmarkUndo); } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND int indent = (int)getwhitecols_curline(); if (indent > 0) { (void)del_bytes(indent, FALSE, FALSE); mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0); + (colnr_T)0, 0L, (long)-indent, 0, kExtmarkUndo); } } curwin->w_cursor.lnum--; @@ -4862,6 +4998,23 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curbuf->b_op_end.col--; } + if (did_change) { + extmark_col_adjust_delete(curbuf, + pos->lnum, + startpos.col + 2, + endpos.col + 1 + length, + kExtmarkUndo, + 0); + long col_amount = (long)strlen(nvim_lltoa((int64_t)n, 10)); + col_amount = negative ? col_amount + 1 : col_amount; + extmark_col_adjust(curbuf, + pos->lnum, + startpos.col + 1, + 0, + col_amount, + kExtmarkUndo); + } + theend: if (visual) { curwin->w_cursor = save_cursor; diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 47d253e083..8e86ea08c5 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -14,6 +14,10 @@ typedef int colnr_T; enum { MAXLNUM = 0x7fffffff }; /// Maximal column number, 31 bits enum { MAXCOL = 0x7fffffff }; +// Minimum line number +enum { MINLNUM = 1 }; +// minimum column number +enum { MINCOL = 1 }; /* * position in file or buffer diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 035613c7fd..91a142214b 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -91,7 +91,9 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/buffer_updates.h" +#include "nvim/pos.h" // MAXLNUM #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/memline.h" #include "nvim/message.h" #include "nvim/misc1.h" @@ -106,6 +108,7 @@ #include "nvim/types.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +#include "nvim/lib/kvec.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.c.generated.h" @@ -384,6 +387,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) * up the undo info when out of memory. */ uhp = xmalloc(sizeof(u_header_T)); + kv_init(uhp->uh_extmark); #ifdef U_DEBUG uhp->uh_magic = UH_MAGIC; #endif @@ -2249,10 +2253,10 @@ static void u_undoredo(int undo, bool do_buf_event) xfree((char_u *)uep->ue_array); } - /* adjust marks */ + // Adjust marks if (oldsize != newsize) { mark_adjust(top + 1, top + oldsize, (long)MAXLNUM, - (long)newsize - (long)oldsize, false); + (long)newsize - (long)oldsize, false, kExtmarkNOOP); if (curbuf->b_op_start.lnum > top + oldsize) { curbuf->b_op_start.lnum += newsize - oldsize; } @@ -2285,6 +2289,23 @@ static void u_undoredo(int undo, bool do_buf_event) newlist = uep; } + // Adjust Extmarks + ExtmarkUndoObject undo_info; + if (undo) { + for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) { + undo_info = kv_A(curhead->uh_extmark, i); + extmark_apply_undo(undo_info, undo); + } + // redo + } else { + for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) { + undo_info = kv_A(curhead->uh_extmark, i); + extmark_apply_undo(undo_info, undo); + } + } + // finish Adjusting extmarks + + curhead->uh_entry = newlist; curhead->uh_flags = new_flags; if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) { @@ -2828,6 +2849,9 @@ u_freeentries( u_freeentry(uep, uep->ue_size); } + // TODO(timeyyy): is this the correct place? ... + kv_destroy(uhp->uh_extmark); + #ifdef U_DEBUG uhp->uh_magic = 0; #endif @@ -3022,3 +3046,28 @@ list_T *u_eval_tree(const u_header_T *const first_uhp) return list; } + +// Given the buffer, Return the undo header. If none is set, set one first. +// NULL will be returned if e.g undolevels = -1 (undo disabled) +u_header_T *force_get_undo_header(buf_T *buf) +{ + u_header_T *uhp = NULL; + if (buf->b_u_curhead != NULL) { + uhp = buf->b_u_curhead; + } else if (buf->b_u_newhead) { + uhp = buf->b_u_newhead; + } + // Create the first undo header for the buffer + if (!uhp) { + // TODO(timeyyy): there would be a better way to do this! + u_save_cursor(); + uhp = buf->b_u_curhead; + if (!uhp) { + uhp = buf->b_u_newhead; + if (get_undolevel() > 0) { + assert(uhp); + } + } + } + return uhp; +} diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 6c7e2bba41..0fa3b415ec 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -4,6 +4,7 @@ #include // for time_t #include "nvim/pos.h" +#include "nvim/mark_extended_defs.h" #include "nvim/mark_defs.h" typedef struct u_header u_header_T; @@ -56,14 +57,15 @@ struct u_header { u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */ pos_T uh_cursor; /* cursor position before saving */ long uh_cursor_vcol; - int uh_flags; /* see below */ - fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */ - visualinfo_T uh_visual; /* Visual areas before undo/after redo */ - time_t uh_time; /* timestamp when the change was made */ - long uh_save_nr; /* set when the file was saved after the - changes in this block */ + int uh_flags; // see below + fmark_T uh_namedm[NMARKS]; // marks before undo/after redo + extmark_undo_vec_t uh_extmark; // info to move extmarks + visualinfo_T uh_visual; // Visual areas before undo/after redo + time_t uh_time; // timestamp when the change was made + long uh_save_nr; // set when the file was saved after the + // changes in this block #ifdef U_DEBUG - int uh_magic; /* magic number to check allocation */ + int uh_magic; // magic number to check allocation #endif }; -- cgit From 18a8b702c0ce7a8bacd84f6c95e440ae23a3299e Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 9 Nov 2019 12:41:50 +0100 Subject: extmark: review changes --- src/nvim/api/buffer.c | 87 +++++--- src/nvim/api/private/helpers.c | 14 +- src/nvim/buffer_defs.h | 2 +- src/nvim/ex_cmds.c | 2 - src/nvim/map.c | 1 - src/nvim/map.h | 1 - src/nvim/mark_extended.c | 464 +++++++++++++++-------------------------- src/nvim/mark_extended.h | 65 +++--- src/nvim/mark_extended_defs.h | 4 +- src/nvim/ops.c | 77 ++----- src/nvim/undo.c | 7 +- 11 files changed, 294 insertions(+), 430 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a77c33e891..24fa963fbc 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1003,16 +1003,16 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } -/// Returns position info for a given extmark id +/// Returns position for a given extmark id /// /// @param buffer The buffer handle /// @param namespace a identifier returned previously with nvim_create_namespace /// @param id the extmark id /// @param[out] err Details of an error that may have occurred /// @return (row, col) tuple or empty list () if extmark id was absent -ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer namespace, +ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, Integer id, Error *err) - FUNC_API_SINCE(6) + FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1022,13 +1022,13 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer namespace, return rv; } - if (!ns_initialized((uint64_t)namespace)) { - api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); return rv; } ExtendedMark *extmark = extmark_from_id(buf, - (uint64_t)namespace, + (uint64_t)ns_id, (uint64_t)id); if (!extmark) { return rv; @@ -1052,16 +1052,17 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer namespace, /// first marks prior to a given position. /// /// @param buffer The buffer handle -/// @param namespace An id returned previously from nvim_create_namespace +/// @param ns_id An id returned previously from nvim_create_namespace /// @param lower One of: extmark id, (row, col) or 0, -1 for buffer ends /// @param upper One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param amount Maximum number of marks to return or -1 for all marks found -/// /// @param[out] err Details of an error that may have occurred +/// @param opts additional options. Supports the keys: +/// - amount: Maximum number of marks to return +/// @param[out] err Details of an error that may have occurred /// @return [[nsmark_id, row, col], ...] Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, - Object start, Object end, Integer amount, + Object start, Object end, Dictionary opts, Error *err) - FUNC_API_SINCE(6) + FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1071,9 +1072,26 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); return rv; } + Integer amount = -1; + + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("amount", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, "amount is not an integer"); + return rv; + } + amount = v->data.integer; + v->data.integer = LUA_NOREF; + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return rv; + } + } if (amount == 0) { return rv; @@ -1122,20 +1140,30 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, return rv; } -/// Create or update a namespaced mark at a position +/// Create or update an extmark at a position /// /// If an invalid namespace is given, an error will be raised. /// +/// To create a new extmark, pass in id=0. The new extmark id will be +/// returned. To move an existing mark, pass in its id. +/// +/// It is also allowed to create a new mark by passing in a previously unused +/// id, but the caller must then keep track of existing and unused ids itself. +/// This is mainly useful over RPC, to avoid needing to wait for the return +/// value. +/// /// @param buffer The buffer handle /// @param ns_id a identifier returned previously with nvim_create_namespace -/// @param id The extmark's id or 0 for next free id +/// @param id The extmark's id or 0 to create a new mark. /// @param row The row to set the extmark to. /// @param col The column to set the extmark to. +/// @param opts Optional parameters. Currently not used. /// @param[out] err Details of an error that may have occurred -/// @return the nsmark_id for a new mark, or 0 for an update +/// @return the id of the extmark. Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, - Integer line, Integer col, Error *err) - FUNC_API_SINCE(6) + Integer line, Integer col, + Dictionary opts, Error *err) + FUNC_API_SINCE(7) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -1143,7 +1171,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); return 0; } @@ -1172,16 +1205,10 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return 0; } - bool new = extmark_set(buf, (uint64_t)ns_id, id_num, - (linenr_T)line+1, - (colnr_T)col+1, - kExtmarkUndo); + extmark_set(buf, (uint64_t)ns_id, id_num, + (linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo); - if (new) { - return (Integer)id_num; - } else { - return 0; - } + return (Integer)id_num; } /// Remove an extmark @@ -1190,12 +1217,12 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, /// @param ns_id a identifier returned previously with nvim_create_namespace /// @param id The extmarks's id /// @param[out] err Details of an error that may have occurred -/// @return true on success, false if no extmarks found +/// @return true on success, false if the extmark was not found. Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *err) - FUNC_API_SINCE(6) + FUNC_API_SINCE(7) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1203,7 +1230,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, return false; } if (!ns_initialized((uint64_t)ns_id)) { - api_set_error(err, kErrorTypeValidation, _("Invalid mark namespace")); + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); return false; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 5e9a572a78..6b69350429 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1575,12 +1575,14 @@ bool ns_initialized(uint64_t ns) return ns < (uint64_t)next_namespace_id; } -// Extmarks may be queried from position or name or even special names -// in the future such as "cursor". This macro sets the line and col -// to make the extmark functions recognize what's required -// -// *lnum: linenr_T, lnum to be set -// *col: colnr_T, col to be set +/// Get line and column from extmark object +/// +/// Extmarks may be queried from position or name or even special names +/// in the future such as "cursor". This function sets the line and col +/// to make the extmark functions recognize what's required +/// +/// @param[out] lnum lnum to be set +/// @param[out] colnr col to be set bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, Object obj, linenr_T *lnum, colnr_T *colnr, Error *err) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 2d65d0e160..29eb32d40a 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -809,7 +809,7 @@ struct file_buffer { kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights PMap(uint64_t) *b_extmark_ns; // extmark namespaces - kbtree_t(extlines) b_extlines; // extmarks + kbtree_t(extmarklines) b_extlines; // extmarks kvec_t(ExtMarkLine *) b_extmark_move_space; // temp space for extmarks // array of channel_id:s which have asked to receive updates for this diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index fb97c46117..c5f7815fc5 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -106,8 +106,6 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_cmds.c.generated.h" -#include "mark_extended.h" - #endif /// ":ascii" and "ga" implementation diff --git a/src/nvim/map.c b/src/nvim/map.c index a4cf5304e0..cdade5ee71 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -184,7 +184,6 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) #define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) -MAP_IMPL(uint64_t, cstr_t, DEFAULT_INITIALIZER) /// Deletes a key:value pair from a string:pointer map, and frees the diff --git a/src/nvim/map.h b/src/nvim/map.h index 073be6822a..75ab64cca4 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -42,7 +42,6 @@ MAP_DECLS(handle_T, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) -MAP_DECLS(uint64_t, cstr_t) #define map_new(T, U) map_##T##_##U##_new #define map_free(T, U) map_##T##_##U##_free diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index cb5f45d88c..88348a8045 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -1,8 +1,8 @@ // 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 -// Implements extended marks for plugins -// Each Mark exists in a btree of lines containing btrees of columns. +// Implements extended marks for plugins. Each mark exists in a btree of +// lines containing btrees of columns. // // The btree provides efficent range lookups. // A map of pointers to the marks is used for fast lookup by mark id. @@ -17,17 +17,17 @@ // copy extmarks is for the area being effected by a delete. // // Marks live in namespaces that allow plugins/users to segregate marks -// from other users, namespaces have to be initialized before usage +// from other users. // // For possible ideas for efficency improvements see: // http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html +// TODO(bfredl): These ideas could be used for an enhanced btree, which +// wouldn't need separate line and column layers. // Other implementations exist in gtk and tk toolkits. // -// Deleting marks only happens explicitly extmark_del, deleteing over a -// range of marks will only move the marks. -// -// deleting on a mark will leave it in that same position unless it is on -// the eol of the line. +// Deleting marks only happens when explicitly calling extmark_del, deleteing +// over a range of marks will only move the marks. Deleting on a mark will +// leave it in same position unless it is on the EOL of a line. #include #include "nvim/vim.h" @@ -50,23 +50,19 @@ /// /// must not be used during iteration! /// @returns whether a new mark was created -int extmark_set(buf_T *buf, - uint64_t ns, - uint64_t id, - linenr_T lnum, - colnr_T col, - ExtmarkOp op) +int extmark_set(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, ExtmarkOp op) { ExtendedMark *extmark = extmark_from_id(buf, ns, id); if (!extmark) { extmark_create(buf, ns, id, lnum, col, op); return true; } else { - ExtMarkLine *extline = extmark->line; + ExtMarkLine *extmarkline = extmark->line; extmark_update(extmark, buf, ns, id, lnum, col, op, NULL); - if (kb_size(&extline->items) == 0) { - kb_del(extlines, &buf->b_extlines, extline); - extline_free(extline); + if (kb_size(&extmarkline->items) == 0) { + kb_del(extmarklines, &buf->b_extlines, extmarkline); + extmarkline_free(extmarkline); } return false; } @@ -74,10 +70,7 @@ int extmark_set(buf_T *buf, // Remove an extmark // Returns 0 on missing id -int extmark_del(buf_T *buf, - uint64_t ns, - uint64_t id, - ExtmarkOp op) +int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op) { ExtendedMark *extmark = extmark_from_id(buf, ns, id); if (!extmark) { @@ -88,11 +81,8 @@ int extmark_del(buf_T *buf, // Free extmarks in a ns between lines // if ns = 0, it means clear all namespaces -void extmark_clear(buf_T *buf, - uint64_t ns, - linenr_T l_lnum, - linenr_T u_lnum, - ExtmarkOp undo) +void extmark_clear(buf_T *buf, uint64_t ns, + linenr_T l_lnum, linenr_T u_lnum, ExtmarkOp undo) { if (!buf->b_extmark_ns) { return; @@ -115,7 +105,7 @@ void extmark_clear(buf_T *buf, } FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { - FOR_ALL_EXTMARKS_IN_LINE(extline->items, 0, MAXCOL, { + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, { if (extmark->ns_id == ns || all_ns) { marks_cleared = true; if (all_ns) { @@ -124,12 +114,12 @@ void extmark_clear(buf_T *buf, ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); } pmap_del(uint64_t)(ns_obj->map, extmark->mark_id); - kb_del_itr(markitems, &extline->items, &mitr); + kb_del_itr(markitems, &extmarkline->items, &mitr); } }); - if (kb_size(&extline->items) == 0) { - kb_del_itr(extlines, &buf->b_extlines, &itr); - extline_free(extline); + if (kb_size(&extmarkline->items) == 0) { + kb_del_itr(extmarklines, &buf->b_extlines, &itr); + extmarkline_free(extmarkline); } }); @@ -145,14 +135,10 @@ void extmark_clear(buf_T *buf, // will be searched to the start, or end // dir can be set to control the order of the array // amount = amount of marks to find or -1 for all -ExtmarkArray extmark_get(buf_T *buf, - uint64_t ns, - linenr_T l_lnum, - colnr_T l_col, - linenr_T u_lnum, - colnr_T u_col, - int64_t amount, - bool reverse) +ExtmarkArray extmark_get(buf_T *buf, uint64_t ns, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col, + int64_t amount, bool reverse) { ExtmarkArray array = KV_INITIAL_VALUE; // Find all the marks @@ -178,12 +164,8 @@ ExtmarkArray extmark_get(buf_T *buf, return array; } -static void extmark_create(buf_T *buf, - uint64_t ns, - uint64_t id, - linenr_T lnum, - colnr_T col, - ExtmarkOp op) +static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, ExtmarkOp op) { if (!buf->b_extmark_ns) { buf->b_extmark_ns = pmap_new(uint64_t)(); @@ -198,13 +180,13 @@ static void extmark_create(buf_T *buf, } // Create or get a line - ExtMarkLine *extline = extline_ref(buf, lnum, true); + ExtMarkLine *extmarkline = extmarkline_ref(buf, lnum, true); // Create and put mark on the line - extmark_put(col, id, extline, ns); + extmark_put(col, id, extmarkline, ns); // Marks do not have stable address so we have to look them up // by using the line instead of the mark - pmap_put(uint64_t)(ns_obj->map, id, extline); + pmap_put(uint64_t)(ns_obj->map, id, extmarkline); if (op != kExtmarkNoUndo) { u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet); } @@ -215,14 +197,10 @@ static void extmark_create(buf_T *buf, // update the position of an extmark // to update while iterating pass the markitems itr -static void extmark_update(ExtendedMark *extmark, - buf_T *buf, - uint64_t ns, - uint64_t id, - linenr_T lnum, - colnr_T col, - ExtmarkOp op, - kbitr_t(markitems) *mitr) +static void extmark_update(ExtendedMark *extmark, buf_T *buf, + uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, + ExtmarkOp op, kbitr_t(markitems) *mitr) { assert(op != kExtmarkNOOP); if (op != kExtmarkNoUndo) { @@ -232,7 +210,7 @@ static void extmark_update(ExtendedMark *extmark, ExtMarkLine *old_line = extmark->line; // Move the mark to a new line and update column if (old_line->lnum != lnum) { - ExtMarkLine *ref_line = extline_ref(buf, lnum, true); + ExtMarkLine *ref_line = extmarkline_ref(buf, lnum, true); extmark_put(col, id, ref_line, ns); // Update the hashmap ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); @@ -272,12 +250,12 @@ static int extmark_delete(ExtendedMark *extmark, pmap_del(uint64_t)(ns_obj->map, id); // Remove the mark mark from the line - ExtMarkLine *extline = extmark->line; - kb_del(markitems, &extline->items, *extmark); + ExtMarkLine *extmarkline = extmark->line; + kb_del(markitems, &extmarkline->items, *extmark); // Remove the line if there are no more marks in the line - if (kb_size(&extline->items) == 0) { - kb_del(extlines, &buf->b_extlines, extline); - extline_free(extline); + if (kb_size(&extmarkline->items) == 0) { + kb_del(extmarklines, &buf->b_extlines, extmarkline); + extmarkline_free(extmarkline); } return true; } @@ -292,12 +270,12 @@ ExtendedMark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) if (!ns_obj || !kh_size(ns_obj->map->table)) { return NULL; } - ExtMarkLine *extline = pmap_get(uint64_t)(ns_obj->map, id); - if (!extline) { + ExtMarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id); + if (!extmarkline) { return NULL; } - FOR_ALL_EXTMARKS_IN_LINE(extline->items, 0, MAXCOL, { + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, { if (extmark->ns_id == ns && extmark->mark_id == id) { return extmark; @@ -354,8 +332,8 @@ void extmark_free_all(buf_T *buf) ExtmarkNs *ns_obj; FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, { - kb_del_itr(extlines, &buf->b_extlines, &itr); - extline_free(extline); + kb_del_itr(extmarklines, &buf->b_extlines, &itr); + extmarkline_free(extmarkline); }) map_foreach(buf->b_extmark_ns, ns, ns_obj, { @@ -365,9 +343,10 @@ void extmark_free_all(buf_T *buf) }); pmap_free(uint64_t)(buf->b_extmark_ns); + buf->b_extmark_ns = NULL; // k?_init called to set pointers to NULL - kb_destroy(extlines, (&buf->b_extlines)); + kb_destroy(extmarklines, (&buf->b_extlines)); kb_init(&buf->b_extlines); kv_destroy(buf->b_extmark_move_space); @@ -376,14 +355,10 @@ void extmark_free_all(buf_T *buf) // Save info for undo/redo of set marks -static void u_extmark_set(buf_T *buf, - uint64_t ns, - uint64_t id, - linenr_T lnum, - colnr_T col, - UndoObjectType undo_type) +static void u_extmark_set(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, UndoObjectType undo_type) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -401,15 +376,11 @@ static void u_extmark_set(buf_T *buf, } // Save info for undo/redo of deleted marks -static void u_extmark_update(buf_T *buf, - uint64_t ns, - uint64_t id, - linenr_T old_lnum, - colnr_T old_col, - linenr_T lnum, - colnr_T col) +static void u_extmark_update(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T old_lnum, colnr_T old_col, + linenr_T lnum, colnr_T col) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -431,13 +402,10 @@ static void u_extmark_update(buf_T *buf, // - Instead of 1 undo object for each char inserted, // we create 1 undo objet for all text inserted before the user hits esc // Return True if we compacted else False -static bool u_compact_col_adjust(buf_T *buf, - linenr_T lnum, - colnr_T mincol, - long lnum_amount, - long col_amount) +static bool u_compact_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol, + long lnum_amount, long col_amount) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return false; } @@ -472,13 +440,10 @@ static bool u_compact_col_adjust(buf_T *buf, } // Save col_adjust info so we can undo/redo -void u_extmark_col_adjust(buf_T *buf, - linenr_T lnum, - colnr_T mincol, - long lnum_amount, - long col_amount) +void u_extmark_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol, + long lnum_amount, long col_amount) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -498,13 +463,10 @@ void u_extmark_col_adjust(buf_T *buf, } // Save col_adjust_delete info so we can undo/redo -void u_extmark_col_adjust_delete(buf_T *buf, - linenr_T lnum, - colnr_T mincol, - colnr_T endcol, - int eol) +void u_extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, + colnr_T mincol, colnr_T endcol, int eol) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -522,13 +484,10 @@ void u_extmark_col_adjust_delete(buf_T *buf, } // Save adjust info so we can undo/redo -static void u_extmark_adjust(buf_T * buf, - linenr_T line1, - linenr_T line2, - long amount, - long amount_after) +static void u_extmark_adjust(buf_T * buf, linenr_T line1, linenr_T line2, + long amount, long amount_after) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -546,15 +505,11 @@ static void u_extmark_adjust(buf_T * buf, } // save info to undo/redo a :move -void u_extmark_move(buf_T *buf, - linenr_T line1, - linenr_T line2, - linenr_T last_line, - linenr_T dest, - linenr_T num_lines, +void u_extmark_move(buf_T *buf, linenr_T line1, linenr_T line2, + linenr_T last_line, linenr_T dest, linenr_T num_lines, linenr_T extra) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -577,14 +532,11 @@ void u_extmark_move(buf_T *buf, // the operation. This will do nothing on redo, enforces correct position when // undo. // if ns = 0, it means copy all namespaces -void u_extmark_copy(buf_T *buf, - uint64_t ns, - linenr_T l_lnum, - colnr_T l_col, - linenr_T u_lnum, - colnr_T u_col) +void u_extmark_copy(buf_T *buf, uint64_t ns, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -608,14 +560,11 @@ void u_extmark_copy(buf_T *buf, } void u_extmark_copy_place(buf_T *buf, - linenr_T l_lnum, - colnr_T l_col, - linenr_T u_lnum, - colnr_T u_col, - linenr_T p_lnum, - colnr_T p_col) + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col, + linenr_T p_lnum, colnr_T p_col) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -635,12 +584,10 @@ void u_extmark_copy_place(buf_T *buf, } // Save info for undo/redo of extmark_clear -static void u_extmark_clear(buf_T *buf, - uint64_t ns, - linenr_T l_lnum, - linenr_T u_lnum) +static void u_extmark_clear(buf_T *buf, uint64_t ns, + linenr_T l_lnum, linenr_T u_lnum) { - u_header_T *uhp = force_get_undo_header(buf); + u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } @@ -843,103 +790,33 @@ static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo) if (undo) { if (dest >= line2) { - extmark_adjust(curbuf, - dest - num_lines + 1, - dest, - last_line - dest + num_lines - 1, - 0L, - kExtmarkNoUndo, + extmark_adjust(curbuf, dest - num_lines + 1, dest, + last_line - dest + num_lines - 1, 0L, kExtmarkNoUndo, true); - extmark_adjust(curbuf, - dest - line2, - dest - line1, - dest - line2, - 0L, - kExtmarkNoUndo, - false); + extmark_adjust(curbuf, dest - line2, dest - line1, + dest - line2, 0L, kExtmarkNoUndo, false); } else { - extmark_adjust(curbuf, - line1-num_lines, - line2-num_lines, - last_line - (line1-num_lines), - 0L, - kExtmarkNoUndo, - true); - extmark_adjust(curbuf, - (line1-num_lines) + 1, - (line2-num_lines) + 1, - -num_lines, - 0L, - kExtmarkNoUndo, - false); + extmark_adjust(curbuf, line1-num_lines, line2-num_lines, + last_line - (line1-num_lines), 0L, kExtmarkNoUndo, true); + extmark_adjust(curbuf, (line1-num_lines) + 1, (line2-num_lines) + 1, + -num_lines, 0L, kExtmarkNoUndo, false); } - extmark_adjust(curbuf, - last_line, - last_line + num_lines - 1, - line1 - last_line, - 0L, - kExtmarkNoUndo, - true); + extmark_adjust(curbuf, last_line, last_line + num_lines - 1, + line1 - last_line, 0L, kExtmarkNoUndo, true); // redo } else { - extmark_adjust(curbuf, - line1, - line2, - last_line - line2, - 0L, - kExtmarkNoUndo, - true); + extmark_adjust(curbuf, line1, line2, + last_line - line2, 0L, kExtmarkNoUndo, true); if (dest >= line2) { - extmark_adjust(curbuf, - line2 + 1, - dest, - -num_lines, - 0L, - kExtmarkNoUndo, - false); + extmark_adjust(curbuf, line2 + 1, dest, + -num_lines, 0L, kExtmarkNoUndo, false); } else { - extmark_adjust(curbuf, - dest + 1, - line1 - 1, - num_lines, - 0L, - kExtmarkNoUndo, - false); + extmark_adjust(curbuf, dest + 1, line1 - 1, + num_lines, 0L, kExtmarkNoUndo, false); } - extmark_adjust(curbuf, - last_line - num_lines + 1, - last_line, - -(last_line - dest - extra), - 0L, - kExtmarkNoUndo, - true); - } -} - -// for anything other than deletes -// Return, desired col amount where the adjustment should take place -// (not taking) eol into account -static long update_constantly(colnr_T _, colnr_T __, long col_amount) -{ - return col_amount; -} - -// for deletes, -// Return, desired col amount where the adjustment should take place -// (not taking) eol into account -static long update_variably(colnr_T mincol, colnr_T current, long endcol) -{ - colnr_T start_effected_range = mincol - 1; - long col_amount; - // When mark inside range - if (current < endcol) { - col_amount = -(current - start_effected_range); - // Mark outside of range - } else { - // -1 because a delete of width 0 should still move marks - col_amount = -(endcol - mincol) - 1; + extmark_adjust(curbuf, last_line - num_lines + 1, last_line, + -(last_line - dest - extra), 0L, kExtmarkNoUndo, true); } - return col_amount; } @@ -960,23 +837,37 @@ colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum) // returns true if something was moved otherwise false static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum, colnr_T mincol, long lnum_amount, - long (*calc_amount)(colnr_T, colnr_T, long), - long func_arg) + bool for_delete, + long update_col) { bool marks_exist = false; - colnr_T *cp; - long col_amount; - ExtMarkLine *extline = extline_ref(buf, lnum, false); - if (!extline) { + ExtMarkLine *extmarkline = extmarkline_ref(buf, lnum, false); + if (!extmarkline) { return false; } - FOR_ALL_EXTMARKS_IN_LINE(extline->items, mincol, MAXCOL, { + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, mincol, MAXCOL, { marks_exist = true; - cp = &(extmark->col); - col_amount = (*calc_amount)(mincol, *cp, func_arg); + // Calculate desired col amount where the adjustment should take place + // (not taking) eol into account + long col_amount; + if (for_delete) { + if (extmark->col < update_col) { + // When mark inside range + colnr_T start_effected_range = mincol - 1; + col_amount = -(extmark->col - start_effected_range); + } else { + // Mark outside of range + // -1 because a delete of width 0 should still move marks + col_amount = -(update_col - mincol) - 1; + } + } else { + // for anything other than deletes + col_amount = update_col; + } + // No update required for this guy if (col_amount == 0 && lnum_amount == 0) { continue; @@ -984,23 +875,22 @@ static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum, // Set mark to start of line if (col_amount < 0 - && *cp <= (colnr_T)-col_amount) { // TODO(timeyyy): does mark.c - // need this line? + && extmark->col <= (colnr_T)-col_amount) { extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, - extline->lnum + lnum_amount, + extmarkline->lnum + lnum_amount, 1, kExtmarkNoUndo, &mitr); // Update the mark } else { // Note: The undo is handled by u_extmark_col_adjust, NoUndo here extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, - extline->lnum + lnum_amount, - *cp + (colnr_T)col_amount, kExtmarkNoUndo, &mitr); + extmarkline->lnum + lnum_amount, + extmark->col + (colnr_T)col_amount, kExtmarkNoUndo, &mitr); } }) - if (kb_size(&extline->items) == 0) { - kb_del(extlines, &buf->b_extlines, extline); - extline_free(extline); + if (kb_size(&extmarkline->items) == 0) { + kb_del(extmarklines, &buf->b_extlines, extmarkline); + extmarkline_free(extmarkline); } return marks_exist; @@ -1019,7 +909,7 @@ void extmark_col_adjust(buf_T *buf, linenr_T lnum, assert(col_amount > INT_MIN && col_amount <= INT_MAX); bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount, - &update_constantly, col_amount); + false, col_amount); if (undo == kExtmarkUndo && marks_moved) { u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount); @@ -1038,17 +928,6 @@ void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, ExtmarkOp undo, int _eol) { colnr_T start_effected_range = mincol; - // TODO(timeyyy): understand why this has to be uncommented out for the - // tests to pass.. shouldn't this be required? - // why is this even being called if it's not neeed? - - // Some tests in the vim suite were hitting the assertion that was here. - // functional/ui/mouse_spec/ - // We don't know what to do in this case so just bail! - - // if (start_effected_range <= endcol) { - // return; - // } bool marks_moved; if (undo == kExtmarkUndo) { @@ -1058,7 +937,7 @@ void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, } marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0, - &update_variably, (long)endcol); + true, (long)endcol); // Deletes at the end of the line have different behaviour than the normal // case when deleted. @@ -1071,7 +950,7 @@ void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, } FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, { extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, - extline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr); + extmarkline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr); }) // Record the undo for the actual move @@ -1092,12 +971,12 @@ void extmark_adjust(buf_T *buf, ExtMarkLine *_extline; // btree needs to be kept ordered to work, so far only :move requires this - // 2nd call with end_temp = unpack the lines from the temp position + // 2nd call with end_temp = true unpack the lines from the temp position if (end_temp && amount < 0) { for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) { _extline = kv_A(buf->b_extmark_move_space, i); _extline->lnum += amount; - kb_put(extlines, &buf->b_extlines, _extline); + kb_put(extmarklines, &buf->b_extlines, _extline); } kv_size(buf->b_extmark_move_space) = 0; return; @@ -1108,11 +987,12 @@ void extmark_adjust(buf_T *buf, linenr_T adj_start = line1; if (amount == MAXLNUM) { - // Careful! marks from deleted region can end up on en extisting extline + // Careful! marks from deleted region can end up on en extisting extmarkline // that is goinig to be adjusted to the target position. linenr_T join_num = line1 - amount_after; - ExtMarkLine *joinline = join_num > line2 \ - ? extline_ref(buf, join_num, false) : NULL; + ExtMarkLine *joinline = (join_num > line2 + ? extmarkline_ref(buf, join_num, false) : NULL); + // extmark_adjust is already redoable, the copy should only be for undo marks_exist = extmark_copy_and_place(curbuf, line1, 1, @@ -1123,12 +1003,12 @@ void extmark_adjust(buf_T *buf, } FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, { marks_exist = true; - lp = &(extline->lnum); + lp = &(extmarkline->lnum); if (*lp <= line2) { // 1st call with end_temp = true, store the lines in a temp position if (end_temp && amount > 0) { - kb_del_itr_extlines(&buf->b_extlines, &itr); - kv_push(buf->b_extmark_move_space, extline); + kb_del_itr_extmarklines(&buf->b_extlines, &itr); + kv_push(buf->b_extmark_move_space, extmarkline); } *lp += amount; @@ -1147,14 +1027,10 @@ void extmark_adjust(buf_T *buf, /// if part of a larger iteration we can't delete, then the caller /// must check for empty lines. bool extmark_copy_and_place(buf_T *buf, - linenr_T l_lnum, - colnr_T l_col, - linenr_T u_lnum, - colnr_T u_col, - linenr_T p_lnum, - colnr_T p_col, - ExtmarkOp undo, - bool delete, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col, + linenr_T p_lnum, colnr_T p_col, + ExtmarkOp undo, bool delete, ExtMarkLine *destline) { @@ -1166,17 +1042,17 @@ bool extmark_copy_and_place(buf_T *buf, // Move extmarks to their final position // Careful: if we move items within the same line, we might change order of - // marks within the same extline. Too keep it simple, first delete all items - // from the extline and put them back in the right order. + // marks within the same extmarkline. Too keep it simple, first delete all + // items from the extmarkline and put them back in the right order. FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { kvec_t(ExtendedMark) temp_space = KV_INITIAL_VALUE; - bool same_line = extline == destline; - FOR_ALL_EXTMARKS_IN_LINE(extline->items, - (extline->lnum > l_lnum) ? 0 : l_col, - (extline->lnum < u_lnum) ? MAXCOL : u_col, { + bool same_line = extmarkline == destline; + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, + (extmarkline->lnum > l_lnum) ? 0 : l_col, + (extmarkline->lnum < u_lnum) ? MAXCOL : u_col, { if (!destline) { - destline = extline_ref(buf, p_lnum, true); - same_line = extline == destline; + destline = extmarkline_ref(buf, p_lnum, true); + same_line = extmarkline == destline; } marks_moved = true; if (!same_line) { @@ -1188,17 +1064,17 @@ bool extmark_copy_and_place(buf_T *buf, kv_push(temp_space, *extmark); } // Delete old mark - kb_del_itr(markitems, &extline->items, &mitr); + kb_del_itr(markitems, &extmarkline->items, &mitr); }) if (same_line) { for (size_t i = 0; i < kv_size(temp_space); i++) { ExtendedMark mark = kv_A(temp_space, i); - extmark_put(p_col, mark.mark_id, extline, mark.ns_id); + extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id); } kv_destroy(temp_space); - } else if (delete && kb_size(&extline->items) == 0) { - kb_del_itr(extlines, &buf->b_extlines, &itr); - extline_free(extline); + } else if (delete && kb_size(&extmarkline->items) == 0) { + kb_del_itr(extmarklines, &buf->b_extlines, &itr); + extmarkline_free(extmarkline); } }) @@ -1211,13 +1087,13 @@ bool extmark_copy_and_place(buf_T *buf, } // Get reference to line in kbtree_t, allocating it if neccessary. -ExtMarkLine *extline_ref(buf_T *buf, linenr_T lnum, bool put) +ExtMarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) { - kbtree_t(extlines) *b = &buf->b_extlines; + kbtree_t(extmarklines) *b = &buf->b_extlines; ExtMarkLine t, **pp; t.lnum = lnum; - pp = kb_get(extlines, b, &t); + pp = kb_get(extmarklines, b, &t); if (!pp) { if (!put) { return NULL; @@ -1225,34 +1101,32 @@ ExtMarkLine *extline_ref(buf_T *buf, linenr_T lnum, bool put) ExtMarkLine *p = xcalloc(sizeof(ExtMarkLine), 1); p->lnum = lnum; // p->items zero initialized - kb_put(extlines, b, p); + kb_put(extmarklines, b, p); return p; } // Return existing return *pp; } -void extline_free(ExtMarkLine *extline) +void extmarkline_free(ExtMarkLine *extmarkline) { - kb_destroy(markitems, (&extline->items)); - xfree(extline); + kb_destroy(markitems, (&extmarkline->items)); + xfree(extmarkline); } /// Put an extmark into a line, /// /// caller must ensure combination of id and ns_id isn't in use. -void extmark_put(colnr_T col, - uint64_t id, - ExtMarkLine *extline, - uint64_t ns) +void extmark_put(colnr_T col, uint64_t id, + ExtMarkLine *extmarkline, uint64_t ns) { ExtendedMark t; t.col = col; t.mark_id = id; - t.line = extline; + t.line = extmarkline; t.ns_id = ns; - kbtree_t(markitems) *b = &(extline->items); + kbtree_t(markitems) *b = &(extmarkline->items); // kb_put requries the key to not be there assert(!kb_getp(markitems, b, &t)); diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h index 7f407a683c..b8b766b243 100644 --- a/src/nvim/mark_extended.h +++ b/src/nvim/mark_extended.h @@ -14,16 +14,17 @@ // see FOR_ALL_? for documentation #define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\ - kbitr_t(extlines) itr;\ + kbitr_t(extmarklines) itr;\ ExtMarkLine t;\ t.lnum = l_lnum;\ - if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \ - kb_itr_next(extlines, &buf->b_extlines, &itr);\ + if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ + kb_itr_next(extmarklines, &buf->b_extlines, &itr);\ }\ - ExtMarkLine *extline;\ - for (; kb_itr_valid(&itr); kb_itr_next(extlines, &buf->b_extlines, &itr)) { \ - extline = kb_itr_key(&itr);\ - if (extline->lnum > u_lnum) { \ + ExtMarkLine *extmarkline;\ + for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \ + &buf->b_extlines, &itr)) { \ + extmarkline = kb_itr_key(&itr);\ + if (extmarkline->lnum > u_lnum) { \ break;\ }\ code;\ @@ -31,16 +32,17 @@ // see FOR_ALL_? for documentation #define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\ - kbitr_t(extlines) itr;\ + kbitr_t(extmarklines) itr;\ ExtMarkLine t;\ t.lnum = u_lnum;\ - if (!kb_itr_get(extlines, &buf->b_extlines, &t, &itr)) { \ - kb_itr_prev(extlines, &buf->b_extlines, &itr);\ + if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ + kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\ }\ - ExtMarkLine *extline;\ - for (; kb_itr_valid(&itr); kb_itr_prev(extlines, &buf->b_extlines, &itr)) { \ - extline = kb_itr_key(&itr);\ - if (extline->lnum < l_lnum) { \ + ExtMarkLine *extmarkline;\ + for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \ + &buf->b_extlines, &itr)) { \ + extmarkline = kb_itr_key(&itr);\ + if (extmarkline->lnum < l_lnum) { \ break;\ }\ code;\ @@ -54,14 +56,14 @@ mt.mark_id = 0;\ mt.line = NULL;\ FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \ - mt.col = (extline->lnum != l_lnum) ? MINCOL : l_col;\ - if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \ - kb_itr_next(markitems, &extline->items, &mitr);\ + mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\ + if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ + kb_itr_next(markitems, &extmarkline->items, &mitr);\ } \ ExtendedMark *extmark;\ for (; \ kb_itr_valid(&mitr); \ - kb_itr_next(markitems, &extline->items, &mitr)) { \ + kb_itr_next(markitems, &extmarkline->items, &mitr)) { \ extmark = &kb_itr_key(&mitr);\ if (extmark->line->lnum == u_lnum \ && extmark->col > u_col) { \ @@ -79,14 +81,14 @@ mt.mark_id = sizeof(uint64_t);\ mt.ns_id = ns;\ FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \ - mt.col = (extline->lnum != u_lnum) ? MAXCOL : u_col;\ - if (!kb_itr_get(markitems, &extline->items, mt, &mitr)) { \ - kb_itr_prev(markitems, &extline->items, &mitr);\ + mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\ + if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ + kb_itr_prev(markitems, &extmarkline->items, &mitr);\ } \ ExtendedMark *extmark;\ for (; \ kb_itr_valid(&mitr); \ - kb_itr_prev(markitems, &extline->items, &mitr)) { \ + kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \ extmark = &kb_itr_key(&mitr);\ if (extmark->line->lnum == l_lnum \ && extmark->col < l_col) { \ @@ -104,14 +106,14 @@ mt.mark_id = 0;\ mt.line = NULL;\ mt.col = l_col;\ - colnr_T extline_u_col = u_col;\ + colnr_T extmarkline_u_col = u_col;\ if (!kb_itr_get(markitems, &items, mt, &mitr)) { \ kb_itr_next(markitems, &items, &mitr);\ } \ ExtendedMark *extmark;\ for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \ extmark = &kb_itr_key(&mitr);\ - if (extmark->col > extline_u_col) { \ + if (extmark->col > extmarkline_u_col) { \ break;\ }\ code;\ @@ -125,7 +127,6 @@ typedef struct ExtmarkNs { // For namespacing extmarks typedef kvec_t(ExtendedMark *) ExtmarkArray; -typedef kvec_t(ExtMarkLine *) ExtlineArray; // Undo/redo extmarks @@ -138,6 +139,7 @@ typedef enum { } ExtmarkOp; +// adjust line numbers only, corresponding to mark_adjust call typedef struct { linenr_T line1; linenr_T line2; @@ -145,6 +147,7 @@ typedef struct { long amount_after; } Adjust; +// adjust columns after split/join line, like mark_col_adjust typedef struct { linenr_T lnum; colnr_T mincol; @@ -152,6 +155,7 @@ typedef struct { long lnum_amount; } ColAdjust; +// delete the columns between mincol and endcol typedef struct { linenr_T lnum; colnr_T mincol; @@ -159,6 +163,7 @@ typedef struct { int eol; } ColAdjustDelete; +// adjust linenumbers after :move operation typedef struct { linenr_T line1; linenr_T line2; @@ -168,6 +173,9 @@ typedef struct { linenr_T extra; } AdjustMove; +// TODO(bfredl): reconsider if we really should track mark creation/updating +// itself, these are not really "edit" operation. +// extmark was created typedef struct { uint64_t ns_id; uint64_t mark_id; @@ -175,6 +183,7 @@ typedef struct { colnr_T col; } ExtmarkSet; +// extmark was updated typedef struct { uint64_t ns_id; uint64_t mark_id; @@ -184,6 +193,7 @@ typedef struct { colnr_T col; } ExtmarkUpdate; +// copied mark before deletion (as operation is destructive) typedef struct { uint64_t ns_id; uint64_t mark_id; @@ -191,6 +201,8 @@ typedef struct { colnr_T col; } ExtmarkCopy; +// also used as part of :move operation? probably can be simplified to one +// event. typedef struct { linenr_T l_lnum; colnr_T l_col; @@ -200,6 +212,8 @@ typedef struct { colnr_T p_col; } ExtmarkCopyPlace; +// extmark was cleared. +// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate typedef struct { uint64_t ns_id; linenr_T l_lnum; @@ -220,6 +234,7 @@ typedef enum { kExtmarkClear, } UndoObjectType; +// TODO(bfredl): reduce the number of undo action types struct undo_object { UndoObjectType type; union { diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h index 4da35a05c8..b9475d46f5 100644 --- a/src/nvim/mark_extended_defs.h +++ b/src/nvim/mark_extended_defs.h @@ -43,8 +43,8 @@ typedef struct ExtMarkLine kbtree_t(markitems) items; } ExtMarkLine; -#define extline_cmp(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum)) -KBTREE_INIT(extlines, ExtMarkLine *, extline_cmp, 10) +#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum)) +KBTREE_INIT(extmarklines, ExtMarkLine *, EXTMARKLINE_CMP, 10) typedef struct undo_object ExtmarkUndoObject; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 1631204840..95674f8b40 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -127,30 +127,6 @@ static char opchars[][3] = { Ctrl_X, NUL, false }, // OP_NR_SUB }; -char *nvim_lltoa(int64_t val, int base) -{ - static char buf[64] = { 0 }; - - int i = 62; - int sign = (val < 0); - if (sign) { - val = -val; - } - - if (val == 0) { - return "0"; - } - - for (; val && i ; i--, val /= base) { - buf[i] = "0123456789abcdef"[val % base]; - } - - if (sign) { - buf[i--] = '-'; - } - return &buf[i+1]; -} - /* * Translate a command name into an operator type. * Must only be called with a valid operator name! @@ -332,13 +308,8 @@ void shift_line( } else { (void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0); - colnr_T col_amount; colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw; - if (left) { - col_amount = -p_sw; - } else { - col_amount = p_sw; - } + colnr_T col_amount = left ? -p_sw : p_sw; extmark_col_adjust(curbuf, curwin->w_cursor.lnum, mincol, @@ -519,10 +490,7 @@ static void shift_block(oparg_T *oap, int amount) curwin->w_cursor.col = oldcol; p_ri = old_p_ri; - colnr_T col_amount = p_sw; - if (left) { - col_amount = -col_amount; - } + colnr_T col_amount = left ? -p_sw : p_sw; extmark_col_adjust(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col, 0, col_amount, kExtmarkUndo); } @@ -1669,7 +1637,7 @@ int op_delete(oparg_T *oap) curpos = curwin->w_cursor; // remember curwin->w_cursor curwin->w_cursor.lnum++; - del_lines(oap->line_count - 2, true); + del_lines(oap->line_count - 2, false); // delete from start of line until op_end n = (oap->end.col + 1 - !oap->inclusive); @@ -1715,12 +1683,7 @@ setmarks: lnum = curwin->w_cursor.lnum; if (oap->is_VIsual == false) { - // for some reason we required this :/ - endcol = endcol - 1; - // for some reason we required this :/ - if (endcol < mincol) { - endcol = mincol; - } + endcol = MAX(endcol - 1, mincol); } extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0); } @@ -2787,8 +2750,6 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) } - -// Function length couldn't be over 500 lines.. static void extmarks_do_put(int dir, size_t totlen, MotionType y_type, @@ -2796,12 +2757,7 @@ static void extmarks_do_put(int dir, colnr_T col) { // adjust extmarks - colnr_T col_amount; - if (dir == FORWARD) { - col_amount = (colnr_T)(totlen-1); - } else { - col_amount = (colnr_T)totlen; - } + colnr_T col_amount = (colnr_T)(dir == FORWARD ? totlen-1 : totlen); // Move extmark with char put if (y_type == kMTCharWise) { extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); @@ -3869,11 +3825,6 @@ int do_join(size_t count, * should not really be a problem. */ - linenr_T lnum; - colnr_T mincol; - long lnum_amount; - long col_amount; - for (t = (linenr_T)count - 1;; t--) { cend -= currsize; memmove(cend, curr, (size_t)currsize); @@ -3885,10 +3836,10 @@ int do_join(size_t count, // If deleting more spaces than adding, the cursor moves no more than // what is added if it is inside these spaces. const int spaces_removed = (int)((curr - curr_start) - spaces[t]); - lnum = curwin->w_cursor.lnum + t; - mincol = (colnr_T)0; - lnum_amount = (linenr_T)-t; - col_amount = (long)(cend - newp - spaces_removed); + linenr_T lnum = curwin->w_cursor.lnum + t; + colnr_T mincol = (colnr_T)0; + long lnum_amount = (linenr_T)-t; + long col_amount = (long)(cend - newp - spaces_removed); mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed, kExtmarkUndo); @@ -4675,7 +4626,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) { int col; - char_u *buf1; + char_u *buf1 = NULL; char_u buf2[NUMBUFLEN]; int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin static bool hexupper = false; // 0xABC @@ -4984,7 +4935,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) *ptr = NUL; STRCAT(buf1, buf2); ins_str(buf1); // insert the new number - xfree(buf1); endpos = curwin->w_cursor; if (curwin->w_cursor.col) { curwin->w_cursor.col--; @@ -4998,15 +4948,15 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curbuf->b_op_end.col--; } - if (did_change) { + // if buf1 wasn't allocated, only a singe ASCII char was changed in-place. + if (did_change && buf1 != NULL) { extmark_col_adjust_delete(curbuf, pos->lnum, startpos.col + 2, endpos.col + 1 + length, kExtmarkUndo, 0); - long col_amount = (long)strlen(nvim_lltoa((int64_t)n, 10)); - col_amount = negative ? col_amount + 1 : col_amount; + long col_amount = (long)STRLEN(buf1); extmark_col_adjust(curbuf, pos->lnum, startpos.col + 1, @@ -5016,6 +4966,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } theend: + xfree(buf1); if (visual) { curwin->w_cursor = save_cursor; } else if (did_change) { diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 91a142214b..b00d2d505f 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2849,7 +2849,6 @@ u_freeentries( u_freeentry(uep, uep->ue_size); } - // TODO(timeyyy): is this the correct place? ... kv_destroy(uhp->uh_extmark); #ifdef U_DEBUG @@ -3049,7 +3048,7 @@ list_T *u_eval_tree(const u_header_T *const first_uhp) // Given the buffer, Return the undo header. If none is set, set one first. // NULL will be returned if e.g undolevels = -1 (undo disabled) -u_header_T *force_get_undo_header(buf_T *buf) +u_header_T *u_force_get_undo_header(buf_T *buf) { u_header_T *uhp = NULL; if (buf->b_u_curhead != NULL) { @@ -3064,8 +3063,8 @@ u_header_T *force_get_undo_header(buf_T *buf) uhp = buf->b_u_curhead; if (!uhp) { uhp = buf->b_u_newhead; - if (get_undolevel() > 0) { - assert(uhp); + if (get_undolevel() > 0 && !uhp) { + abort(); } } } -- cgit From 8d70335b00484055fba6e3c1e1bb90acef0bb1ae Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 12 Nov 2019 00:24:25 -0500 Subject: vim-patch:8.1.0622: adding quickfix items marks items as valid errors #11373 Problem: Adding quickfix items marks items as valid errors. (Daniel Hahler) Solution: Check when items are valid. (Yegappan Lakshmanan, closes vim/vim#3683, closes vim/vim#3633) https://github.com/vim/vim/commit/9752c72f492312acd1c84e673864faed31a3bc97 --- src/nvim/quickfix.c | 34 ++++++++++++++++++++++++---------- src/nvim/testdir/test_quickfix.vim | 22 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index da315252b5..276427f216 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5779,11 +5779,13 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) } /// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the -/// items in the dict 'd'. +/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry' +/// to true. static int qf_add_entry_from_dict( qf_list_T *qfl, const dict_T *d, - bool first_entry) + bool first_entry, + bool *valid_entry) FUNC_ATTR_NONNULL_ALL { static bool did_bufnr_emsg; @@ -5846,6 +5848,10 @@ static int qf_add_entry_from_dict( xfree(pattern); xfree(text); + if (valid) { + *valid_entry = true; + } + return status; } @@ -5857,6 +5863,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, qf_list_T *qfl = qf_get_list(qi, qf_idx); qfline_T *old_last = NULL; int retval = OK; + bool valid_entry = false; if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list @@ -5881,23 +5888,30 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, continue; } - retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list)); + retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list), + &valid_entry); if (retval == QF_FAIL) { break; } }); - if (qfl->qf_index == 0) { - // no valid entry - qfl->qf_nonevalid = true; - } else { + // Check if any valid error entries are added to the list. + if (valid_entry) { qfl->qf_nonevalid = false; + } else if (qfl->qf_index == 0) { + qfl->qf_nonevalid = true; } + + // If not appending to the list, set the current error to the first entry if (action != 'a') { qfl->qf_ptr = qfl->qf_start; - if (!qf_list_empty(qfl)) { - qfl->qf_index = 1; - } + } + + // Update the current error index if not appending to the list or if the + // list was empty before and it is not empty now. + if ((action != 'a' || qfl->qf_index == 0) + && !qf_list_empty(qfl)) { + qfl->qf_index = 1; } // Don't update the cursor in quickfix window when appending entries diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 8949b3d968..15cbf52cb5 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1320,6 +1320,28 @@ func SetXlistTests(cchar, bnum) let l = g:Xgetlist() call g:Xsetlist(l) call assert_equal(0, g:Xgetlist()[0].valid) + " Adding a non-valid entry should not mark the list as having valid entries + call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a') + Xwindow + call assert_equal(1, winnr('$')) + + " :cnext/:cprev should still work even with invalid entries in the list + let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0}, + \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}] + call g:Xsetlist(l) + Xnext + call assert_equal(2, g:Xgetlist({'idx' : 0}).idx) + Xprev + call assert_equal(1, g:Xgetlist({'idx' : 0}).idx) + " :cnext/:cprev should still work after appending invalid entries to an + " empty list + call g:Xsetlist([]) + call g:Xsetlist(l, 'a') + Xnext + call assert_equal(2, g:Xgetlist({'idx' : 0}).idx) + Xprev + call assert_equal(1, g:Xgetlist({'idx' : 0}).idx) + call g:Xsetlist([{'text':'Text1', 'valid':1}]) Xwindow call assert_equal(2, winnr('$')) -- cgit From 54473e9a677b001b47b1fe528b6056e9feed0f60 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 10 Nov 2019 16:52:14 -0800 Subject: doc [ci skip] --- src/nvim/api/buffer.c | 2 +- src/nvim/ex_cmds.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 5909fd7c02..58011702ac 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1058,7 +1058,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// @param opts additional options. Supports the keys: /// - amount: Maximum number of marks to return /// @param[out] err Details of an error that may have occurred -/// @return [[nsmark_id, row, col], ...] +/// @return [[extmark_id, row, col], ...] Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts, Error *err) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index c5f7815fc5..4725246764 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3457,9 +3457,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, linenr_T newline_in_pat = 0; linenr_T newline_in_sub = 0; - // inccomand tests fail without this check + // inccommand tests fail without this check if (!preview) { - // Requried for Undo to work for nsmarks, + // Required for Undo to work for extmarks. u_save_cursor(); } -- cgit From 2d7e1c32a87655b78cd23f2a3ad9a7a140493bd5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Nov 2019 22:04:21 -0800 Subject: extmark: rename ExtendedMark => Extmark --- src/nvim/api/buffer.c | 6 ++---- src/nvim/api/private/helpers.c | 12 ++++-------- src/nvim/mark_extended.c | 19 +++++++++---------- src/nvim/mark_extended.h | 14 +++++++------- src/nvim/mark_extended_defs.h | 8 ++++---- 5 files changed, 26 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 58011702ac..9ec96840d1 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1027,9 +1027,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } - ExtendedMark *extmark = extmark_from_id(buf, - (uint64_t)ns_id, - (uint64_t)id); + Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); if (!extmark) { return rv; } @@ -1129,7 +1127,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, for (size_t i = 0; i < kv_size(marks); i++) { Array mark = ARRAY_DICT_INIT; - ExtendedMark *extmark = kv_A(marks, i); + Extmark *extmark = kv_A(marks, i); ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id)); ADD(mark, INTEGER_OBJ(extmark->line->lnum-1)); ADD(mark, INTEGER_OBJ(extmark->col-1)); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 6b69350429..fbfdb27827 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1512,11 +1512,8 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) // If throw == true then an error will be raised if nothing // was found // Returns NULL if something went wrong -ExtendedMark *extmark_from_id_or_pos(Buffer buffer, - Integer namespace, - Object id, - Error *err, - bool throw) +Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, + Error *err, bool throw) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1524,7 +1521,7 @@ ExtendedMark *extmark_from_id_or_pos(Buffer buffer, return NULL; } - ExtendedMark *extmark = NULL; + Extmark *extmark = NULL; if (id.type == kObjectTypeArray) { if (id.data.array.size != 2) { api_set_error(err, kErrorTypeValidation, @@ -1603,8 +1600,7 @@ bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, return false; } - ExtendedMark *extmark = extmark_from_id(buf, (uint64_t)namespace, - (uint64_t)id); + Extmark *extmark = extmark_from_id(buf, (uint64_t)namespace, (uint64_t)id); if (extmark) { *lnum = extmark->line->lnum; *colnr = extmark->col; diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index 88348a8045..545478065e 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -53,7 +53,7 @@ int extmark_set(buf_T *buf, uint64_t ns, uint64_t id, linenr_T lnum, colnr_T col, ExtmarkOp op) { - ExtendedMark *extmark = extmark_from_id(buf, ns, id); + Extmark *extmark = extmark_from_id(buf, ns, id); if (!extmark) { extmark_create(buf, ns, id, lnum, col, op); return true; @@ -72,7 +72,7 @@ int extmark_set(buf_T *buf, uint64_t ns, uint64_t id, // Returns 0 on missing id int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op) { - ExtendedMark *extmark = extmark_from_id(buf, ns, id); + Extmark *extmark = extmark_from_id(buf, ns, id); if (!extmark) { return 0; } @@ -197,7 +197,7 @@ static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id, // update the position of an extmark // to update while iterating pass the markitems itr -static void extmark_update(ExtendedMark *extmark, buf_T *buf, +static void extmark_update(Extmark *extmark, buf_T *buf, uint64_t ns, uint64_t id, linenr_T lnum, colnr_T col, ExtmarkOp op, kbitr_t(markitems) *mitr) @@ -234,7 +234,7 @@ static void extmark_update(ExtendedMark *extmark, buf_T *buf, } } -static int extmark_delete(ExtendedMark *extmark, +static int extmark_delete(Extmark *extmark, buf_T *buf, uint64_t ns, uint64_t id, @@ -261,7 +261,7 @@ static int extmark_delete(ExtendedMark *extmark, } // Lookup an extmark by id -ExtendedMark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) +Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) { if (!buf->b_extmark_ns) { return NULL; @@ -285,8 +285,7 @@ ExtendedMark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) } // Lookup an extmark by position -ExtendedMark *extmark_from_pos(buf_T *buf, - uint64_t ns, linenr_T lnum, colnr_T col) +Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col) { if (!buf->b_extmark_ns) { return NULL; @@ -1045,7 +1044,7 @@ bool extmark_copy_and_place(buf_T *buf, // marks within the same extmarkline. Too keep it simple, first delete all // items from the extmarkline and put them back in the right order. FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { - kvec_t(ExtendedMark) temp_space = KV_INITIAL_VALUE; + kvec_t(Extmark) temp_space = KV_INITIAL_VALUE; bool same_line = extmarkline == destline; FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, (extmarkline->lnum > l_lnum) ? 0 : l_col, @@ -1068,7 +1067,7 @@ bool extmark_copy_and_place(buf_T *buf, }) if (same_line) { for (size_t i = 0; i < kv_size(temp_space); i++) { - ExtendedMark mark = kv_A(temp_space, i); + Extmark mark = kv_A(temp_space, i); extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id); } kv_destroy(temp_space); @@ -1120,7 +1119,7 @@ void extmarkline_free(ExtMarkLine *extmarkline) void extmark_put(colnr_T col, uint64_t id, ExtMarkLine *extmarkline, uint64_t ns) { - ExtendedMark t; + Extmark t; t.col = col; t.mark_id = id; t.line = extmarkline; diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h index b8b766b243..b81adfaaef 100644 --- a/src/nvim/mark_extended.h +++ b/src/nvim/mark_extended.h @@ -51,7 +51,7 @@ // see FOR_ALL_? for documentation #define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ kbitr_t(markitems) mitr;\ - ExtendedMark mt;\ + Extmark mt;\ mt.ns_id = ns;\ mt.mark_id = 0;\ mt.line = NULL;\ @@ -60,7 +60,7 @@ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ kb_itr_next(markitems, &extmarkline->items, &mitr);\ } \ - ExtendedMark *extmark;\ + Extmark *extmark;\ for (; \ kb_itr_valid(&mitr); \ kb_itr_next(markitems, &extmarkline->items, &mitr)) { \ @@ -77,7 +77,7 @@ // see FOR_ALL_? for documentation #define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ kbitr_t(markitems) mitr;\ - ExtendedMark mt;\ + Extmark mt;\ mt.mark_id = sizeof(uint64_t);\ mt.ns_id = ns;\ FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \ @@ -85,7 +85,7 @@ if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ kb_itr_prev(markitems, &extmarkline->items, &mitr);\ } \ - ExtendedMark *extmark;\ + Extmark *extmark;\ for (; \ kb_itr_valid(&mitr); \ kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \ @@ -101,7 +101,7 @@ #define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\ kbitr_t(markitems) mitr;\ - ExtendedMark mt;\ + Extmark mt;\ mt.ns_id = 0;\ mt.mark_id = 0;\ mt.line = NULL;\ @@ -110,7 +110,7 @@ if (!kb_itr_get(markitems, &items, mt, &mitr)) { \ kb_itr_next(markitems, &items, &mitr);\ } \ - ExtendedMark *extmark;\ + Extmark *extmark;\ for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \ extmark = &kb_itr_key(&mitr);\ if (extmark->col > extmarkline_u_col) { \ @@ -126,7 +126,7 @@ typedef struct ExtmarkNs { // For namespacing extmarks } ExtmarkNs; -typedef kvec_t(ExtendedMark *) ExtmarkArray; +typedef kvec_t(Extmark *) ExtmarkArray; // Undo/redo extmarks diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h index b9475d46f5..42d57229f0 100644 --- a/src/nvim/mark_extended_defs.h +++ b/src/nvim/mark_extended_defs.h @@ -8,19 +8,19 @@ struct ExtMarkLine; -typedef struct ExtendedMark +typedef struct Extmark { uint64_t ns_id; uint64_t mark_id; struct ExtMarkLine *line; colnr_T col; -} ExtendedMark; +} Extmark; // We only need to compare columns as rows are stored in a different tree. // Marks are ordered by: position, namespace, mark_id // This improves moving marks but slows down all other use cases (searches) -static inline int extmark_cmp(ExtendedMark a, ExtendedMark b) +static inline int extmark_cmp(Extmark a, Extmark b) { int cmp = kb_generic_cmp(a.col, b.col); if (cmp != 0) { @@ -35,7 +35,7 @@ static inline int extmark_cmp(ExtendedMark a, ExtendedMark b) #define markitems_cmp(a, b) (extmark_cmp((a), (b))) -KBTREE_INIT(markitems, ExtendedMark, markitems_cmp, 10) +KBTREE_INIT(markitems, Extmark, markitems_cmp, 10) typedef struct ExtMarkLine { -- cgit From a24eff0e2762657b4a82b2a7e78b4246f6462f95 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 11 Nov 2019 22:10:37 -0800 Subject: extmark: fix spelling of "Extmark" The spelling "Extmark" is used almost everywhere, so don't spell it "ExtMark" inconsistently. --- src/nvim/buffer_defs.h | 2 +- src/nvim/mark_extended.c | 30 +++++++++++++++--------------- src/nvim/mark_extended.h | 8 ++++---- src/nvim/mark_extended_defs.h | 10 +++++----- 4 files changed, 25 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 29eb32d40a..3b8e5b5632 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -810,7 +810,7 @@ struct file_buffer { PMap(uint64_t) *b_extmark_ns; // extmark namespaces kbtree_t(extmarklines) b_extlines; // extmarks - kvec_t(ExtMarkLine *) b_extmark_move_space; // temp space for extmarks + kvec_t(ExtmarkLine *) b_extmark_move_space; // temp space for extmarks // array of channel_id:s which have asked to receive updates for this // buffer. diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index 545478065e..01745f484d 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -58,7 +58,7 @@ int extmark_set(buf_T *buf, uint64_t ns, uint64_t id, extmark_create(buf, ns, id, lnum, col, op); return true; } else { - ExtMarkLine *extmarkline = extmark->line; + ExtmarkLine *extmarkline = extmark->line; extmark_update(extmark, buf, ns, id, lnum, col, op, NULL); if (kb_size(&extmarkline->items) == 0) { kb_del(extmarklines, &buf->b_extlines, extmarkline); @@ -180,7 +180,7 @@ static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id, } // Create or get a line - ExtMarkLine *extmarkline = extmarkline_ref(buf, lnum, true); + ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, true); // Create and put mark on the line extmark_put(col, id, extmarkline, ns); @@ -207,10 +207,10 @@ static void extmark_update(Extmark *extmark, buf_T *buf, u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col, lnum, col); } - ExtMarkLine *old_line = extmark->line; + ExtmarkLine *old_line = extmark->line; // Move the mark to a new line and update column if (old_line->lnum != lnum) { - ExtMarkLine *ref_line = extmarkline_ref(buf, lnum, true); + ExtmarkLine *ref_line = extmarkline_ref(buf, lnum, true); extmark_put(col, id, ref_line, ns); // Update the hashmap ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); @@ -250,7 +250,7 @@ static int extmark_delete(Extmark *extmark, pmap_del(uint64_t)(ns_obj->map, id); // Remove the mark mark from the line - ExtMarkLine *extmarkline = extmark->line; + ExtmarkLine *extmarkline = extmark->line; kb_del(markitems, &extmarkline->items, *extmark); // Remove the line if there are no more marks in the line if (kb_size(&extmarkline->items) == 0) { @@ -270,7 +270,7 @@ Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) if (!ns_obj || !kh_size(ns_obj->map->table)) { return NULL; } - ExtMarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id); + ExtmarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id); if (!extmarkline) { return NULL; } @@ -841,7 +841,7 @@ static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum, { bool marks_exist = false; - ExtMarkLine *extmarkline = extmarkline_ref(buf, lnum, false); + ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false); if (!extmarkline) { return false; } @@ -967,7 +967,7 @@ void extmark_adjust(buf_T *buf, ExtmarkOp undo, bool end_temp) { - ExtMarkLine *_extline; + ExtmarkLine *_extline; // btree needs to be kept ordered to work, so far only :move requires this // 2nd call with end_temp = true unpack the lines from the temp position @@ -989,7 +989,7 @@ void extmark_adjust(buf_T *buf, // Careful! marks from deleted region can end up on en extisting extmarkline // that is goinig to be adjusted to the target position. linenr_T join_num = line1 - amount_after; - ExtMarkLine *joinline = (join_num > line2 + ExtmarkLine *joinline = (join_num > line2 ? extmarkline_ref(buf, join_num, false) : NULL); // extmark_adjust is already redoable, the copy should only be for undo @@ -1030,7 +1030,7 @@ bool extmark_copy_and_place(buf_T *buf, linenr_T u_lnum, colnr_T u_col, linenr_T p_lnum, colnr_T p_col, ExtmarkOp undo, bool delete, - ExtMarkLine *destline) + ExtmarkLine *destline) { bool marks_moved = false; @@ -1086,10 +1086,10 @@ bool extmark_copy_and_place(buf_T *buf, } // Get reference to line in kbtree_t, allocating it if neccessary. -ExtMarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) +ExtmarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) { kbtree_t(extmarklines) *b = &buf->b_extlines; - ExtMarkLine t, **pp; + ExtmarkLine t, **pp; t.lnum = lnum; pp = kb_get(extmarklines, b, &t); @@ -1097,7 +1097,7 @@ ExtMarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) if (!put) { return NULL; } - ExtMarkLine *p = xcalloc(sizeof(ExtMarkLine), 1); + ExtmarkLine *p = xcalloc(sizeof(ExtmarkLine), 1); p->lnum = lnum; // p->items zero initialized kb_put(extmarklines, b, p); @@ -1107,7 +1107,7 @@ ExtMarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) return *pp; } -void extmarkline_free(ExtMarkLine *extmarkline) +void extmarkline_free(ExtmarkLine *extmarkline) { kb_destroy(markitems, (&extmarkline->items)); xfree(extmarkline); @@ -1117,7 +1117,7 @@ void extmarkline_free(ExtMarkLine *extmarkline) /// /// caller must ensure combination of id and ns_id isn't in use. void extmark_put(colnr_T col, uint64_t id, - ExtMarkLine *extmarkline, uint64_t ns) + ExtmarkLine *extmarkline, uint64_t ns) { Extmark t; t.col = col; diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h index b81adfaaef..ee1da26875 100644 --- a/src/nvim/mark_extended.h +++ b/src/nvim/mark_extended.h @@ -15,12 +15,12 @@ // see FOR_ALL_? for documentation #define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\ kbitr_t(extmarklines) itr;\ - ExtMarkLine t;\ + ExtmarkLine t;\ t.lnum = l_lnum;\ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ kb_itr_next(extmarklines, &buf->b_extlines, &itr);\ }\ - ExtMarkLine *extmarkline;\ + ExtmarkLine *extmarkline;\ for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \ &buf->b_extlines, &itr)) { \ extmarkline = kb_itr_key(&itr);\ @@ -33,12 +33,12 @@ // see FOR_ALL_? for documentation #define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\ kbitr_t(extmarklines) itr;\ - ExtMarkLine t;\ + ExtmarkLine t;\ t.lnum = u_lnum;\ if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\ }\ - ExtMarkLine *extmarkline;\ + ExtmarkLine *extmarkline;\ for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \ &buf->b_extlines, &itr)) { \ extmarkline = kb_itr_key(&itr);\ diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h index 42d57229f0..565c599d06 100644 --- a/src/nvim/mark_extended_defs.h +++ b/src/nvim/mark_extended_defs.h @@ -6,13 +6,13 @@ #include "nvim/lib/kbtree.h" #include "nvim/lib/kvec.h" -struct ExtMarkLine; +struct ExtmarkLine; typedef struct Extmark { uint64_t ns_id; uint64_t mark_id; - struct ExtMarkLine *line; + struct ExtmarkLine *line; colnr_T col; } Extmark; @@ -37,14 +37,14 @@ static inline int extmark_cmp(Extmark a, Extmark b) #define markitems_cmp(a, b) (extmark_cmp((a), (b))) KBTREE_INIT(markitems, Extmark, markitems_cmp, 10) -typedef struct ExtMarkLine +typedef struct ExtmarkLine { linenr_T lnum; kbtree_t(markitems) items; -} ExtMarkLine; +} ExtmarkLine; #define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum)) -KBTREE_INIT(extmarklines, ExtMarkLine *, EXTMARKLINE_CMP, 10) +KBTREE_INIT(extmarklines, ExtmarkLine *, EXTMARKLINE_CMP, 10) typedef struct undo_object ExtmarkUndoObject; -- cgit From 00dc12c5d8454a2d3c6806710f63bbb446076e96 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 13 Nov 2019 12:55:26 -0800 Subject: lua LSP client: initial implementation (#11336) Mainly configuration and RPC infrastructure can be considered "done". Specific requests and their callbacks will be improved later (and also served by plugins). There are also some TODO:s for the client itself, like incremental updates. Co-authored by at-tjdevries and at-h-michael, with many review/suggestion contributions. --- src/nvim/lua/vim.lua | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 2b4c5486c7..ce24d1716d 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -256,6 +256,13 @@ local function __index(t, key) -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] return t[key] + elseif require('vim.uri')[key] ~= nil then + -- Expose all `vim.uri` functions on the `vim` module. + t[key] = require('vim.uri')[key] + return t[key] + elseif key == 'lsp' then + t.lsp = require('vim.lsp') + return t.lsp end end -- cgit From c512dffb55b259d50fb91326e40a7b4da95e525b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 30 Oct 2019 20:36:53 -0400 Subject: vim-patch:8.1.0266: parsing Ex address range is not a separate function Problem: Parsing Ex address range is not a separate function. Solution: Refactor do_one_cmd() to separate address parsing. https://github.com/vim/vim/commit/ee8415bc5998792fab6f4dcf289d027856e05b89 --- src/nvim/ex_docmd.c | 314 +++++++++++++++++++++++++++------------------------- src/nvim/mark.c | 6 +- 2 files changed, 166 insertions(+), 154 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 7d02623d67..c48e5e0a4e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1255,7 +1255,6 @@ static char_u * do_one_cmd(char_u **cmdlinep, cmdmod_T save_cmdmod; const int save_reg_executing = reg_executing; char_u *cmd; - int address_count = 1; memset(&ea, 0, sizeof(ea)); ea.line1 = 1; @@ -1571,148 +1570,9 @@ static char_u * do_one_cmd(char_u **cmdlinep, } } - /* repeat for all ',' or ';' separated addresses */ ea.cmd = cmd; - for (;; ) { - ea.line1 = ea.line2; - switch (ea.addr_type) { - case ADDR_LINES: - // default is current line number - ea.line2 = curwin->w_cursor.lnum; - break; - case ADDR_WINDOWS: - ea.line2 = CURRENT_WIN_NR; - break; - case ADDR_ARGUMENTS: - ea.line2 = curwin->w_arg_idx + 1; - if (ea.line2 > ARGCOUNT) { - ea.line2 = ARGCOUNT; - } - break; - case ADDR_LOADED_BUFFERS: - case ADDR_BUFFERS: - ea.line2 = curbuf->b_fnum; - break; - case ADDR_TABS: - ea.line2 = CURRENT_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - ea.line2 = 1; - break; - case ADDR_QUICKFIX: - ea.line2 = qf_get_cur_valid_idx(&ea); - break; - } - ea.cmd = skipwhite(ea.cmd); - lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip, - ea.addr_count == 0, address_count++); - if (ea.cmd == NULL) { // error detected - goto doend; - } - if (lnum == MAXLNUM) { - if (*ea.cmd == '%') { /* '%' - all lines */ - ++ea.cmd; - switch (ea.addr_type) { - case ADDR_LINES: - ea.line1 = 1; - ea.line2 = curbuf->b_ml.ml_line_count; - break; - case ADDR_LOADED_BUFFERS: { - buf_T *buf = firstbuf; - while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_next; - } - ea.line1 = buf->b_fnum; - buf = lastbuf; - while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_prev; - } - ea.line2 = buf->b_fnum; - break; - } - case ADDR_BUFFERS: - ea.line1 = firstbuf->b_fnum; - ea.line2 = lastbuf->b_fnum; - break; - case ADDR_WINDOWS: - case ADDR_TABS: - if (IS_USER_CMDIDX(ea.cmdidx)) { - ea.line1 = 1; - ea.line2 = - ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR; - } else { - // there is no Vim command which uses '%' and - // ADDR_WINDOWS or ADDR_TABS - errormsg = (char_u *)_(e_invrange); - goto doend; - } - break; - case ADDR_TABS_RELATIVE: - errormsg = (char_u *)_(e_invrange); - goto doend; - break; - case ADDR_ARGUMENTS: - if (ARGCOUNT == 0) { - ea.line1 = ea.line2 = 0; - } else { - ea.line1 = 1; - ea.line2 = ARGCOUNT; - } - break; - case ADDR_QUICKFIX: - ea.line1 = 1; - ea.line2 = qf_get_size(&ea); - if (ea.line2 == 0) { - ea.line2 = 1; - } - break; - } - ++ea.addr_count; - } - /* '*' - visual area */ - else if (*ea.cmd == '*') { - pos_T *fp; - - if (ea.addr_type != ADDR_LINES) { - errormsg = (char_u *)_(e_invrange); - goto doend; - } - - ++ea.cmd; - if (!ea.skip) { - fp = getmark('<', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line1 = fp->lnum; - fp = getmark('>', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line2 = fp->lnum; - ++ea.addr_count; - } - } - } else - ea.line2 = lnum; - ea.addr_count++; - - if (*ea.cmd == ';') { - if (!ea.skip) { - curwin->w_cursor.lnum = ea.line2; - // don't leave the cursor on an illegal line or column - check_cursor(); - } - } else if (*ea.cmd != ',') { - break; - } - ea.cmd++; - } - - /* One address given: set start and end lines */ - if (ea.addr_count == 1) { - ea.line1 = ea.line2; - /* ... but only implicit: really no address given */ - if (lnum == MAXLNUM) - ea.addr_count = 0; + if (parse_cmd_address(&ea, &errormsg) == FAIL) { + goto doend; } /* @@ -2376,6 +2236,160 @@ doend: return ea.nextcmd; } +// Parse the address range, if any, in "eap". +// Return FAIL and set "errormsg" or return OK. +int parse_cmd_address(exarg_T *eap, char_u **errormsg) + FUNC_ATTR_NONNULL_ALL +{ + int address_count = 1; + linenr_T lnum; + + // Repeat for all ',' or ';' separated addresses. + for (;;) { + eap->line1 = eap->line2; + switch (eap->addr_type) { + case ADDR_LINES: + // default is current line number + eap->line2 = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + eap->line2 = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + eap->line2 = curwin->w_arg_idx + 1; + if (eap->line2 > ARGCOUNT) { + eap->line2 = ARGCOUNT; + } + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + eap->line2 = curbuf->b_fnum; + break; + case ADDR_TABS: + eap->line2 = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + eap->line2 = 1; + break; + case ADDR_QUICKFIX: + eap->line2 = qf_get_cur_valid_idx(eap); + break; + } + eap->cmd = skipwhite(eap->cmd); + lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, + eap->addr_count == 0, address_count++); + if (eap->cmd == NULL) { // error detected + return FAIL; + } + if (lnum == MAXLNUM) { + if (*eap->cmd == '%') { // '%' - all lines + eap->cmd++; + switch (eap->addr_type) { + case ADDR_LINES: + eap->line1 = 1; + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_next; + } + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_prev; + } + eap->line2 = buf->b_fnum; + break; + } + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + case ADDR_TABS: + if (IS_USER_CMDIDX(eap->cmdidx)) { + eap->line1 = 1; + eap->line2 = eap->addr_type == ADDR_WINDOWS + ? LAST_WIN_NR : LAST_TAB_NR; + } else { + // there is no Vim command which uses '%' and + // ADDR_WINDOWS or ADDR_TABS + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + break; + case ADDR_TABS_RELATIVE: + *errormsg = (char_u *)_(e_invrange); + return FAIL; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) { + eap->line1 = eap->line2 = 0; + } else { + eap->line1 = 1; + eap->line2 = ARGCOUNT; + } + break; + case ADDR_QUICKFIX: + eap->line1 = 1; + eap->line2 = qf_get_size(eap); + if (eap->line2 == 0) { + eap->line2 = 1; + } + break; + } + eap->addr_count++; + } else if (*eap->cmd == '*') { + // '*' - visual area + if (eap->addr_type != ADDR_LINES) { + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + + eap->cmd++; + if (!eap->skip) { + pos_T *fp = getmark('<', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line1 = fp->lnum; + fp = getmark('>', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line2 = fp->lnum; + eap->addr_count++; + } + } + } else { + eap->line2 = lnum; + } + eap->addr_count++; + + if (*eap->cmd == ';') { + if (!eap->skip) { + curwin->w_cursor.lnum = eap->line2; + // don't leave the cursor on an illegal line or column + check_cursor(); + } + } else if (*eap->cmd != ',') { + break; + } + eap->cmd++; + } + + // One address given: set start and end lines. + if (eap->addr_count == 1) { + eap->line1 = eap->line2; + // ... but only implicit: really no address given + if (lnum == MAXLNUM) { + eap->addr_count = 0; + } + } + return OK; +} + /* * Check for an Ex command with optional tail. * If there is a match advance "pp" to the argument and return TRUE. @@ -3556,15 +3570,13 @@ const char * set_one_cmd_context( return NULL; } -/* - * skip a range specifier of the form: addr [,addr] [;addr] .. - * - * Backslashed delimiters after / or ? will be skipped, and commands will - * not be expanded between /'s and ?'s or after "'". - * - * Also skip white space and ":" characters. - * Returns the "cmd" pointer advanced to beyond the range. - */ +// Skip a range specifier of the form: addr [,addr] [;addr] .. +// +// Backslashed delimiters after / or ? will be skipped, and commands will +// not be expanded between /'s and ?'s or after "'". +// +// Also skip white space and ":" characters. +// Returns the "cmd" pointer advanced to beyond the range. char_u *skip_range( const char_u *cmd, int *ctx // pointer to xp_context or NULL diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e8f1651a6e..432639d540 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -296,17 +296,17 @@ pos_T *movechangelist(int count) * - NULL if there is no mark called 'c'. * - -1 if mark is in other file and jumped there (only if changefile is TRUE) */ -pos_T *getmark_buf(buf_T *buf, int c, int changefile) +pos_T *getmark_buf(buf_T *buf, int c, bool changefile) { return getmark_buf_fnum(buf, c, changefile, NULL); } -pos_T *getmark(int c, int changefile) +pos_T *getmark(int c, bool changefile) { return getmark_buf_fnum(curbuf, c, changefile, NULL); } -pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) +pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum) { pos_T *posp; pos_T *startp, *endp; -- cgit From 63abe3ca196aa0c889b8cf295e3a6c6c0c20cf2b Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 5 Nov 2019 20:13:06 +0000 Subject: Factor out parse_one_cmd() This will allow us to reuse the parsing logic elsewhere, namely for inccommand logic. --- src/nvim/ex_docmd.c | 411 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 259 insertions(+), 152 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c48e5e0a4e..b826bb3262 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -140,6 +140,31 @@ struct dbg_stuff { except_T *current_exception; }; +typedef struct { + // parsed results + exarg_T *eap; + char_u *parsed_upto; // local we've parsed up to so far + char_u *cmd; // start of command + char_u *after_modifier; + + // errors + char_u *errormsg; + + // globals that need to be updated + cmdmod_T cmdmod; + int sandbox; + int msg_silent; + int emsg_silent; + bool ex_pressedreturn; + long p_verbose; + + // other side-effects + bool set_eventignore; + long verbose_save; + int save_msg_silent; + int did_esilent; + bool did_sandbox; +} parse_state_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.c.generated.h" @@ -1218,68 +1243,57 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) return (char_u *)p; } -/* - * Execute one Ex command. - * - * If 'sourcing' is TRUE, the command will be included in the error message. - * - * 1. skip comment lines and leading space - * 2. handle command modifiers - * 3. skip over the range to find the command - * 4. parse the range - * 5. parse the command - * 6. parse arguments - * 7. switch on command name - * - * Note: "fgetline" can be NULL. - * - * This function may be called recursively! - */ -static char_u * do_one_cmd(char_u **cmdlinep, - int flags, - struct condstack *cstack, - LineGetter fgetline, - void *cookie /* argument for fgetline() */ - ) +static void parse_state_to_global(const parse_state_T *parse_state) { - char_u *p; - linenr_T lnum; - long n; - char_u *errormsg = NULL; /* error message */ - exarg_T ea; /* Ex command arguments */ - long verbose_save = -1; - int save_msg_scroll = msg_scroll; - int save_msg_silent = -1; - int did_esilent = 0; - int did_sandbox = FALSE; - cmdmod_T save_cmdmod; - const int save_reg_executing = reg_executing; - char_u *cmd; + cmdmod = parse_state->cmdmod; + sandbox = parse_state->sandbox; + msg_silent = parse_state->msg_silent; + emsg_silent = parse_state->emsg_silent; + ex_pressedreturn = parse_state->ex_pressedreturn; + p_verbose = parse_state->p_verbose; - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; - ex_nesting_level++; + if (parse_state->set_eventignore) { + set_string_option_direct( + (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + } +} - /* When the last file has not been edited :q has to be typed twice. */ - if (quitmore - /* avoid that a function call in 'statusline' does this */ - && !getline_equal(fgetline, cookie, get_func_line) - /* avoid that an autocommand, e.g. QuitPre, does this */ - && !getline_equal(fgetline, cookie, getnextac) - ) - --quitmore; +static void parse_state_from_global(parse_state_T *parse_state) +{ + memset(parse_state, 0, sizeof(*parse_state)); + parse_state->cmdmod = cmdmod; + parse_state->sandbox = sandbox; + parse_state->msg_silent = msg_silent; + parse_state->emsg_silent = emsg_silent; + parse_state->ex_pressedreturn = ex_pressedreturn; + parse_state->p_verbose = p_verbose; +} - /* - * Reset browse, confirm, etc.. They are restored when returning, for - * recursive calls. - */ - save_cmdmod = cmdmod; - memset(&cmdmod, 0, sizeof(cmdmod)); +// +// Parse one Ex command. +// +// This has no side-effects, except for modifying parameters +// passed in by pointer. +// +// The `out` should be zeroed, and its `ea` member initialised, +// before calling this function. +// +static bool parse_one_cmd( + char_u **cmdlinep, + parse_state_T *const out, + LineGetter fgetline, + void *fgetline_cookie) +{ + exarg_T ea = { + .line1 = 1, + .line2 = 1, + }; + *out->eap = ea; - /* "#!anything" is handled like a comment. */ - if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') - goto doend; + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { + return false; + } /* * Repeat until no more command modifiers are found. @@ -1289,70 +1303,76 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * 1. Skip comment lines and leading white space and colons. */ - while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') - ++ea.cmd; + while (*ea.cmd == ' ' + || *ea.cmd == '\t' + || *ea.cmd == ':') { + ea.cmd++; + } - /* in ex mode, an empty line works like :+ */ + // in ex mode, an empty line works like :+ if (*ea.cmd == NUL && exmode_active - && (getline_equal(fgetline, cookie, getexmodeline) - || getline_equal(fgetline, cookie, getexline)) + && (getline_equal(fgetline, fgetline_cookie, getexmodeline) + || getline_equal(fgetline, fgetline_cookie, getexline)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { ea.cmd = (char_u *)"+"; - ex_pressedreturn = true; + out->ex_pressedreturn = true; } - /* ignore comment and empty lines */ - if (*ea.cmd == '"') - goto doend; + // ignore comment and empty lines + if (*ea.cmd == '"') { + return false; + } if (*ea.cmd == NUL) { - ex_pressedreturn = true; - goto doend; + out->ex_pressedreturn = true; + return false; } /* * 2. Handle command modifiers. */ - p = skip_range(ea.cmd, NULL); + char_u *p = skip_range(ea.cmd, NULL); switch (*p) { - /* When adding an entry, also modify cmd_exists(). */ + // When adding an entry, also modify cmd_exists(). case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3)) break; - cmdmod.split |= WSP_ABOVE; + out->cmdmod.split |= WSP_ABOVE; continue; case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) { - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; } if (checkforcmd(&ea.cmd, "browse", 3)) { - cmdmod.browse = true; + out->cmdmod.browse = true; continue; } - if (!checkforcmd(&ea.cmd, "botright", 2)) + if (!checkforcmd(&ea.cmd, "botright", 2)) { break; - cmdmod.split |= WSP_BOT; + } + out->cmdmod.split |= WSP_BOT; continue; case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4)) break; - cmdmod.confirm = true; + out->cmdmod.confirm = true; continue; case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) { - cmdmod.keepmarks = true; + out->cmdmod.keepmarks = true; continue; } if (checkforcmd(&ea.cmd, "keepalt", 5)) { - cmdmod.keepalt = true; + out->cmdmod.keepalt = true; continue; } if (checkforcmd(&ea.cmd, "keeppatterns", 5)) { - cmdmod.keeppatterns = true; + out->cmdmod.keeppatterns = true; continue; } - if (!checkforcmd(&ea.cmd, "keepjumps", 5)) + if (!checkforcmd(&ea.cmd, "keepjumps", 5)) { break; - cmdmod.keepjumps = true; + } + out->cmdmod.keepjumps = true; continue; case 'f': { // only accept ":filter {pat} cmd" @@ -1362,7 +1382,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, break; } if (*p == '!') { - cmdmod.filter_force = true; + out->cmdmod.filter_force = true; p = skipwhite(p + 1); if (*p == NUL || ends_excmd(*p)) { break; @@ -1372,134 +1392,217 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (p == NULL || *p == NUL) { break; } - cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); - if (cmdmod.filter_regmatch.regprog == NULL) { + out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); + if (out->cmdmod.filter_regmatch.regprog == NULL) { break; } ea.cmd = p; continue; } - /* ":hide" and ":hide | cmd" are not modifiers */ + // ":hide" and ":hide | cmd" are not modifiers case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3) || *p == NUL || ends_excmd(*p)) break; ea.cmd = p; - cmdmod.hide = true; + out->cmdmod.hide = true; continue; case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) { - cmdmod.lockmarks = true; + out->cmdmod.lockmarks = true; continue; } - if (!checkforcmd(&ea.cmd, "leftabove", 5)) + if (!checkforcmd(&ea.cmd, "leftabove", 5)) { break; - cmdmod.split |= WSP_ABOVE; + } + out->cmdmod.split |= WSP_ABOVE; continue; case 'n': if (checkforcmd(&ea.cmd, "noautocmd", 3)) { - if (cmdmod.save_ei == NULL) { - /* Set 'eventignore' to "all". Restore the - * existing option value later. */ - cmdmod.save_ei = vim_strsave(p_ei); - set_string_option_direct( - (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + if (out->cmdmod.save_ei == NULL) { + // Set 'eventignore' to "all". Restore the + // existing option value later. + out->cmdmod.save_ei = vim_strsave(p_ei); + out->set_eventignore = true; } continue; } if (!checkforcmd(&ea.cmd, "noswapfile", 3)) { break; } - cmdmod.noswapfile = true; + out->cmdmod.noswapfile = true; continue; case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6)) break; - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) { - if (!did_sandbox) - ++sandbox; - did_sandbox = TRUE; + if (!out->did_sandbox) { + out->sandbox++; + } + out->did_sandbox = true; continue; } - if (!checkforcmd(&ea.cmd, "silent", 3)) + if (!checkforcmd(&ea.cmd, "silent", 3)) { break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - ++msg_silent; + } + if (out->save_msg_silent == -1) { + out->save_msg_silent = out->msg_silent; + } + out->msg_silent++; if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) { - /* ":silent!", but not "silent !cmd" */ + // ":silent!", but not "silent !cmd" ea.cmd = skipwhite(ea.cmd + 1); - ++emsg_silent; - ++did_esilent; + out->emsg_silent++; + out->did_esilent++; } continue; case 't': if (checkforcmd(&p, "tab", 3)) { - long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + long tabnr = get_address( + &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + if (tabnr == MAXLNUM) { - cmdmod.tab = tabpage_index(curtab) + 1; + out->cmdmod.tab = tabpage_index(curtab) + 1; } else { if (tabnr < 0 || tabnr > LAST_TAB_NR) { - errormsg = (char_u *)_(e_invrange); - goto doend; + out->errormsg = (char_u *)_(e_invrange); + return false; } - cmdmod.tab = tabnr + 1; + out->cmdmod.tab = tabnr + 1; } ea.cmd = p; continue; } - if (!checkforcmd(&ea.cmd, "topleft", 2)) + if (!checkforcmd(&ea.cmd, "topleft", 2)) { break; - cmdmod.split |= WSP_TOP; + } + out->cmdmod.split |= WSP_TOP; continue; case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3)) break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - msg_silent = 0; + if (out->save_msg_silent == -1) { + out->save_msg_silent = out->msg_silent; + } + out->msg_silent = 0; continue; case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) { - cmdmod.split |= WSP_VERT; + out->cmdmod.split |= WSP_VERT; continue; } if (!checkforcmd(&p, "verbose", 4)) break; - if (verbose_save < 0) - verbose_save = p_verbose; - if (ascii_isdigit(*ea.cmd)) - p_verbose = atoi((char *)ea.cmd); - else - p_verbose = 1; + if (out->verbose_save < 0) { + out->verbose_save = out->p_verbose; + } + if (ascii_isdigit(*ea.cmd)) { + out->p_verbose = atoi((char *)ea.cmd); + } else { + out->p_verbose = 1; + } ea.cmd = p; continue; } break; } - char_u *after_modifier = ea.cmd; - - ea.skip = (did_emsg - || got_int - || current_exception - || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); + out->after_modifier = ea.cmd; // 3. Skip over the range to find the command. Let "p" point to after it. // // We need the command to know what kind of range it uses. - cmd = ea.cmd; + out->cmd = ea.cmd; ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - p = find_command(&ea, NULL); + out->parsed_upto = find_command(&ea, NULL); + + *out->eap = ea; + + return true; +} + +/* + * Execute one Ex command. + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * + * 1. skip comment lines and leading space + * 2. handle command modifiers + * 3. skip over the range to find the command + * 4. parse the range + * 5. parse the command + * 6. parse arguments + * 7. switch on command name + * + * Note: "fgetline" can be NULL. + * + * This function may be called recursively! + */ +static char_u * do_one_cmd(char_u **cmdlinep, + int flags, + struct condstack *cstack, + LineGetter fgetline, + void *cookie /* argument for fgetline() */ + ) +{ + char_u *p; + linenr_T lnum; + long n; + char_u *errormsg = NULL; // error message + exarg_T ea; + int save_msg_scroll = msg_scroll; + parse_state_T parsed; + cmdmod_T save_cmdmod; + const int save_reg_executing = reg_executing; + + ex_nesting_level++; + + /* When the last file has not been edited :q has to be typed twice. */ + if (quitmore + /* avoid that a function call in 'statusline' does this */ + && !getline_equal(fgetline, cookie, get_func_line) + /* avoid that an autocommand, e.g. QuitPre, does this */ + && !getline_equal(fgetline, cookie, getnextac) + ) + --quitmore; + + /* + * Reset browse, confirm, etc.. They are restored when returning, for + * recursive calls. + */ + save_cmdmod = cmdmod; + memset(&cmdmod, 0, sizeof(cmdmod)); + + parse_state_from_global(&parsed); + parsed.eap = &ea; + parsed.verbose_save = -1; + parsed.save_msg_silent = -1; + parsed.did_esilent = 0; + parsed.did_sandbox = false; + bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie); + parse_state_to_global(&parsed); + + // Update locals from parse_one_cmd() + errormsg = parsed.errormsg; + p = parsed.parsed_upto; + + if (!parse_success) { + goto doend; + } + + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES @@ -1570,7 +1673,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, } } - ea.cmd = cmd; + ea.cmd = parsed.cmd; if (parse_cmd_address(&ea, &errormsg) == FAIL) { goto doend; } @@ -1651,8 +1754,8 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (!(flags & DOCMD_VERBOSE)) { // If the modifier was parsed OK the error must be in the following // command - if (after_modifier != NULL) { - append_command(after_modifier); + if (parsed.after_modifier != NULL) { + append_command(parsed.after_modifier); } else { append_command(*cmdlinep); } @@ -2120,12 +2223,12 @@ static char_u * do_one_cmd(char_u **cmdlinep, // The :try command saves the emsg_silent flag, reset it here when // ":silent! try" was used, it should only apply to :try itself. - if (ea.cmdidx == CMD_try && did_esilent > 0) { - emsg_silent -= did_esilent; + if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) { + emsg_silent -= parsed.did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } - did_esilent = 0; + parsed.did_esilent = 0; } // 7. Execute the command. @@ -2191,8 +2294,9 @@ doend: ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); - if (verbose_save >= 0) - p_verbose = verbose_save; + if (parsed.verbose_save >= 0) { + p_verbose = parsed.verbose_save; + } if (cmdmod.save_ei != NULL) { /* Restore 'eventignore' to the value before ":noautocmd". */ set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, @@ -2207,16 +2311,18 @@ doend: cmdmod = save_cmdmod; reg_executing = save_reg_executing; - if (save_msg_silent != -1) { - /* messages could be enabled for a serious error, need to check if the - * counters don't become negative */ - if (!did_emsg || msg_silent > save_msg_silent) - msg_silent = save_msg_silent; - emsg_silent -= did_esilent; - if (emsg_silent < 0) + if (parsed.save_msg_silent != -1) { + // messages could be enabled for a serious error, need to check if the + // counters don't become negative + if (!did_emsg || msg_silent > parsed.save_msg_silent) { + msg_silent = parsed.save_msg_silent; + } + emsg_silent -= parsed.did_esilent; + if (emsg_silent < 0) { emsg_silent = 0; - /* Restore msg_scroll, it's set by file I/O commands, even when no - * message is actually displayed. */ + } + // Restore msg_scroll, it's set by file I/O commands, even when no + // message is actually displayed. msg_scroll = save_msg_scroll; /* "silent reg" or "silent echo x" inside "redir" leaves msg_col @@ -2225,8 +2331,9 @@ doend: msg_col = 0; } - if (did_sandbox) - --sandbox; + if (parsed.did_sandbox) { + sandbox--; + } if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ ea.nextcmd = NULL; -- cgit From 0cb6fc804dbf7b46772ea14e8c7b40909b97202c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 12 Nov 2019 20:04:15 -0500 Subject: vim-patch:8.1.2293: join adds trailing space when second line is empty Problem: Join adds trailing space when second line is empty. (Brennan Vincent) Solution: Do not add a trailing space. https://github.com/vim/vim/commit/cc184cfb09161b3bbc7d5d8859a18e812367d19c --- src/nvim/ops.c | 5 ++++- src/nvim/testdir/test_join.vim | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 95674f8b40..2301b2159f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3773,7 +3773,10 @@ int do_join(size_t count, if (insert_space && t > 0) { curr = skipwhite(curr); - if (*curr != ')' && currsize != 0 && endcurr1 != TAB + if (*curr != NUL + && *curr != ')' + && currsize != 0 + && endcurr1 != TAB && (!has_format_option(FO_MBYTE_JOIN) || (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100)) && (!has_format_option(FO_MBYTE_JOIN2) diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim index 1c97414164..ecb55c9af6 100644 --- a/src/nvim/testdir/test_join.vim +++ b/src/nvim/testdir/test_join.vim @@ -9,6 +9,27 @@ func Test_join_with_count() call setline(1, ['one', 'two', 'three', 'four']) normal 10J call assert_equal('one two three four', getline(1)) + + call setline(1, ['one', '', 'two']) + normal J + call assert_equal('one', getline(1)) + + call setline(1, ['one', ' ', 'two']) + normal J + call assert_equal('one', getline(1)) + + call setline(1, ['one', '', '', 'two']) + normal JJ + call assert_equal('one', getline(1)) + + call setline(1, ['one', ' ', ' ', 'two']) + normal JJ + call assert_equal('one', getline(1)) + + call setline(1, ['one', '', '', 'two']) + normal 2J + call assert_equal('one', getline(1)) + quit! endfunc -- cgit From c2ceed49947c8266e2fdfbf596dab23dd3d39279 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 13 Nov 2019 05:43:31 -0500 Subject: quickfix: fix dead assignment Cherry-picked from vim patch 8.1.1489. --- src/nvim/quickfix.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 276427f216..ed57b28029 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4559,9 +4559,9 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, /// Get the nth quickfix entry below the specified entry treating multiple /// entries on a single line as one. Searches forward in the list. -static qfline_T *qf_get_nth_below_entry(qfline_T *entry, - int *errornr, - linenr_T n) +static void qf_get_nth_below_entry(qfline_T *entry, + int *errornr, + linenr_T n) { while (n-- > 0 && !got_int) { qfline_T *first_entry = entry; @@ -4582,15 +4582,13 @@ static qfline_T *qf_get_nth_below_entry(qfline_T *entry, entry = entry->qf_next; (*errornr)++; } - - return entry; } /// Get the nth quickfix entry above the specified entry treating multiple /// entries on a single line as one. Searches backwards in the list. -static qfline_T *qf_get_nth_above_entry(qfline_T *entry, - int *errornr, - linenr_T n) +static void qf_get_nth_above_entry(qfline_T *entry, + int *errornr, + linenr_T n) { while (n-- > 0 && !got_int) { if (entry->qf_prev == NULL @@ -4604,8 +4602,6 @@ static qfline_T *qf_get_nth_above_entry(qfline_T *entry, // If multiple entries are on the same line, then use the first entry entry = qf_find_first_entry_on_line(entry, errornr); } - - return entry; } /// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the @@ -4629,9 +4625,9 @@ static int qf_find_nth_adj_entry(qf_list_T *qfl, if (--n > 0) { // Go to the n'th entry in the current buffer if (dir == FORWARD) { - adj_entry = qf_get_nth_below_entry(adj_entry, &errornr, n); + qf_get_nth_below_entry(adj_entry, &errornr, n); } else { - adj_entry = qf_get_nth_above_entry(adj_entry, &errornr, n); + qf_get_nth_above_entry(adj_entry, &errornr, n); } } -- cgit From a0c18bf2017ac7d38d13014a810bda44d09dfcbb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 13 Nov 2019 06:12:46 -0500 Subject: spell: fix clang logic error https://neovim.io/doc/reports/clang/report-487b01.html#EndPath https://neovim.io/doc/reports/clang/report-ce4c3c.html#EndPath --- src/nvim/spell.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 687c86b4a8..5feb7efda9 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1910,11 +1910,11 @@ int init_syl_tab(slang_T *slang) // Count the number of syllables in "word". // When "word" contains spaces the syllables after the last space are counted. // Returns zero if syllables are not defines. -static int count_syllables(slang_T *slang, char_u *word) +static int count_syllables(slang_T *slang, const char_u *word) + FUNC_ATTR_NONNULL_ALL { int cnt = 0; bool skip = false; - char_u *p; int len; syl_item_T *syl; int c; @@ -1922,7 +1922,7 @@ static int count_syllables(slang_T *slang, char_u *word) if (slang->sl_syllable == NULL) return 0; - for (p = word; *p != NUL; p += len) { + for (const char_u *p = word; *p != NUL; p += len) { // When running into a space reset counter. if (*p == ' ') { len = 1; @@ -2625,9 +2625,10 @@ static bool spell_mb_isword_class(int cl, const win_T *wp) // Returns true if "p" points to a word character. // Wide version of spell_iswordp(). -static bool spell_iswordp_w(int *p, win_T *wp) +static bool spell_iswordp_w(const int *p, const win_T *wp) + FUNC_ATTR_NONNULL_ALL { - int *s; + const int *s; if (*p < 256 ? wp->w_s->b_spell_ismw[*p] : (wp->w_s->b_spell_ismw_mb != NULL -- cgit From 570ee5f404966a6ffd9a5241d91adc6a9f5f2c9d Mon Sep 17 00:00:00 2001 From: James McCoy Date: Thu, 14 Nov 2019 00:34:10 -0500 Subject: f_getenv/setenv: Access v_special when v_type is VAR_SPECIAL #11388 Multiple Debian builds were failing these tests: Failures: From test_environ.vim: Found errors in Test_external_env(): function RunTheTest[37]..Test_external_env line 16: Expected '' but got 'FOO=null\n' Found errors in Test_getenv(): function RunTheTest[37]..Test_getenv line 2: Expected v:null but got v:false Found errors in Test_setenv(): function RunTheTest[37]..Test_setenv line 5: Expected v:null but got 'null' This is because nvim has a separate tag (`v_special`) in `typval_T` for special variables, whereas vim re-uses the `v_number` tag. On little-endian architectures, using the incorrect tag is not an issue because the byte representation is the same. However, on big-endian systems this caused the `v_number == kSpecialVarNull` checks to fail, and the non-special code to execute. --- src/nvim/eval.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e08e129656..3621f90511 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8711,7 +8711,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (p == NULL) { rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = kSpecialVarNull; + rettv->vval.v_special = kSpecialVarNull; return; } rettv->vval.v_string = p; @@ -15669,7 +15669,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *name = tv_get_string_buf(&argvars[0], namebuf); if (argvars[1].v_type == VAR_SPECIAL - && argvars[1].vval.v_number == kSpecialVarNull) { + && argvars[1].vval.v_special == kSpecialVarNull) { os_unsetenv(name); } else { os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); -- cgit From d79164c9f9ffbb17b82b3a523e217e61f43697be Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 16 Nov 2019 03:34:25 -0500 Subject: vim-patch:8.1.0992: :normal resets reg_executing() result #11398 Problem: A :normal command while executing a register resets the reg_executing() result. Solution: Save and restore reg_executing. (closes vim/vim#4066) https://github.com/vim/vim/commit/cce713ddcc0c9ab29926c28e287cbb587a959b08 --- src/nvim/buffer_defs.h | 22 +++++++++++----------- src/nvim/ex_docmd.c | 4 +++- src/nvim/testdir/test_functions.vim | 7 +++++++ 3 files changed, 21 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3b8e5b5632..700d8b82e6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -918,19 +918,19 @@ typedef struct w_line { * or row (FR_ROW) layout or is a leaf, which has a window. */ struct frame_S { - char fr_layout; /* FR_LEAF, FR_COL or FR_ROW */ + char fr_layout; // FR_LEAF, FR_COL or FR_ROW int fr_width; - int fr_newwidth; /* new width used in win_equal_rec() */ + int fr_newwidth; // new width used in win_equal_rec() int fr_height; - int fr_newheight; /* new height used in win_equal_rec() */ - frame_T *fr_parent; /* containing frame or NULL */ - frame_T *fr_next; /* frame right or below in same parent, NULL - for first */ - frame_T *fr_prev; /* frame left or above in same parent, NULL - for last */ - /* fr_child and fr_win are mutually exclusive */ - frame_T *fr_child; /* first contained frame */ - win_T *fr_win; /* window that fills this frame */ + int fr_newheight; // new height used in win_equal_rec() + frame_T *fr_parent; // containing frame or NULL + frame_T *fr_next; // frame right or below in same parent, NULL + // for last + frame_T *fr_prev; // frame left or above in same parent, NULL + // for first + // fr_child and fr_win are mutually exclusive + frame_T *fr_child; // first contained frame + win_T *fr_win; // window that fills this frame }; #define FR_LEAF 0 /* frame is a leaf */ diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 7d02623d67..6a8bea28a7 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8207,6 +8207,7 @@ static void ex_normal(exarg_T *eap) int save_insertmode = p_im; int save_finish_op = finish_op; long save_opcount = opcount; + const int save_reg_executing = reg_executing; char_u *arg = NULL; int l; char_u *p; @@ -8301,7 +8302,8 @@ static void ex_normal(exarg_T *eap) p_im = save_insertmode; finish_op = save_finish_op; opcount = save_opcount; - msg_didout |= save_msg_didout; /* don't reset msg_didout now */ + reg_executing = save_reg_executing; + msg_didout |= save_msg_didout; // don't reset msg_didout now /* Restore the state (needed when called from a function executed for * 'indentexpr'). Update the mouse and cursor, they may have changed. */ diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index a36c51f56f..7822507f86 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1132,6 +1132,13 @@ func Test_reg_executing_and_recording() " :normal command saves and restores reg_executing let s:reg_stat = '' + let @q = ":call TestFunc()\:call s:save_reg_stat()\" + func TestFunc() abort + normal! ia + endfunc + call feedkeys("@q", 'xt') + call assert_equal(':q', s:reg_stat) + delfunc TestFunc " getchar() command saves and restores reg_executing map W :call TestFunc() -- cgit From 6222cca36adbf09109987e27e58d025a00020f3a Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 16 Nov 2019 11:03:43 +0100 Subject: undo: delete undo_off global without effect --- src/nvim/getchar.c | 1 - src/nvim/globals.h | 1 - src/nvim/undo.c | 15 --------------- 3 files changed, 17 deletions(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 399f0671b4..c038977127 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2409,7 +2409,6 @@ int inchar( did_outofmem_msg = FALSE; /* display out of memory message (again) */ did_swapwrite_msg = FALSE; /* display swap file write error again */ } - undo_off = FALSE; /* restart undo now */ // Get a character from a script file if there is one. // If interrupted: Stop reading script files, close them all. diff --git a/src/nvim/globals.h b/src/nvim/globals.h index c3d1a4d40b..227119bcee 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -768,7 +768,6 @@ EXTERN int did_outofmem_msg INIT(= false); // set after out of memory msg EXTERN int did_swapwrite_msg INIT(= false); // set after swap write error msg -EXTERN int undo_off INIT(= false); // undo switched off for now EXTERN int global_busy INIT(= 0); // set when :global is executing EXTERN int listcmd_busy INIT(= false); // set when :argdo, :windo or // :bufdo is executing diff --git a/src/nvim/undo.c b/src/nvim/undo.c index b00d2d505f..b36d1ac3ec 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -225,9 +225,6 @@ int u_save_cursor(void) */ int u_save(linenr_T top, linenr_T bot) { - if (undo_off) - return OK; - if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) { return FAIL; /* rely on caller to do error messages */ } @@ -246,9 +243,6 @@ int u_save(linenr_T top, linenr_T bot) */ int u_savesub(linenr_T lnum) { - if (undo_off) - return OK; - return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE); } @@ -260,9 +254,6 @@ int u_savesub(linenr_T lnum) */ int u_inssub(linenr_T lnum) { - if (undo_off) - return OK; - return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE); } @@ -275,9 +266,6 @@ int u_inssub(linenr_T lnum) */ int u_savedel(linenr_T lnum, long nlines) { - if (undo_off) - return OK; - return u_savecommon(lnum - 1, lnum + nlines, nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE); } @@ -2925,9 +2913,6 @@ void u_undoline(void) colnr_T t; char_u *oldp; - if (undo_off) - return; - if (curbuf->b_u_line_ptr == NULL || curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) { beep_flush(); -- cgit From ebdf90e7d7c97b4355f42e06769e9424c279d695 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 16 Nov 2019 11:05:56 +0100 Subject: extmark: don't crash in RO buffer. --- src/nvim/undo.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/undo.c b/src/nvim/undo.c index b36d1ac3ec..539d42765d 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -243,7 +243,7 @@ int u_save(linenr_T top, linenr_T bot) */ int u_savesub(linenr_T lnum) { - return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE); + return u_savecommon(lnum - 1, lnum + 1, lnum + 1, false); } /* @@ -254,7 +254,7 @@ int u_savesub(linenr_T lnum) */ int u_inssub(linenr_T lnum) { - return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE); + return u_savecommon(lnum - 1, lnum, lnum + 1, false); } /* @@ -3043,8 +3043,14 @@ u_header_T *u_force_get_undo_header(buf_T *buf) } // Create the first undo header for the buffer if (!uhp) { - // TODO(timeyyy): there would be a better way to do this! - u_save_cursor(); + // Undo is normally invoked in change code, which already has swapped + // curbuf. + buf_T *save_curbuf = curbuf; + curbuf = buf; + // Args are tricky: this means replace empty range by empty range.. + u_savecommon(0, 1, 1, true); + curbuf = save_curbuf; + uhp = buf->b_u_curhead; if (!uhp) { uhp = buf->b_u_newhead; -- cgit From dab40f43b18d35b283804ecb033310198cbf7548 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 30 Oct 2019 20:53:09 +0100 Subject: Add v:lua.func() vimL syntax for calling lua Also simplify error messages when calling lua from vimL. --- src/nvim/eval.c | 119 ++++++++++++++++++++++++++++++++++++++++++------ src/nvim/eval.h | 1 + src/nvim/lua/executor.c | 114 +++++++++++++++++++++++++--------------------- 3 files changed, 169 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 3621f90511..9fe92a92cc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -422,6 +422,7 @@ static struct vimvar { VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), }; #undef VV @@ -433,11 +434,14 @@ static struct vimvar { #define vv_str vv_di.di_tv.vval.v_string #define vv_list vv_di.di_tv.vval.v_list #define vv_dict vv_di.di_tv.vval.v_dict +#define vv_partial vv_di.di_tv.vval.v_partial #define vv_tv vv_di.di_tv /// Variable used for v: static ScopeDictDictItem vimvars_var; +static partial_T *vvlua_partial; + /// v: hashtab #define vimvarht vimvardict.dv_hashtab @@ -639,6 +643,13 @@ void eval_init(void) set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); + vimvars[VV_LUA].vv_type = VAR_PARTIAL; + vvlua_partial = xcalloc(1, sizeof(partial_T)); + vimvars[VV_LUA].vv_partial = vvlua_partial; + // this value shouldn't be printed, but if it is, do not crash + vvlua_partial->pt_name = xmallocz(0); + vvlua_partial->pt_refcount++; + set_reg_var(0); // default for v:register is not 0 but '"' } @@ -1313,12 +1324,25 @@ int call_vim_function( { int doesrange; int ret; + int len = (int)STRLEN(func); + partial_T *pt = NULL; + + if (len >= 6 && !memcmp(func, "v:lua.", 6)) { + func += 6; + len = check_luafunc_name((const char *)func, false); + if (len == 0) { + ret = FAIL; + goto fail; + } + pt = vvlua_partial; + } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL, + ret = call_func(func, len, rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + &doesrange, true, pt, NULL); +fail: if (ret == FAIL) { tv_clear(rettv); } @@ -2462,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } + if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv) + && len == -1 && rettv == NULL) { + tv_clear(&var1); + EMSG2(e_illvar, "v:['lua']"); + return NULL; + } + if (lp->ll_di == NULL) { // Can't add "v:" or "a:" variable. if (lp->ll_dict == &vimvardict @@ -4699,7 +4730,7 @@ eval_index( if (evaluate) { n1 = 0; - if (!empty1 && rettv->v_type != VAR_DICT) { + if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) { n1 = tv_get_number(&var1); tv_clear(&var1); } @@ -4823,7 +4854,7 @@ eval_index( if (len == -1) { tv_clear(&var1); } - if (item == NULL) { + if (item == NULL || tv_is_luafunc(&item->di_tv)) { return FAIL; } @@ -6334,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp, */ static int get_func_tv( - char_u *name, // name of the function + const char_u *name, // name of the function int len, // length of "name" typval_T *rettv, char_u **arg, // argument, pointing to the '(' @@ -6590,7 +6621,15 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (!builtin_function((const char *)rfname, -1)) { + if (partial == vvlua_partial) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } else { + error = ERROR_UNKNOWN; + } + } else if (!builtin_function((const char *)rfname, -1)) { // User defined function. if (partial != NULL && partial->pt_func != NULL) { fp = partial->pt_func; @@ -6707,14 +6746,14 @@ call_func( /// /// @param ermsg must be passed without translation (use N_() instead of _()). /// @param name function name -static void emsg_funcname(char *ermsg, char_u *name) +static void emsg_funcname(char *ermsg, const char_u *name) { char_u *p; if (*name == K_SPECIAL) { p = concat_str((char_u *)"", name + 3); } else { - p = name; + p = (char_u *)name; } EMSG2(_(ermsg), p); @@ -20168,6 +20207,26 @@ static void check_vars(const char *name, size_t len) } } +/// check if special v:lua value for calling lua functions +static bool tv_is_luafunc(typval_T *tv) +{ + return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial; +} + +/// check the function name after "v:lua." +static int check_luafunc_name(const char *str, bool paren) +{ + const char *p = str; + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + p++; + } + if (*p != (paren ? '(' : NUL)) { + return 0; + } else { + return (int)(p-str); + } +} + /// Handle expr[expr], expr[expr:expr] subscript and .name lookup. /// Also handle function call with Funcref variable: func(expr) /// Can all be combined: dict.func(expr)[idx]['func'](expr) @@ -20181,9 +20240,30 @@ handle_subscript( { int ret = OK; dict_T *selfdict = NULL; - char_u *s; + const char_u *s; int len; typval_T functv; + int slen = 0; + bool lua = false; + + if (tv_is_luafunc(rettv)) { + if (**arg != '.') { + tv_clear(rettv); + ret = FAIL; + } else { + (*arg)++; + + lua = true; + s = (char_u *)(*arg); + slen = check_luafunc_name(*arg, true); + if (slen == 0) { + tv_clear(rettv); + ret = FAIL; + } + (*arg) += slen; + } + } + while (ret == OK && (**arg == '[' @@ -20200,14 +20280,16 @@ handle_subscript( // Invoke the function. Recursive! if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; - s = partial_name(pt); + if (!lua) { + s = partial_name(pt); + } } else { s = functv.vval.v_string; } } else { s = (char_u *)""; } - ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg, + ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -22039,8 +22121,19 @@ trans_function_name( *pp = (char_u *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; + if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index e099de831a..2aa08e2074 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -117,6 +117,7 @@ typedef enum { VV_TYPE_BOOL, VV_ECHOSPACE, VV_EXITING, + VV_LUA, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5e5cb0cea5..093c130c5f 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -48,9 +48,6 @@ typedef struct { # include "lua/executor.c.generated.h" #endif -/// Name of the run code for use in messages -#define NLUA_EVAL_NAME "" - /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -397,29 +394,6 @@ static lua_State *nlua_enter(void) return lstate; } -/// Execute lua string -/// -/// @param[in] str String to execute. -/// @param[out] ret_tv Location where result will be saved. -/// -/// @return Result of the execution. -void executor_exec_lua(const String str, typval_T *const ret_tv) - FUNC_ATTR_NONNULL_ALL -{ - lua_State *const lstate = nlua_enter(); - - if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return; - } - - nlua_pop_typval(lstate, ret_tv); -} - static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -732,10 +706,6 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - lua_State *const lstate = nlua_enter(); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); #define EVALHEADER "local _A=select(1,...) return (" const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; char *lcmd; @@ -748,30 +718,71 @@ void executor_eval_lua(const String str, typval_T *const arg, memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return; - } + typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); + if (lcmd != (char *)IObuff) { xfree(lcmd); } +} - if (arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); +void executor_call_lua(const char *str, size_t len, typval_T *const args, + int argcount, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ +#define CALLHEADER "return " +#define CALLSUFFIX "(...)" + const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; } else { - nlua_push_typval(lstate, arg, true); + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1); + memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len); + memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX, + sizeof(CALLSUFFIX) - 1); +#undef CALLHEADER +#undef CALLSUFFIX + + typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); + + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } +} + +static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, + typval_T *const args, int argcount, bool special, + typval_T *ret_tv) +{ + if (check_restricted() || check_secure()) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; + return; } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + + lua_State *const lstate = nlua_enter(); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) { + nlua_error(lstate, _("E5107: Error loading lua %.*s")); return; } - nlua_pop_typval(lstate, ret_tv); + for (int i = 0; i < argcount; i++) { + if (args[i].v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, &args[i], special); + } + } + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { + nlua_error(lstate, _("E5108: Error executing lua %.*s")); + return; + } + + if (ret_tv) { + nlua_pop_typval(lstate, ret_tv); + } } /// Execute lua string @@ -857,9 +868,8 @@ void ex_lua(exarg_T *const eap) xfree(code); return; } - typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua((String) { .data = code, .size = len }, &tv); - tv_clear(&tv); + typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL); + xfree(code); } @@ -897,8 +907,8 @@ void ex_luado(exarg_T *const eap) #undef DOSTART #undef DOEND - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) { + nlua_error(lstate, _("E5109: Error loading lua: %.*s")); if (lcmd_len >= IOSIZE) { xfree(lcmd); } @@ -908,7 +918,7 @@ void ex_luado(exarg_T *const eap) xfree(lcmd); } if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + nlua_error(lstate, _("E5110: Error executing lua: %.*s")); return; } for (linenr_T l = eap->line1; l <= eap->line2; l++) { @@ -919,7 +929,7 @@ void ex_luado(exarg_T *const eap) lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { -- cgit From 7274f5c177d49660ed8c06998da402a09f5f31c6 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 16 Nov 2019 12:31:46 -0500 Subject: vim-patch:8.1.1922: in diff mode global operations can be very slow Problem: In diff mode global operations can be very slow. Solution: Do not call diff_redraw() many times, call it once when redrawing. And also don't update folds multiple times. https://github.com/vim/vim/commit/4f57eefe1e84b5a90e08474092ea6fc8825ad5c9 --- src/nvim/diff.c | 6 ++++-- src/nvim/fold.c | 5 +++++ src/nvim/globals.h | 1 + src/nvim/screen.c | 5 +++++ 4 files changed, 15 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 313f77474d..0f916106a2 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -490,7 +490,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, } if (tp == curtab) { - diff_redraw(true); + // Don't redraw right away, this updates the diffs, which can be slow. + need_diff_redraw = true; // Need to recompute the scroll binding, may remove or add filler // lines (e.g., when adding lines above w_topline). But it's slow when @@ -634,8 +635,9 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp) /// Mark all diff buffers in the current tab page for redraw. /// /// @param dofold Also recompute the folds -static void diff_redraw(int dofold) +void diff_redraw(bool dofold) { + need_diff_redraw = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (!wp->w_p_diff) { continue; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 5ce953e626..b193b4005c 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -771,6 +771,11 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) return; } + if (need_diff_redraw) { + // will update later + return; + } + // Mark all folds from top to bot as maybe-small. fold_T *fp; (void)foldFind(&wp->w_folds, top, &fp); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 227119bcee..e9f2800991 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -744,6 +744,7 @@ EXTERN int maptick INIT(= 0); // tick for each non-mapped char EXTERN int must_redraw INIT(= 0); // type of redraw necessary EXTERN bool skip_redraw INIT(= false); // skip redraw once EXTERN bool do_redraw INIT(= false); // extra redraw once +EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw() EXTERN bool must_redraw_pum INIT(= false); // redraw pum. NB: must_redraw // should also be set. diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 1ce0b5217e..7b9601a5a6 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -286,6 +286,11 @@ int update_screen(int type) return FAIL; } + // May have postponed updating diffs. + if (need_diff_redraw) { + diff_redraw(true); + } + if (must_redraw) { if (type < must_redraw) /* use maximal type */ type = must_redraw; -- cgit From da7bb53d995b409208b29a5fd5941075230f1504 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 16 Nov 2019 12:45:44 -0500 Subject: diff: move diff globals to diff.h --- src/nvim/diff.h | 7 +++++++ src/nvim/globals.h | 6 ------ 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/diff.h b/src/nvim/diff.h index 3624ce29bb..99a60381bd 100644 --- a/src/nvim/diff.h +++ b/src/nvim/diff.h @@ -4,6 +4,13 @@ #include "nvim/pos.h" #include "nvim/ex_cmds_defs.h" +// Value set from 'diffopt'. +EXTERN int diff_context INIT(= 6); // context for folds +EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode +EXTERN bool diff_need_scrollbind INIT(= false); + +EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw() + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "diff.h.generated.h" #endif diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e9f2800991..15ad6d8767 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -400,11 +400,6 @@ EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */ EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with mouse dragging */ -/* Value set from 'diffopt'. */ -EXTERN int diff_context INIT(= 6); /* context for folds */ -EXTERN int diff_foldcolumn INIT(= 2); /* 'foldcolumn' for diff mode */ -EXTERN int diff_need_scrollbind INIT(= FALSE); - /* The root of the menu hierarchy. */ EXTERN vimmenu_T *root_menu INIT(= NULL); /* @@ -744,7 +739,6 @@ EXTERN int maptick INIT(= 0); // tick for each non-mapped char EXTERN int must_redraw INIT(= 0); // type of redraw necessary EXTERN bool skip_redraw INIT(= false); // skip redraw once EXTERN bool do_redraw INIT(= false); // extra redraw once -EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw() EXTERN bool must_redraw_pum INIT(= false); // redraw pum. NB: must_redraw // should also be set. -- cgit From b83027858af71e5ca976c3b43e0b798c624f5529 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 16 Nov 2019 16:13:51 -0500 Subject: vim-patch:8.1.2289: after :diffsplit closing the window does not disable diff Problem: After :diffsplit closing the window does not disable diff. Solution: Add "closeoff" to 'diffopt' and add it to the default. https://github.com/vim/vim/commit/c8234779790dd873acb88331c50988adf94cc383 --- src/nvim/diff.c | 23 +++++++++++++++++++++-- src/nvim/options.lua | 2 +- src/nvim/testdir/test_diffmode.vim | 25 +++++++++++++++++++++++++ src/nvim/window.c | 17 +++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 0f916106a2..dccde01d29 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -44,7 +44,7 @@ #include "nvim/os/shell.h" static int diff_busy = false; // using diff structs, don't change them -static int diff_need_update = false; // ex_diffupdate needs to be called +static bool diff_need_update = false; // ex_diffupdate needs to be called // Flags obtained from the 'diffopt' option #define DIFF_FILLER 0x001 // display filler lines @@ -57,8 +57,9 @@ static int diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_VERTICAL 0x080 // vertical splits #define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden #define DIFF_INTERNAL 0x200 // use internal xdiff algorithm +#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) -static int diff_flags = DIFF_INTERNAL | DIFF_FILLER; +static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; static long diff_algorithm = 0; @@ -1474,6 +1475,13 @@ void ex_diffoff(exarg_T *eap) diff_buf_clear(); } + if (!diffwin) { + diff_need_update = false; + curtab->tp_diff_invalid = false; + curtab->tp_diff_update = false; + diff_clear(curtab); + } + // Remove "hor" from from 'scrollopt' if there are no diff windows left. if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) { do_cmdline_cmd("set sbo-=hor"); @@ -1714,6 +1722,7 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, /// /// @param tp void diff_clear(tabpage_T *tp) + FUNC_ATTR_NONNULL_ALL { diff_T *p; diff_T *next_p; @@ -2143,6 +2152,9 @@ int diffopt_changed(void) } else if (STRNCMP(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; + } else if (STRNCMP(p, "closeoff", 8) == 0) { + p += 8; + diff_flags_new |= DIFF_CLOSE_OFF; } else if (STRNCMP(p, "indent-heuristic", 16) == 0) { p += 16; diff_indent_heuristic = XDF_INDENT_HEURISTIC; @@ -2218,6 +2230,13 @@ bool diffopt_hiddenoff(void) return (diff_flags & DIFF_HIDDEN_OFF) != 0; } +// Return true if 'diffopt' contains "closeoff". +bool diffopt_closeoff(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (diff_flags & DIFF_CLOSE_OFF) != 0; +} + /// Find the difference within a changed line. /// /// @param wp window whose current buffer to check diff --git a/src/nvim/options.lua b/src/nvim/options.lua index e96b3f8e02..d20174466d 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -612,7 +612,7 @@ return { alloced=true, redraw={'current_window'}, varname='p_dip', - defaults={if_true={vi="internal,filler"}} + defaults={if_true={vi="internal,filler,closeoff"}} }, { full_name='digraph', abbreviation='dg', diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 57b19aa817..21e0271bda 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -773,3 +773,28 @@ func Test_diff_of_diff() call StopVimInTerminal(buf) call delete('Xtest_diff_diff') endfunc + +func CloseoffSetup() + enew + call setline(1, ['one', 'two', 'three']) + diffthis + new + call setline(1, ['one', 'tow', 'three']) + diffthis + call assert_equal(1, &diff) + only! +endfunc + +func Test_diff_closeoff() + " "closeoff" included by default: last diff win gets 'diff' reset' + call CloseoffSetup() + call assert_equal(0, &diff) + enew! + + " "closeoff" excluded: last diff win keeps 'diff' set' + set diffopt-=closeoff + call CloseoffSetup() + call assert_equal(1, &diff) + diffoff! + enew! +endfunc diff --git a/src/nvim/window.c b/src/nvim/window.c index 0531ad1938..2a7578e33c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2418,6 +2418,7 @@ int win_close(win_T *win, bool free_buf) bool help_window = false; tabpage_T *prev_curtab = curtab; frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; + const bool had_diffmode = win->w_p_diff; if (last_window() && !win->w_floating) { EMSG(_("E444: Cannot close last window")); @@ -2642,6 +2643,22 @@ int win_close(win_T *win, bool free_buf) if (help_window) restore_snapshot(SNAP_HELP_IDX, close_curwin); + // If the window had 'diff' set and now there is only one window left in + // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then + // execute ":diffoff!". + if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) { + int diffcount = 0; + + FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) { + if (dwin->w_p_diff) { + diffcount++; + } + } + if (diffcount == 1) { + do_cmdline_cmd("diffoff!"); + } + } + curwin->w_pos_changed = true; redraw_all_later(NOT_VALID); return OK; -- cgit From 3056ff4b8c39ea0679a564760c607021df4ad14f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 16 Nov 2019 16:41:19 -0500 Subject: vim-patch:8.1.2305: no warning for wrong entry in translations Problem: No warning for wrong entry in translations. Solution: Check semicolons in keywords entry of desktop file. https://github.com/vim/vim/commit/e53ec39270c805a95e405812e4c463c87a150eda --- src/nvim/po/check.vim | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim index eae27ef74d..650c6155e2 100644 --- a/src/nvim/po/check.vim +++ b/src/nvim/po/check.vim @@ -47,6 +47,17 @@ let wsv = winsaveview() let error = 0 while 1 + let lnum = line('.') + if getline(lnum) =~ 'msgid "Text;.*;"' + if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"' + echomsg 'Mismatching ; in line ' . (lnum + 1) + echomsg 'Did you forget the trailing semicolon?' + if error == 0 + let error = lnum + 1 + endif + endif + endif + if getline(line('.') - 1) !~ "no-c-format" " go over the "msgid" and "msgid_plural" lines let prevfromline = 'foobar' -- cgit From 91f4bb0aeec0b2ae983ea310ada73be5f35f7c5c Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 18 Nov 2019 05:58:58 +0900 Subject: TUI: use stdio names instead of magic numbers #11410 --- src/nvim/os/tty.c | 1 + src/nvim/tui/input.c | 8 ++++---- src/nvim/tui/tui.c | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c index bd5b9b4506..4f525bed9a 100644 --- a/src/nvim/os/tty.c +++ b/src/nvim/os/tty.c @@ -6,6 +6,7 @@ // #include "nvim/os/os.h" +#include "nvim/os/tty.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/tty.c.generated.h" diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 844bc0db40..c71378463f 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -26,7 +26,7 @@ void tinput_init(TermInput *input, Loop *loop) { input->loop = loop; input->paste = 0; - input->in_fd = 0; + input->in_fd = STDIN_FILENO; input->waiting_for_bg_response = 0; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); uv_mutex_init(&input->key_buffer_mutex); @@ -36,7 +36,7 @@ void tinput_init(TermInput *input, Loop *loop) // echo q | nvim -es // ls *.md | xargs nvim #ifdef WIN32 - if (!os_isatty(0)) { + if (!os_isatty(input->in_fd)) { const HANDLE conin_handle = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, @@ -46,8 +46,8 @@ void tinput_init(TermInput *input, Loop *loop) assert(input->in_fd != -1); } #else - if (!os_isatty(0) && os_isatty(2)) { - input->in_fd = 2; + if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) { + input->in_fd = STDERR_FILENO; } #endif input_global_fd_init(input->in_fd); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 11746441aa..60e1353000 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -220,7 +220,7 @@ static void terminfo_start(UI *ui) data->unibi_ext.reset_cursor_style = -1; data->unibi_ext.get_bg = -1; data->unibi_ext.set_underline_color = -1; - data->out_fd = 1; + data->out_fd = STDOUT_FILENO; data->out_isatty = os_isatty(data->out_fd); const char *term = os_getenv("TERM"); -- cgit From e4b185893f511b050b8d38e92e3b1100e489dd9f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 17 Nov 2019 21:13:38 -0500 Subject: vim-patch:8.1.2312: "line:" field in tags file not used Problem: "line:" field in tags file not used. Solution: Recognize the field and use the value. (Andy Massimino, Daniel Hahler, closes vim/vim#5232, closes vim/vim#2546, closes vim/vim#1057) https://github.com/vim/vim/commit/077b9dd3541339a23ade0cc6a23e804ee39312c5 --- src/nvim/tag.c | 12 +++++++++++- src/nvim/testdir/test_tagjump.vim | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a3967c70b5..9e8c05fb1e 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -65,6 +65,7 @@ typedef struct tag_pointers { char_u *tagkind_end; // end of tagkind char_u *user_data; // user_data string char_u *user_data_end; // end of user_data + linenr_T tagline; // "line:" value } tagptrs_T; /* @@ -2545,6 +2546,7 @@ parse_match( tagp->tagkind = NULL; tagp->user_data = NULL; + tagp->tagline = 0; tagp->command_end = NULL; if (retval == OK) { @@ -2564,6 +2566,8 @@ parse_match( tagp->tagkind = p + 5; } else if (STRNCMP(p, "user_data:", 10) == 0) { tagp->user_data = p + 10; + } else if (STRNCMP(p, "line:", 5) == 0) { + tagp->tagline = atoi((char *)p + 5); } if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; @@ -2811,7 +2815,13 @@ static int jumpto_tag( p_ic = FALSE; /* don't ignore case now */ p_scs = FALSE; save_lnum = curwin->w_cursor.lnum; - curwin->w_cursor.lnum = 0; /* start search before first line */ + if (tagp.tagline > 0) { + // start search before line from "line:" field + curwin->w_cursor.lnum = tagp.tagline - 1; + } else { + // start search before first line + curwin->w_cursor.lnum = 0; + } if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { retval = OK; diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index ce527a5e1d..f93af76f17 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -466,4 +466,28 @@ func Test_tag_line_toolong() let &verbose = old_vbs endfunc +func Test_tagline() + call writefile([ + \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo', + \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar', + \], 'Xtags') + call writefile([ + \ ' def provision(self, **kwargs):', + \ ' pass', + \ ' def provision(self, **kwargs):', + \ ' pass', + \], 'Xtest.py') + + set tags=Xtags + + 1tag provision + call assert_equal(line('.'), 1) + 2tag provision + call assert_equal(line('.'), 3) + + call delete('Xtags') + call delete('Xtest.py') + set tags& +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From f484d3b2d4ec7f3a0c18c46125021aceedec03af Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 17 Nov 2019 21:48:59 -0500 Subject: vim-patch:8.1.2314: vi' sometimes does not select anything Problem: vi' sometimes does not select anything. Solution: Recognize an empty selection. (Christian Brabandt, closes vim/vim#5183) https://github.com/vim/vim/commit/7170b295b06e3168424985530d8477ed2e058b67 --- src/nvim/search.c | 5 +++-- src/nvim/testdir/test_textobjects.vim | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index d396e7551b..a298f7333e 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3808,8 +3808,9 @@ current_quote( } vis_bef_curs = lt(VIsual, curwin->w_cursor); + vis_empty = equalpos(VIsual, curwin->w_cursor); if (*p_sel == 'e') { - if (!vis_bef_curs) { + if (!vis_bef_curs && !vis_empty) { // VIsual needs to be start of Visual selection. pos_T t = curwin->w_cursor; @@ -3819,8 +3820,8 @@ current_quote( restore_vis_bef = true; } dec_cursor(); + vis_empty = equalpos(VIsual, curwin->w_cursor); } - vis_empty = equalpos(VIsual, curwin->w_cursor); } if (!vis_empty) { diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 9194e0014d..448b2dc51c 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -48,6 +48,9 @@ func Test_quote_selection_selection_exclusive() set selection=exclusive exe "norm! fdvhi'y" call assert_equal('bcde', @") + let @"='dummy' + exe "norm! $gevi'y" + call assert_equal('bcde', @") set selection&vim bw! endfunc -- cgit From 6ca3e6bfa9e0551b453af040ca283ec7eb756178 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 17 Nov 2019 21:54:45 -0500 Subject: vim-patch:8.1.2317: no test for spell affix file with flag on suffix Problem: No test for spell affix file with flag on suffix. Solution: Add a test case. https://github.com/vim/vim/commit/37ff4cf87069e54f991d86df9beff19bfdd15875 --- src/nvim/testdir/test_spell.vim | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 9dce87774b..e2016d7927 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -320,6 +320,19 @@ func Test_zz_Numbers() \ ]) endfunc +" Affix flags +func Test_zz_affix_flags() + call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10) + call RunGoodBad("drink drinkable drinkables drinktable drinkabletable", + \ "bad: drinks drinkstable drinkablestable", + \ ["drink", "drinkable", "drinkables", "table"], + \ [['bad', []], + \ ['drinks', ['drink']], + \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']], + \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']], + \ ]) +endfunc + function FirstSpellWord() call feedkeys("/^start:\n", 'tx') normal ]smm @@ -751,6 +764,21 @@ let g:test_data_dic9 = [ \"foo", \"bar", \ ] +let g:test_data_aff10 = [ + \"COMPOUNDRULE se", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX A Y 1", + \"SFX A 0 able/Mp .", + \"", + \"SFX M Y 1", + \"SFX M 0 s .", + \ ] +let g:test_data_dic10 = [ + \"1234", + \"drink/As", + \"table/e", + \ ] let g:test_data_aff_sal = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", -- cgit From af53a0c0123338575dd59934449d7fe836835d1c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 17 Nov 2019 19:06:59 -0800 Subject: doc: Lua [ci skip] #11378 - Rework :help lua-commands - Rename if_lua.txt => lua.txt --- src/nvim/api/buffer.c | 20 ++++++++++++++------ src/nvim/lua/vim.lua | 5 ++--- 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9ec96840d1..a5f8b0974e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -106,6 +106,14 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// Activates buffer-update events on a channel, or as Lua callbacks. /// +/// Example (Lua): capture buffer updates in a global `events` variable +/// (use "print(vim.inspect(events))" to see its contents): +///
+///   events = {}
+///   vim.api.nvim_buf_attach(0, false, {
+///     on_lines=function(...) table.insert(events, {...}) end})
+/// 
+/// /// @see |nvim_buf_detach()| /// @see |api-buffer-updates-lua| /// @@ -1041,18 +1049,18 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// range ends can be specified as (row, col) tuples, as well as extmark /// ids in the same namespace. In addition, 0 and -1 works as shorthands /// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be -/// quieried as: +/// queried as: /// -/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1) +/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) /// /// If end is a lower position than start, then the range will be traversed -/// backwards. This is mostly used with limited amount, to be able to get the +/// backwards. This is mostly useful with limited amount, to be able to get the /// first marks prior to a given position. /// /// @param buffer The buffer handle /// @param ns_id An id returned previously from nvim_create_namespace -/// @param lower One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param upper One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param start One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param end One of: extmark id, (row, col) or 0, -1 for buffer ends /// @param opts additional options. Supports the keys: /// - amount: Maximum number of marks to return /// @param[out] err Details of an error that may have occurred @@ -1153,7 +1161,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// @param buffer The buffer handle /// @param ns_id a identifier returned previously with nvim_create_namespace /// @param id The extmark's id or 0 to create a new mark. -/// @param row The row to set the extmark to. +/// @param line The row to set the extmark to. /// @param col The column to set the extmark to. /// @param opts Optional parameters. Currently not used. /// @param[out] err Details of an error that may have occurred diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index ce24d1716d..1665a55aff 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -167,8 +167,7 @@ end --- --- Example: To remove ANSI color codes when pasting: ---
---- vim.paste = (function()
----   local overridden = vim.paste
+--- vim.paste = (function(overridden)
 ---   return function(lines, phase)
 ---     for i,line in ipairs(lines) do
 ---       -- Scrub ANSI color codes from paste input.
@@ -176,7 +175,7 @@ end
 ---     end
 ---     overridden(lines, phase)
 ---   end
---- end)()
+--- end)(vim.paste)
 --- 
--- --@see |paste| -- cgit From b1c4a8191ef65790f3f039a450cffe064499cc9b Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Mon, 18 Nov 2019 04:19:52 +0100 Subject: version.c: update [ci skip] #11160 vim-patch:8.0.1405: duplicated code for getting a typed character vim-patch:8.1.0828: still using FEAT_VIRTUALEDIT vim-patch:8.1.1971: manually enabling features causes build errors vim-patch:8.1.2000: plugin cannot get the current IME status vim-patch:8.1.2119: memory access error for empty string vim-patch:8.1.2122: cannot build without terminal feature vim-patch:8.1.2123: parsing CSI sequence is messy vim-patch:8.1.2128: renamed libvterm sources makes merging difficult vim-patch:8.1.2130: MSVC build fails vim-patch:8.1.2132: MS-Windows: screen mess when not recognizing insider build vim-patch:8.1.2138: including the build number in the Win32 binary is confusing vim-patch:8.1.2144: side effects when using t_ti to enable modifyOtherKeys vim-patch:8.1.2157: libvterm source files missing from distribution vim-patch:8.1.2160: cannot build with +syntax but without +terminal vim-patch:8.1.2163: cannot build with +spell but without +syntax vim-patch:8.1.2170: cannot build without the +termresponse feature vim-patch:8.1.2179: pressing "q" at the more prompt doesn't stop Python output vim-patch:8.1.2186: error for bad regexp even though regexp is not used vim-patch:8.1.2196: MS-Windows: running tests with MSVC lacks updates vim-patch:8.1.2199: build failure when using normal features without GUI vim-patch:8.1.2201: cannot build with dynamically linked Python 3.8 vim-patch:8.1.2202: MS-Windows: build failure with GUI and small features vim-patch:8.1.2203: running libvterm tests without the +terminal feature vim-patch:8.1.2204: crash on exit when closing terminals vim-patch:8.1.2209: LF in escape codes may be expanded to CR-LF vim-patch:8.1.2217: compiler warning for unused variable vim-patch:8.1.2224: cannot build Amiga version vim-patch:8.1.2237: mode() result depends on whether CURSOR_SHAPE is defined vim-patch:8.1.2248: CTRL-W dot does not work when modifyOtherKeys is enabled vim-patch:8.1.2254: MS-Windows: mouse scroll wheel doesn't work in popup vim-patch:8.1.2255: ":term ++shell" does not work on MS-Windows vim-patch:8.1.2256: test for ":term ++shell" fails on MS-Windows vim-patch:8.1.2260: terminal test may fail on MS-Windows vim-patch:8.1.2271: build error if FEAT_TAG_BINS is not defined vim-patch:8.1.2284: compiler warning for unused variable vim-patch:8.1.2291: memory leak when executing command in a terminal vim-patch:8.1.2301: MS-Windows GUI: drawing error when background color changes vim-patch:8.1.2311: warning for missing function prototype vim-patch:8.1.2316: FORTIFY_SOURCE can also be present in CPPFLAGS vim-patch:8.1.2318: MS-Windows GUI: main background shows in toolbar https://github.com/neovim/neovim/pull/11215 vim-patch:8.1.0084: user name completion does not work on MS-Windows vim-patch:8.1.0085: no test for completing user name and language vim-patch:8.1.0361: remote user not used for completion --- src/nvim/version.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index db02279fa3..f678b743c2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -68,9 +68,6 @@ NULL // clang-format off static const int included_patches[] = { - 2218, - 2207, - 2173, 1850, 1849, 1848, @@ -86,7 +83,7 @@ static const int included_patches[] = { 1838, 1837, 1836, - // 1835, + 1835, 1834, 1833, 1832, @@ -123,7 +120,7 @@ static const int included_patches[] = { 1801, 1800, 1799, - // 1798, + 1798, 1797, 1796, 1795, @@ -179,7 +176,7 @@ static const int included_patches[] = { // 1745, // 1744, // 1743, - // 1742, + 1742, 1741, 1740, 1739, @@ -196,10 +193,10 @@ static const int included_patches[] = { 1728, 1727, 1726, - // 1725, + 1725, 1724, 1723, - // 1722, + 1722, 1721, 1720, 1719, @@ -210,7 +207,7 @@ static const int included_patches[] = { 1714, 1713, // 1712, - // 1711, + 1711, 1710, 1709, 1708, @@ -236,7 +233,7 @@ static const int included_patches[] = { 1688, 1687, 1686, - // 1685, + 1685, 1684, 1683, 1682, @@ -255,8 +252,8 @@ static const int included_patches[] = { 1669, // 1668, 1667, - // 1666, - // 1665, + 1666, + 1665, 1664, 1663, 1662, @@ -297,12 +294,12 @@ static const int included_patches[] = { 1627, 1626, 1625, - // 1624, + 1624, 1623, 1622, 1621, 1620, - // 1619, + 1619, 1618, // 1617, // 1616, @@ -339,7 +336,7 @@ static const int included_patches[] = { 1585, 1584, 1583, - // 1582, + 1582, 1581, 1580, 1579, @@ -429,7 +426,7 @@ static const int included_patches[] = { // 1495, 1494, 1493, - // 1492, + 1492, // 1491, 1490, 1489, @@ -470,7 +467,7 @@ static const int included_patches[] = { // 1454, 1453, 1452, - // 1451, + 1451, 1450, // 1449, 1448, @@ -745,7 +742,7 @@ static const int included_patches[] = { 1179, 1178, 1177, - // 1176, + 1176, 1175, 1174, 1173, @@ -987,7 +984,7 @@ static const int included_patches[] = { 937, 936, 935, - // 934, + 934, 933, 932, 931, -- cgit From 1ff5b60cb96aea3b3f6ef9e2792c5797dfda72dc Mon Sep 17 00:00:00 2001 From: Joe Hermaszewski Date: Mon, 18 Nov 2019 15:38:27 +0800 Subject: vim-patch:8.1.0251: support full paths for 'backupdir' #11269 Problem: Using a full path is supported for 'directory' but not for 'backupdir'. (Mikolaj Machowski) Solution: Support 'backupdir' as well. (Christian Brabandt, closes vim/vim#179) https://github.com/vim/vim/commit/b782ba475a3f8f2b0be99dda164ba4545347f60f --- src/nvim/fileio.c | 51 ++++++++++++++++++++++++++++------- src/nvim/memline.c | 2 +- src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_backup.vim | 58 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 src/nvim/testdir/test_backup.vim (limited to 'src') diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 58e6b2ae92..fcf15638c7 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2650,6 +2650,7 @@ buf_write( */ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { FileInfo file_info; + const bool no_prepend_dot = false; if ((bkc & BKC_YES) || append) { /* "yes" */ backup_copy = TRUE; @@ -2737,6 +2738,7 @@ buf_write( int some_error = false; char_u *dirp; char_u *rootname; + char_u *p; /* * Try to make the backup in each directory in the 'bdir' option. @@ -2756,6 +2758,17 @@ buf_write( * Isolate one directory name, using an entry in 'bdir'. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + STRLEN(IObuff); + if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) { + // Ends with '//', Use Full path + if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname)) + != NULL) { + backup = (char_u *)modname((char *)p, (char *)backup_ext, + no_prepend_dot); + xfree(p); + } + } + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { some_error = TRUE; /* out of memory */ @@ -2764,10 +2777,14 @@ buf_write( FileInfo file_info_new; { - /* - * Make backup file name. - */ - backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); + // + // Make the backup file name. + // + if (backup == NULL) { + backup = (char_u *)modname((char *)rootname, (char *)backup_ext, + no_prepend_dot); + } + if (backup == NULL) { xfree(rootname); some_error = TRUE; /* out of memory */ @@ -2893,12 +2910,26 @@ nobackup: * Isolate one directory name and make the backup file name. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); - rootname = get_file_in_dir(fname, IObuff); - if (rootname == NULL) - backup = NULL; - else { - backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); - xfree(rootname); + p = IObuff + STRLEN(IObuff); + if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) { + // path ends with '//', use full path + if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname)) + != NULL) { + backup = (char_u *)modname((char *)p, (char *)backup_ext, + no_prepend_dot); + xfree(p); + } + } + + if (backup == NULL) { + rootname = get_file_in_dir(fname, IObuff); + if (rootname == NULL) { + backup = NULL; + } else { + backup = (char_u *)modname((char *)rootname, (char *)backup_ext, + no_prepend_dot); + xfree(rootname); + } } if (backup != NULL) { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 05cc62bb33..2824d57f49 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1437,7 +1437,7 @@ recover_names ( * Append the full path to name with path separators made into percent * signs, to dir. An unnamed buffer is handled as "" (/"") */ -static char *make_percent_swname(const char *dir, char *name) +char *make_percent_swname(const char *dir, char *name) FUNC_ATTR_NONNULL_ARG(1) { char *d = NULL; diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index f1274b01c8..5668f45dea 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,6 +2,7 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_backup.vim source test_behave.vim source test_cd.vim source test_changedtick.vim diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim new file mode 100644 index 0000000000..fa10430613 --- /dev/null +++ b/src/nvim/testdir/test_backup.vim @@ -0,0 +1,58 @@ +" Tests for the backup function + +func Test_backup() + set backup backupdir=. + new + call setline(1, ['line1', 'line2']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + let l = readfile('Xbackup.txt~') + call assert_equal(['line1', 'line2'], l) + bw! + set backup&vim backupdir&vim + call delete('Xbackup.txt') + call delete('Xbackup.txt~') +endfunc + +func Test_backup2() + set backup backupdir=.// + new + call setline(1, ['line1', 'line2', 'line3']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + sp *Xbackup.txt~ + call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) + let f=expand('%') + call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + bw! + bw! + call delete('Xbackup.txt') + call delete(f) + set backup&vim backupdir&vim +endfunc + +func Test_backup2_backupcopy() + set backup backupdir=.// backupcopy=yes + new + call setline(1, ['line1', 'line2', 'line3']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + sp *Xbackup.txt~ + call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) + let f=expand('%') + call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + bw! + bw! + call delete('Xbackup.txt') + call delete(f) + set backup&vim backupdir&vim backupcopy&vim +endfunc -- cgit From 025399aedb01642de9b7a7b054e4c85aac0c09d5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 19 Nov 2019 19:36:13 -0500 Subject: vim-patch:8.0.1449: slow redrawing with DirectX Problem: Slow redrawing with DirectX. Solution: Avoid calling gui_mch_flush() unnecessarily, especially when updating the cursor. (Ken Takata, closes vim/vim#2560) https://github.com/vim/vim/commit/a338adcf222b6a24e26ea5ae6a2ad27f914acb38 --- src/nvim/edit.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 25b6502b19..cd0f3f4b9d 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3056,7 +3056,9 @@ static void ins_compl_clear(void) XFREE_CLEAR(compl_orig_text); compl_enter_selects = false; // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } /// Check that Insert completion is active. @@ -4313,7 +4315,9 @@ static void ins_compl_delete(void) // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } // Insert the new text being completed. @@ -4335,6 +4339,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); + dict->dv_lock = VAR_FIXED; tv_dict_add_str( dict, S_LEN("word"), (const char *)EMPTY_IF_NULL(match->cp_str)); -- cgit From 756e2b5f65c6ffda50719f874d16c20f98027798 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 19 Nov 2019 21:07:10 -0500 Subject: vim-patch:8.0.1793: no test for "vim -g" Problem: No test for "vim -g". Solution: Add a test for "-g" and "-y". https://github.com/vim/vim/commit/248be5c5de723c4e2715c574fd920b8b1a1dfebb --- src/nvim/testdir/shared.vim | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 84f636077d..a5d83d6a25 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -252,6 +252,8 @@ func GetVimProg() endif endfunc +let g:valgrind_cnt = 1 + " Get the command to run Vim, with -u NONE and --headless arguments. " If there is an argument use it instead of "NONE". func GetVimCommand(...) @@ -267,6 +269,13 @@ func GetVimCommand(...) endif let cmd .= ' --headless -i NONE' let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') + + " If using valgrind, make sure every run uses a different log file. + if cmd =~ 'valgrind.*--log-file=' + let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let g:valgrind_cnt += 1 + endif + return cmd endfunc @@ -290,9 +299,6 @@ endfunc func RunVimPiped(before, after, arguments, pipecmd) let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' let cmd = GetVimCommand() - if cmd == '' - return 0 - endif let args = '' if len(a:before) > 0 call writefile(a:before, 'Xbefore.vim') -- cgit From b5f99a8d7312a880b6b6d04d01519c4ec72f435d Mon Sep 17 00:00:00 2001 From: Chuck Date: Thu, 21 Nov 2019 14:54:54 -0800 Subject: Fix terminal close error message formatting --- src/nvim/buffer.c | 2 +- src/nvim/po/af.po | 2 +- src/nvim/po/fi.po | 2 +- src/nvim/po/uk.po | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 79f339b3aa..c01364aadd 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1238,7 +1238,7 @@ do_buffer( return FAIL; } } else { - EMSG2(_("E89: %s will be killed(add ! to override)"), + EMSG2(_("E89: %s will be killed (add ! to override)"), (char *)buf->b_fname); return FAIL; } diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 61ad72d7e0..79048eac39 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -100,7 +100,7 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Kan nie vr eerste buffer gaan nie" #, fuzzy, c-format -#~ msgid "E89: %s will be killed(add ! to override)" +#~ msgid "E89: %s will be killed (add ! to override)" #~ msgstr "E189: \"%s\" bestaan (gebruik ! om te dwing)" #, c-format diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index 4612988c95..f568a34b3c 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -323,7 +323,7 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Ensimmäisen puskurin ohi ei voi edetä" #, fuzzy, c-format -#~ msgid "E89: %s will be killed(add ! to override)" +#~ msgid "E89: %s will be killed (add ! to override)" #~ msgstr "E189: %s on jo olemassa (lisää komentoon ! ohittaaksesi)" #, fuzzy, c-format diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 211d38e53a..19ea8e897a 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -87,8 +87,8 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Це вже найперший буфер" #, c-format -msgid "E89: %s will be killed(add ! to override)" -msgstr "E89: «%s» буде вбито(! щоб не зважати)" +msgid "E89: %s will be killed (add ! to override)" +msgstr "E89: «%s» буде вбито (! щоб не зважати)" #, c-format msgid "" -- cgit From d5f14b8372b3c8d441187eea659156534cb4c9ba Mon Sep 17 00:00:00 2001 From: Dennis B Date: Fri, 22 Nov 2019 20:55:04 +1100 Subject: Clear 'cc' in nvim_open_win 'minimal' style #11361 (#11427) * Clear 'cc' in nvim_open_win 'minimal' style #11361 Add 'colorcolumn' to the list of options that should be cleared when creating a 'minimal'-style floating window. --- src/nvim/api/vim.c | 7 ++++--- src/nvim/window.c | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 10f7dd1a7b..3535bc3186 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1074,9 +1074,10 @@ fail: /// float where the text should not be edited. Disables /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' -/// is changed to `auto`. The end-of-buffer region is hidden -/// by setting `eob` flag of 'fillchars' to a space char, -/// and clearing the |EndOfBuffer| region in 'winhighlight'. +/// is changed to `auto` and 'colorcolumn' is cleared. The +/// end-of-buffer region is hidden by setting `eob` flag of +/// 'fillchars' to a space char, and clearing the +/// |EndOfBuffer| region in 'winhighlight'. /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error diff --git a/src/nvim/window.c b/src/nvim/window.c index 2a7578e33c..dee36df433 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -633,6 +633,12 @@ void win_set_minimal_style(win_T *wp) xfree(wp->w_p_scl); wp->w_p_scl = (char_u *)xstrdup("auto"); } + + // colorcolumn: cleared + if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { + xfree(wp->w_p_cc); + wp->w_p_cc = (char_u *)xstrdup(""); + } } void win_config_float(win_T *wp, FloatConfig fconfig) -- cgit From 0f29deec80e09b05eae97ea16edd458b440b7c0e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 20 Nov 2019 21:32:11 -0500 Subject: vim-patch:8.1.1235: compiler warnings for using STRLEN() value Problem: Compiler warnings for using STRLEN() value. Solution: Cast to int. (Christian Brabandt, Mike Williams) https://github.com/vim/vim/commit/c0af78fa0869d39314336faef36239f62f09e148 --- src/nvim/tag.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 9e8c05fb1e..10efa65ddb 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1194,12 +1194,10 @@ static int find_tagfunc_tags( taglist = rettv.vval.v_list; TV_LIST_ITER_CONST(taglist, li, { - char_u *mfp; char_u *res_name; char_u *res_fname; char_u *res_cmd; char_u *res_kind; - int len; int has_extra = 0; int name_only = flags & TAG_NAMES; @@ -1208,7 +1206,7 @@ static int find_tagfunc_tags( break; } - len = 2; + size_t len = 2; res_name = NULL; res_fname = NULL; res_cmd = NULL; @@ -1254,15 +1252,7 @@ static int find_tagfunc_tags( break; } - if (name_only) { - mfp = vim_strsave(res_name); - } else { - mfp = (char_u *)xmalloc((int)sizeof(char_u) + len + 1); - } - - if (mfp == NULL) { - continue; - } + char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2); if (!name_only) { char_u *p = mfp; -- cgit From 970329ff8dd138fed74bb5e722ae7690f06ef4ac Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 20 Nov 2019 22:08:01 -0500 Subject: vim-patch:8.1.2269: tags file with very long line stops using binary search Problem: Tags file with very long line stops using binary search. Solution: Reallocate the buffer if needed. https://github.com/vim/vim/commit/dc9ef26845c6bf5ba63bfa8d00fc8a9bdc3b2de5 --- src/nvim/tag.c | 46 +++++++++++++++++---------------------- src/nvim/testdir/test_tagjump.vim | 23 ++++++++++++++++++-- 2 files changed, 41 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 10efa65ddb..3629b37c32 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1659,12 +1659,9 @@ find_tags( break; /* End the binary search without a match. */ else search_info.curr_offset = offset; - } - /* - * Skipping back (after a match during binary search). - */ - else if (state == TS_SKIP_BACK) { - search_info.curr_offset -= LSIZE * 2; + } else if (state == TS_SKIP_BACK) { + // Skipping back (after a match during binary search). + search_info.curr_offset -= lbuf_size * 2; if (search_info.curr_offset < 0) { search_info.curr_offset = 0; rewind(fp); @@ -1680,7 +1677,7 @@ find_tags( /* Adjust the search file offset to the correct position */ search_info.curr_offset_used = search_info.curr_offset; vim_fseek(fp, search_info.curr_offset, SEEK_SET); - eof = vim_fgets(lbuf, LSIZE, fp); + eof = vim_fgets(lbuf, lbuf_size, fp); if (!eof && search_info.curr_offset != 0) { /* The explicit cast is to work around a bug in gcc 3.4.2 * (repeated below). */ @@ -1690,12 +1687,12 @@ find_tags( vim_fseek(fp, search_info.low_offset, SEEK_SET); search_info.curr_offset = search_info.low_offset; } - eof = vim_fgets(lbuf, LSIZE, fp); + eof = vim_fgets(lbuf, lbuf_size, fp); } /* skip empty and blank lines */ while (!eof && vim_isblankline(lbuf)) { search_info.curr_offset = vim_ftell(fp); - eof = vim_fgets(lbuf, LSIZE, fp); + eof = vim_fgets(lbuf, lbuf_size, fp); } if (eof) { /* Hit end of file. Skip backwards. */ @@ -1711,10 +1708,9 @@ find_tags( else { /* skip empty and blank lines */ do { - if (use_cscope) - eof = cs_fgets(lbuf, LSIZE); - else - eof = vim_fgets(lbuf, LSIZE, fp); + eof = use_cscope + ? cs_fgets(lbuf, lbuf_size) + : vim_fgets(lbuf, lbuf_size, fp); } while (!eof && vim_isblankline(lbuf)); if (eof) { @@ -1839,19 +1835,14 @@ parse_line: // When the line is too long the NUL will not be in the // last-but-one byte (see vim_fgets()). // Has been reported for Mozilla JS with extremely long names. - // In that case we can't parse it and we ignore the line. - if (lbuf[LSIZE - 2] != NUL && !use_cscope) { - if (p_verbose >= 5) { - verbose_enter(); - MSG(_("Ignoring long line in tags file")); - verbose_leave(); - } - if (state != TS_LINEAR) { - // Avoid getting stuck. - linear = true; - state = TS_LINEAR; - vim_fseek(fp, search_info.low_offset, SEEK_SET); - } + // In that case we need to increase lbuf_size. + if (lbuf[lbuf_size - 2] != NUL && !use_cscope) { + lbuf_size *= 2; + xfree(lbuf); + lbuf = xmalloc(lbuf_size); + // this will try the same thing again, make sure the offset is + // different + search_info.curr_offset = 0; continue; } @@ -2654,6 +2645,9 @@ static int jumpto_tag( str = tagp.command; for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r'; ) { *pbuf_end++ = *str++; + if (pbuf_end - pbuf + 1 >= LSIZE) { + break; + } } *pbuf_end = NUL; diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index f93af76f17..fe98ef1ae2 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -449,7 +449,8 @@ func Test_tag_line_toolong() call assert_report(v:exception) catch /.*/ endtry - call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) + call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1]) + call writefile([ \ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML' \ ], 'Xtags') @@ -460,8 +461,26 @@ func Test_tag_line_toolong() call assert_report(v:exception) catch /.*/ endtry - call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) + call assert_equal('Searching tags file Xtags', split(execute('messages'), '\n')[-1]) + + " binary search works in file with long line + call writefile([ + \ 'asdfasfd nowhere 16', + \ 'foobar Xsomewhere 3; " 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567', + \ 'zasdfasfd nowhere 16', + \ ], 'Xtags') + call writefile([ + \ 'one', + \ 'two', + \ 'trhee', + \ 'four', + \ ], 'Xsomewhere') + tag foobar + call assert_equal('Xsomewhere', expand('%')) + call assert_equal(3, getcurpos()[1]) + call delete('Xtags') + call delete('Xsomewhere') set tags& let &verbose = old_vbs endfunc -- cgit From 8969efca8c8a4e2e2894e87e6f3236df9d96d665 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 20 Nov 2019 22:46:19 -0500 Subject: vim-patch:8.1.0723: cannot easily run specific test when in src/testdir Problem: Cannot run specific test when in src/testdir the same was as in the src directory. Solution: Move build rule to src/testdir/Makefile. https://github.com/vim/vim/commit/ec50401e1e1357a1340b3c92109fd4860e38a8ac Developer can omit '.res' suffix now. TEST_FILE=test_syntax make oldtest or make -C src/nvim/testdir test_syntax --- src/nvim/testdir/Makefile | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 08353509af..b828167fcb 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -48,7 +48,8 @@ NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \ test_listlbr \ test_largefile \ -NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT)) +NEW_TESTS ?= $(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT) +NEW_TESTS_RES ?= $(addsuffix .res,$(NEW_TESTS)) ifdef VALGRIND_GDB @@ -112,6 +113,16 @@ fixff: -$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \ dotest.in +# Execute an individual new style test, e.g.: +# make test_largefile +$(NEW_TESTS): + rm -f $@.res test.log messages + $(MAKE) -f Makefile $@.res + @if test -f test.log; then \ + cat test.log; \ + fi + cat messages + RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in @@ -172,7 +183,7 @@ newtests: newtestssilent cat messages && cat test.log; \ fi" -newtestssilent: $(NEW_TESTS) +newtestssilent: $(NEW_TESTS_RES) %.res: %.vim .gdbinit @echo "[OLDTEST] Running" $* -- cgit From a6714cf35cb2e93775ce42200d472b5622926a89 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 20 Nov 2019 23:19:57 -0500 Subject: vim-patch:8.1.1490: when a single test fails the exit code is not set Problem: When a single test fails the exit code is not set. (Daniel Hahler) Solution: Add an exit command. (closes vim/vim#4506) https://github.com/vim/vim/commit/4e0bf846279f3efa6299a98143033db1fdfa143a --- src/nvim/testdir/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index b828167fcb..f46cc97a20 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -118,10 +118,10 @@ fixff: $(NEW_TESTS): rm -f $@.res test.log messages $(MAKE) -f Makefile $@.res + cat messages @if test -f test.log; then \ - cat test.log; \ + exit 1; \ fi - cat messages RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok -- cgit From 273ab19452885fad7470f69c49e61250a9089873 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 20 Nov 2019 23:21:48 -0500 Subject: vim-patch:8.1.2183: running a test is a bit verbose Problem: Running a test is a bit verbose. Solution: Silence some messages. (Daniel Hahler, closes vim/vim#5070) https://github.com/vim/vim/commit/ba089307bb8d18ab79a6c4a28ceb8419a72209b3 --- src/nvim/testdir/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index f46cc97a20..1e9031e098 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -117,8 +117,8 @@ fixff: # make test_largefile $(NEW_TESTS): rm -f $@.res test.log messages - $(MAKE) -f Makefile $@.res - cat messages + @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res + @cat messages @if test -f test.log; then \ exit 1; \ fi -- cgit From 3d7d52ee8b4d887c0bfa08b5a7c15e1e2d784a35 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 21 Nov 2019 20:28:24 -0500 Subject: vim-patch:8.1.2329: mouse multiple click test is a bit flaky Problem: Mouse multiple click test is a bit flaky. Solution: Add it to the list of flaky tests. https://github.com/vim/vim/commit/44f0bd878ab6525eb50f2e05a13c0854164cdec2 --- src/nvim/testdir/runtest.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 5c2e570adf..40f7ded2fa 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -297,6 +297,7 @@ let s:flaky_tests = [ \ 'Test_repeat_three()', \ 'Test_state()', \ 'Test_stop_all_in_callback()', + \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', -- cgit From cb107d28a00c3379dfb17df252c742c87369afcb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 21 Nov 2019 20:29:13 -0500 Subject: vim-patch:8.1.2330: vi' does not always work when 'selection' is exclusive Problem: vi' does not always work when 'selection' is exclusive. Solution: Adjust start position. https://github.com/vim/vim/commit/94d9f4fa65bce6f116cf89bfdabdf5a06509056f --- src/nvim/search.c | 56 +++++++++++++++++++++-------------- src/nvim/testdir/test_textobjects.vim | 7 +++++ 2 files changed, 40 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index a298f7333e..5e32715e49 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3777,30 +3777,31 @@ find_prev_quote( return col_start; } -/* - * Find quote under the cursor, cursor at end. - * Returns TRUE if found, else FALSE. - */ -int -current_quote( +// Find quote under the cursor, cursor at end. +// Returns true if found, else false. +bool current_quote( oparg_T *oap, long count, - int include, /* TRUE == include quote char */ - int quotechar /* Quote character */ + bool include, // true == include quote char + int quotechar // Quote character ) + FUNC_ATTR_NONNULL_ALL { char_u *line = get_cursor_line_ptr(); int col_end; int col_start = curwin->w_cursor.col; bool inclusive = false; - int vis_empty = true; // Visual selection <= 1 char - int vis_bef_curs = false; // Visual starts before cursor - int inside_quotes = false; // Looks like "i'" done before - int selected_quote = false; // Has quote inside selection + bool vis_empty = true; // Visual selection <= 1 char + bool vis_bef_curs = false; // Visual starts before cursor + bool did_exclusive_adj = false; // adjusted pos for 'selection' + bool inside_quotes = false; // Looks like "i'" done before + bool selected_quote = false; // Has quote inside selection int i; - int restore_vis_bef = false; // resotre VIsual on abort + bool restore_vis_bef = false; // resotre VIsual on abort - // Correct cursor when 'selection' is "exclusive". + // When 'selection' is "exclusive" move the cursor to where it would be + // with 'selection' "inclusive", so that the logic is the same for both. + // The cursor then is moved forward after adjusting the area. if (VIsual_active) { // this only works within one line if (VIsual.lnum != curwin->w_cursor.lnum) { @@ -3810,6 +3811,14 @@ current_quote( vis_bef_curs = lt(VIsual, curwin->w_cursor); vis_empty = equalpos(VIsual, curwin->w_cursor); if (*p_sel == 'e') { + if (vis_bef_curs) { + dec_cursor(); + did_exclusive_adj = true; + } else if (!vis_empty) { + dec(&VIsual); + did_exclusive_adj = true; + } + vis_empty = equalpos(VIsual, curwin->w_cursor); if (!vis_bef_curs && !vis_empty) { // VIsual needs to be start of Visual selection. pos_T t = curwin->w_cursor; @@ -3819,8 +3828,6 @@ current_quote( vis_bef_curs = true; restore_vis_bef = true; } - dec_cursor(); - vis_empty = equalpos(VIsual, curwin->w_cursor); } } @@ -3846,7 +3853,7 @@ current_quote( /* Find out if we have a quote in the selection. */ while (i <= col_end) if (line[i++] == quotechar) { - selected_quote = TRUE; + selected_quote = true; break; } } @@ -3935,8 +3942,8 @@ current_quote( } } - /* When "include" is TRUE, include spaces after closing quote or before - * the starting quote. */ + // When "include" is true, include spaces after closing quote or before + // the starting quote. if (include) { if (ascii_iswhite(line[col_end + 1])) while (ascii_iswhite(line[col_end + 1])) @@ -3982,9 +3989,10 @@ current_quote( inclusive = true; if (VIsual_active) { if (vis_empty || vis_bef_curs) { - /* decrement cursor when 'selection' is not exclusive */ - if (*p_sel != 'e') + // decrement cursor when 'selection' is not exclusive + if (*p_sel != 'e') { dec_cursor(); + } } else { /* Cursor is at start of Visual area. Set the end of the Visual * area when it was just inside quotes or it didn't end at a @@ -4008,11 +4016,13 @@ current_quote( oap->inclusive = inclusive; } - return OK; + return true; abort_search: if (VIsual_active && *p_sel == 'e') { - inc_cursor(); + if (did_exclusive_adj) { + inc_cursor(); + } if (restore_vis_bef) { pos_T t = curwin->w_cursor; diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 448b2dc51c..b20c4df311 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -46,11 +46,18 @@ func Test_quote_selection_selection_exclusive() new call setline(1, "a 'bcde' f") set selection=exclusive + exe "norm! fdvhi'y" call assert_equal('bcde', @") + let @"='dummy' exe "norm! $gevi'y" call assert_equal('bcde', @") + + let @"='dummy' + exe "norm! 0fbhvi'y" + call assert_equal('bcde', @") + set selection&vim bw! endfunc -- cgit From 7639ccdd0d5033b8e21776df52612842eaba9210 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 22 Nov 2019 23:40:03 -0500 Subject: vim-patch:8.1.1951: mouse double click test is a bit flaky Problem: Mouse double click test is a bit flaky. Solution: Add to list of flaky tests. Update a couple of comments. https://github.com/vim/vim/commit/f1699968baf3619a4147b44c891ba4a0985e7656 --- src/nvim/testdir/runtest.vim | 1 + src/nvim/testdir/shared.vim | 2 +- src/nvim/testdir/test_substitute.vim | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 40f7ded2fa..2d4134a644 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -297,6 +297,7 @@ let s:flaky_tests = [ \ 'Test_repeat_three()', \ 'Test_state()', \ 'Test_stop_all_in_callback()', + \ 'Test_term_mouse_double_click_to_create_tab', \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index a5d83d6a25..74fc516d62 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -279,7 +279,7 @@ func GetVimCommand(...) return cmd endfunc -" Get the command to run Vim, with --clean. +" Get the command to run Vim, with --clean instead of "-u NONE". func GetVimCommandClean() let cmd = GetVimCommand() let cmd = substitute(cmd, '-u NONE', '--clean', '') diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e209310a05..e94bd22cea 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -241,7 +241,7 @@ func Test_sub_cmd_3() call Run_SubCmd_Tests(tests) endfunc -" Test for submatch() on :substitue. +" Test for submatch() on :substitute. func Test_sub_cmd_4() set magic& set cpo& -- cgit From bdebe8516c90839930acbfe3c6f1e354ee56df3f Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 29 Jun 2018 13:35:42 +0200 Subject: bufhl: use extmark column adjustment for bufhl NB: this is not the final implementation. Bufhl should be made a part of the extmark tree, so that "start" adjustment just works automatically. But "stop" will still need some ad-hoc trickery, until extended marks natively support ranges (hopefully sooner than forever). --- src/nvim/buffer.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/edit.c | 3 +- src/nvim/mark_extended.c | 4 +++ 3 files changed, 86 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 79f339b3aa..1244b8502c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -5626,6 +5626,86 @@ void bufhl_mark_adjust(buf_T* buf, } } +/// Adjust a placed highlight for column changes and joined/broken lines +bool bufhl_mark_col_adjust(buf_T *buf, + linenr_T lnum, + colnr_T mincol, + long lnum_amount, + long col_amount) +{ + bool moved = false; + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false); + if (!lineinfo) { + // Old line empty, nothing to do + return false; + } + // Create the new line below only if needed + BufhlLine *lineinfo2 = NULL; + + colnr_T delcol = MAXCOL; + if (lnum_amount == 0 && col_amount < 0) { + delcol = mincol+(int)col_amount; + } + + size_t newidx = 0; + for (size_t i = 0; i < kv_size(lineinfo->items); i++) { + BufhlItem *item = &kv_A(lineinfo->items, i); + bool delete = false; + if (item->start >= mincol) { + moved = true; + item->start += (int)col_amount; + if (item->stop < MAXCOL) { + item->stop += (int)col_amount; + } + if (lnum_amount != 0) { + if (lineinfo2 == NULL) { + lineinfo2 = bufhl_tree_ref(&buf->b_bufhl_info, + lnum+lnum_amount, true); + } + kv_push(lineinfo2->items, *item); + delete = true; + } + } else { + if (item->start >= delcol) { + moved = true; + item->start = delcol; + } + if (item->stop == MAXCOL || item->stop+1 >= mincol) { + if (item->stop == MAXCOL) { + if (delcol < MAXCOL + && delcol > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { + delete = true; + } + } else { + moved = true; + item->stop += (int)col_amount; + } + assert(lnum_amount >= 0); + if (lnum_amount > 0) { + item->stop = MAXCOL; + } + } else if (item->stop+1 >= delcol) { + moved = true; + item->stop = delcol-1; + } + // we covered the entire range with a visual delete or something + if (item->stop < item->start) { + delete = true; + } + } + + if (!delete) { + if (i != newidx) { + kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i); + } + newidx++; + } + } + kv_size(lineinfo->items) = newidx; + + return moved; +} + /// Get highlights to display at a specific line /// diff --git a/src/nvim/edit.c b/src/nvim/edit.c index cd0f3f4b9d..6e38801728 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -5599,9 +5599,10 @@ insertchar ( do_digraph(-1); /* clear digraphs */ do_digraph(buf[i-1]); /* may be the start of a digraph */ buf[i] = NUL; + colnr_T col_start = curwin->w_cursor.col; ins_str(buf); extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)(curwin->w_cursor.col + 1), 0, + (colnr_T)(col_start + 1), 0, (long)STRLEN(buf), kExtmarkUndo); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index 01745f484d..91c2f919ce 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -910,6 +910,9 @@ void extmark_col_adjust(buf_T *buf, linenr_T lnum, bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount, false, col_amount); + marks_moved |= bufhl_mark_col_adjust(buf, lnum, mincol, + lnum_amount, col_amount); + if (undo == kExtmarkUndo && marks_moved) { u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount); } @@ -938,6 +941,7 @@ void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0, true, (long)endcol); + marks_moved |= bufhl_mark_col_adjust(buf, lnum, endcol, 0, mincol-(endcol+1)); // Deletes at the end of the line have different behaviour than the normal // case when deleted. // Cleanup any marks that are floating beyond the end of line. -- cgit From 2cc83c961c0533222890adec51ac56041fb2a6b4 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 14 Nov 2019 20:06:13 +0100 Subject: refactor: use inserted_bytes pattern from vim This covers all "small" inserts and deletes in insert mode, as well as a few more cases like small normal mode deletes vim-patch:8.1.0678: text properties as not adjusted for inserted text --- src/nvim/change.c | 24 +++++++++++++++++++++--- src/nvim/edit.c | 15 --------------- src/nvim/ops.c | 28 ++++------------------------ 3 files changed, 25 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/nvim/change.c b/src/nvim/change.c index 7558055696..8a782c2b20 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -359,6 +359,24 @@ void changed_bytes(linenr_T lnum, colnr_T col) } } +/// insert/delete bytes at column +/// +/// Like changed_bytes() but also adjust extmark for "added" bytes. +/// When "added" is negative text was deleted. +static void inserted_bytes(linenr_T lnum, colnr_T col, int added) +{ + if (added > 0) { + extmark_col_adjust(curbuf, lnum, col+1, 0, added, kExtmarkUndo); + } else if (added < 0) { + // TODO(bfredl): next revision of extmarks should handle both these + // with the same entry point. Also with more sane params.. + extmark_col_adjust_delete(curbuf, lnum, col+2, + col+(-added)+1, kExtmarkUndo, 0); + } + + changed_bytes(lnum, col); +} + /// Appended "count" lines below line "lnum" in the current buffer. /// Must be called AFTER the change and after mark_adjust(). /// Takes care of marking the buffer to be redrawn and sets the changed flag. @@ -630,7 +648,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying - changed_bytes(lnum, (colnr_T)col); + inserted_bytes(lnum, (colnr_T)col, (int)(newlen - oldlen)); // If we're in Insert or Replace mode and 'showmatch' is set, then briefly // show the match for right parens and braces. @@ -676,7 +694,7 @@ void ins_str(char_u *s) assert(bytes >= 0); memmove(newp + col + newlen, oldp + col, (size_t)bytes); ml_replace(lnum, newp, false); - changed_bytes(lnum, col); + inserted_bytes(lnum, col, newlen); curwin->w_cursor.col += newlen; } @@ -797,7 +815,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } // mark the buffer as changed and prepare for displaying - changed_bytes(lnum, curwin->w_cursor.col); + inserted_bytes(lnum, col, -count); return OK; } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 6e38801728..eecea03a19 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -5599,11 +5599,7 @@ insertchar ( do_digraph(-1); /* clear digraphs */ do_digraph(buf[i-1]); /* may be the start of a digraph */ buf[i] = NUL; - colnr_T col_start = curwin->w_cursor.col; ins_str(buf); - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)(col_start + 1), 0, - (long)STRLEN(buf), kExtmarkUndo); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); i = 1; @@ -5614,9 +5610,6 @@ insertchar ( } else { int cc; - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)(curwin->w_cursor.col + 1), 0, - 1, kExtmarkUndo); if ((cc = utf_char2len(c)) > 1) { char_u buf[MB_MAXBYTES + 1]; @@ -8507,14 +8500,6 @@ static bool ins_tab(void) temp -= get_nolist_virtcol() % temp; - // Move extmarks - extmark_col_adjust(curbuf, - curwin->w_cursor.lnum, - curwin->w_cursor.col, - 0, - temp, - kExtmarkUndo); - /* * Insert the first space with ins_char(). It will delete one char in * replace mode. Insert the rest with ins_str(); it will not delete any diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2301b2159f..f2d35d5e43 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1644,8 +1644,6 @@ int op_delete(oparg_T *oap) curwin->w_cursor.col = 0; (void)del_bytes((colnr_T)n, !virtual_op, oap->op_type == OP_DELETE && !oap->is_VIsual); - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-n, kExtmarkUndo); curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); } @@ -1685,7 +1683,6 @@ setmarks: if (oap->is_VIsual == false) { endcol = MAX(endcol - 1, mincol); } - extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0); } return OK; } @@ -2279,7 +2276,7 @@ void op_insert(oparg_T *oap, long count1) colnr_T col = oap->start.col; for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) { extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo); - } + } } /* @@ -4279,14 +4276,14 @@ format_lines( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0, kExtmarkUndo); + (long)-next_leader_len, 0, kExtmarkNOOP); } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND int indent = (int)getwhitecols_curline(); if (indent > 0) { - (void)del_bytes(indent, FALSE, FALSE); + (void)del_bytes(indent, false, false); mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0, kExtmarkUndo); + (colnr_T)0, 0L, (long)-indent, 0, kExtmarkNOOP); } } curwin->w_cursor.lnum--; @@ -4951,23 +4948,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curbuf->b_op_end.col--; } - // if buf1 wasn't allocated, only a singe ASCII char was changed in-place. - if (did_change && buf1 != NULL) { - extmark_col_adjust_delete(curbuf, - pos->lnum, - startpos.col + 2, - endpos.col + 1 + length, - kExtmarkUndo, - 0); - long col_amount = (long)STRLEN(buf1); - extmark_col_adjust(curbuf, - pos->lnum, - startpos.col + 1, - 0, - col_amount, - kExtmarkUndo); - } - theend: xfree(buf1); if (visual) { -- cgit From 4c532f54695e380113ec3e3db8b915e838a0bb16 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 23 Nov 2019 15:42:17 -0500 Subject: vim-patch:8.1.0471: some tests are flaky or fail on some systems Problem: Some tests are flaky or fail on some systems. Solution: Increase waiting time for port number. Use "cmd /c" to execute "echo" on win32. (Ken Takata, closes vim/vim#3534) https://github.com/vim/vim/commit/453ce7c16b1001f23f2281b259176c6a00d82999 --- src/nvim/testdir/shared.vim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 74fc516d62..b0b59db686 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -69,7 +69,8 @@ endfunc " Read the port number from the Xportnr file. func GetPort() let l = [] - for i in range(200) + " with 200 it sometimes failed + for i in range(400) try let l = readfile("Xportnr") catch @@ -284,6 +285,10 @@ func GetVimCommandClean() let cmd = GetVimCommand() let cmd = substitute(cmd, '-u NONE', '--clean', '') let cmd = substitute(cmd, '--headless', '', '') + + " Optionally run Vim under valgrind + " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd + return cmd endfunc -- cgit From 222637c341700294a059651bcea62d2e91795646 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 24 Nov 2019 00:56:58 -0500 Subject: vim-patch:8.1.1334: respect shortmess=F when buffer is hidden #11443 Problem: When buffer is hidden "F" in 'shortmess' is not used. Solution: Check the "F" flag in 'shortmess' when the buffer is already loaded. (Jason Franklin) Add test_getvalue() to be able to test this. https://github.com/vim/vim/commit/eda652215abf696f86b872888945a2d2dd8c7192 test_getvalue() is not implemented. It is only used for checking "need_fileinfo" internal variable. --- src/nvim/buffer.c | 6 ++++-- src/nvim/testdir/test_options.vim | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 1244b8502c..0cd02ce36b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1585,10 +1585,12 @@ void enter_buffer(buf_T *buf) open_buffer(false, NULL, 0); } else { - if (!msg_silent) { + if (!msg_silent && !shortmess(SHM_FILEINFO)) { need_fileinfo = true; // display file info after redraw } - (void)buf_check_timestamp(curbuf, false); // check if file changed + // check if file changed + (void)buf_check_timestamp(curbuf, false); + curwin->w_topline = 1; curwin->w_topfill = 0; apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index f4f5cbca61..6fcc372591 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -476,13 +476,19 @@ func Test_shortmess_F2() call assert_match('file2', execute('bn', '')) set shortmess+=F call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) set hidden call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) set nohidden call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) + " call assert_false(test_getvalue('need_fileinfo')) " Accommodate Nvim default. set shortmess-=F call assert_match('file1', execute('bn', '')) -- cgit From d0d38fc36e0c1602186aa540417070fa6c1e2746 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 24 Nov 2019 02:28:48 -0800 Subject: Lua: vim.env, vim.{g,v,w,bo,wo} #11442 - Add vim variable meta accessors: vim.env, vim.{g,v,w,bo,wo} - Redo gen_char_blob to generate multiple blobs instead of just one so that multiple Lua modules can be inlined. - Reorder vim.lua inclusion so that it can use previously defined C functions and utility functions like vim.shared and vim.inspect things. - Inline shared.lua into nvim, but also keep it available in runtime. --- src/nvim/CMakeLists.txt | 11 +- src/nvim/generators/gen_char_blob.lua | 70 ++++++----- src/nvim/lua/executor.c | 25 +++- src/nvim/lua/vim.lua | 227 +++++++++++++++++++++++----------- 4 files changed, 219 insertions(+), 114 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index b00ac866b7..988021ca7a 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -52,7 +52,8 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) -set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) +set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) +set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua) set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") @@ -317,11 +318,13 @@ add_custom_command( add_custom_command( OUTPUT ${VIM_MODULE_FILE} - COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE} - ${VIM_MODULE_FILE} vim_module + COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE} + ${LUA_VIM_MODULE_SOURCE} vim_module + ${LUA_SHARED_MODULE_SOURCE} shared_module DEPENDS ${CHAR_BLOB_GENERATOR} - ${VIM_MODULE_SOURCE} + ${LUA_VIM_MODULE_SOURCE} + ${LUA_SHARED_MODULE_SOURCE} ) list(APPEND NVIM_GENERATED_SOURCES diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua index 1702add2e4..a7dad50d48 100644 --- a/src/nvim/generators/gen_char_blob.lua +++ b/src/nvim/generators/gen_char_blob.lua @@ -1,49 +1,59 @@ if arg[1] == '--help' then print('Usage:') - print(' gencharblob.lua source target varname') + print(' '..arg[0]..' target source varname [source varname]...') print('') print('Generates C file with big uint8_t blob.') print('Blob will be stored in a static const array named varname.') os.exit() end -assert(#arg == 3) +assert(#arg >= 3 and (#arg - 1) % 2 == 0) -local source_file = arg[1] -local target_file = arg[2] -local varname = arg[3] - -local source = io.open(source_file, 'r') +local target_file = arg[1] or error('Need a target file') local target = io.open(target_file, 'w') target:write('#include \n\n') -target:write(('static const uint8_t %s[] = {\n'):format(varname)) - -local num_bytes = 0 -local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line -target:write(' ') - -local increase_num_bytes -increase_num_bytes = function() - num_bytes = num_bytes + 1 - if num_bytes == MAX_NUM_BYTES then - num_bytes = 0 - target:write('\n ') + +local varnames = {} +for argi = 2, #arg, 2 do + local source_file = arg[argi] + local varname = arg[argi + 1] + if varnames[varname] then + error(string.format("varname %q is already specified for file %q", varname, varnames[varname])) end -end + varnames[varname] = source_file + + local source = io.open(source_file, 'r') + or error(string.format("source_file %q doesn't exist", source_file)) + + target:write(('static const uint8_t %s[] = {\n'):format(varname)) -for line in source:lines() do - for i = 1,string.len(line) do - local byte = string.byte(line, i) - assert(byte ~= 0) - target:write(string.format(' %3u,', byte)) + local num_bytes = 0 + local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line + target:write(' ') + + local increase_num_bytes + increase_num_bytes = function() + num_bytes = num_bytes + 1 + if num_bytes == MAX_NUM_BYTES then + num_bytes = 0 + target:write('\n ') + end + end + + for line in source:lines() do + for i = 1, string.len(line) do + local byte = line:byte(i) + assert(byte ~= 0) + target:write(string.format(' %3u,', byte)) + increase_num_bytes() + end + target:write(string.format(' %3u,', string.byte('\n', 1))) increase_num_bytes() end - target:write(string.format(' %3u,', string.byte('\n', 1))) - increase_num_bytes() -end -target:write(' 0};\n') + target:write(' 0};\n') + source:close() +end -source:close() target:close() diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 093c130c5f..5450f62f54 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -268,12 +268,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL #endif // vim - const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") - || lua_pcall(lstate, 0, LUA_MULTRET, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } + lua_newtable(lstate); // vim.api nlua_add_api_functions(lstate); // vim.types, vim.type_idx, vim.val_idx @@ -334,6 +329,24 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setglobal(lstate, "vim"); + { + const char *code = (char *)&shared_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); + return 1; + } + } + + { + const char *code = (char *)&vim_module[0]; + if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); + return 1; + } + } + return 0; } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 1665a55aff..8019511317 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -33,35 +33,35 @@ -- - https://github.com/bakpakin/Fennel (pretty print, repl) -- - https://github.com/howl-editor/howl/tree/master/lib/howl/util +local vim = vim +assert(vim) -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} -local function _system(cmd) - local out = vim.api.nvim_call_function('system', { cmd }) - local err = vim.api.nvim_get_vvar('shell_error') +function vim._system(cmd) + local out = vim.fn.system(cmd) + local err = vim.v.shell_error return err, out end -- Gets process info from the `ps` command. -- Used by nvim_get_proc() as a fallback. -local function _os_proc_info(pid) +function vim._os_proc_info(pid) if pid == nil or pid <= 0 or type(pid) ~= 'number' then error('invalid pid') end local cmd = { 'ps', '-p', pid, '-o', 'comm=', } - local err, name = _system(cmd) - if 1 == err and string.gsub(name, '%s*', '') == '' then + local err, name = vim._system(cmd) + if 1 == err and vim.trim(name) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end - local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', }) -- Remove trailing whitespace. - name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '') - ppid = string.gsub(ppid, '%s+$', '') - ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + name = vim.trim(name):gsub('^.*/', '') + ppid = tonumber(ppid) or -1 return { name = name, pid = pid, @@ -71,20 +71,19 @@ end -- Gets process children from the `pgrep` command. -- Used by nvim_get_proc_children() as a fallback. -local function _os_proc_children(ppid) +function vim._os_proc_children(ppid) if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then error('invalid ppid') end local cmd = { 'pgrep', '-P', ppid, } - local err, rv = _system(cmd) - if 1 == err and string.gsub(rv, '%s*', '') == '' then + local err, rv = vim._system(cmd) + if 1 == err and vim.trim(rv) == '' then return {} -- Process not found. elseif 0 ~= err then - local args_str = vim.api.nvim_call_function('string', { cmd }) - error('command failed: '..args_str) + error('command failed: '..vim.fn.string(cmd)) end local children = {} - for s in string.gmatch(rv, '%S+') do + for s in rv:gmatch('%S+') do local i = tonumber(s) if i ~= nil then table.insert(children, i) @@ -98,7 +97,7 @@ end -- Last inserted paths. Used to clear out items from package.[c]path when they -- are no longer in &runtimepath. local last_nvim_paths = {} -local function _update_package_paths() +function vim._update_package_paths() local cur_nvim_paths = {} local rtps = vim.api.nvim_list_runtime_paths() local sep = package.config:sub(1, 1) @@ -162,35 +161,35 @@ local function inspect(object, options) -- luacheck: no unused error(object, options) -- Stub for gen_vimdoc.py end ---- Paste handler, invoked by |nvim_paste()| when a conforming UI ---- (such as the |TUI|) pastes text into the editor. ---- ---- Example: To remove ANSI color codes when pasting: ----
---- vim.paste = (function(overridden)
----   return function(lines, phase)
----     for i,line in ipairs(lines) do
----       -- Scrub ANSI color codes from paste input.
----       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
----     end
----     overridden(lines, phase)
----   end
---- end)(vim.paste)
---- 
---- ---@see |paste| ---- ---@param lines |readfile()|-style list of lines to paste. |channel-lines| ---@param phase -1: "non-streaming" paste: the call contains all lines. ---- If paste is "streamed", `phase` indicates the stream state: ---- - 1: starts the paste (exactly once) ---- - 2: continues the paste (zero or more times) ---- - 3: ends the paste (exactly once) ---@returns false if client should cancel the paste. -local function paste(lines, phase) end -- luacheck: no unused -paste = (function() +do local tdots, tick, got_line1 = 0, 0, false - return function(lines, phase) + + --- Paste handler, invoked by |nvim_paste()| when a conforming UI + --- (such as the |TUI|) pastes text into the editor. + --- + --- Example: To remove ANSI color codes when pasting: + ---
+  --- vim.paste = (function(overridden)
+  ---   return function(lines, phase)
+  ---     for i,line in ipairs(lines) do
+  ---       -- Scrub ANSI color codes from paste input.
+  ---       lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+  ---     end
+  ---     overridden(lines, phase)
+  ---   end
+  --- end)(vim.paste)
+  --- 
+ --- + --@see |paste| + --- + --@param lines |readfile()|-style list of lines to paste. |channel-lines| + --@param phase -1: "non-streaming" paste: the call contains all lines. + --- If paste is "streamed", `phase` indicates the stream state: + --- - 1: starts the paste (exactly once) + --- - 2: continues the paste (zero or more times) + --- - 3: ends the paste (exactly once) + --@returns false if client should cancel the paste. + function vim.paste(lines, phase) local call = vim.api.nvim_call_function local now = vim.loop.now() local mode = call('mode', {}):sub(1,1) @@ -230,20 +229,33 @@ paste = (function() end return true -- Paste will not continue if not returning `true`. end -end)() +end --- Defers callback `cb` until the Nvim API is safe to call. --- ---@see |lua-loop-callbacks| ---@see |vim.schedule()| ---@see |vim.in_fast_event()| -local function schedule_wrap(cb) +function vim.schedule_wrap(cb) return (function (...) local args = {...} vim.schedule(function() cb(unpack(args)) end) end) end +-- vim.fn.{func}(...) +vim.fn = setmetatable({}, { + __index = function(t, key) + local function _fn(...) + return vim.call(key, ...) + end + t[key] = _fn + return _fn + end +}) + +-- These are for loading runtime modules lazily since they aren't available in +-- the nvim binary as specified in executor.c local function __index(t, key) if key == 'inspect' then t.inspect = require('vim.inspect') @@ -251,10 +263,6 @@ local function __index(t, key) elseif key == 'treesitter' then t.treesitter = require('vim.treesitter') return t.treesitter - elseif require('vim.shared')[key] ~= nil then - -- Expose all `vim.shared` functions on the `vim` module. - t[key] = require('vim.shared')[key] - return t[key] elseif require('vim.uri')[key] ~= nil then -- Expose all `vim.uri` functions on the `vim` module. t[key] = require('vim.uri')[key] @@ -265,29 +273,100 @@ local function __index(t, key) end end +setmetatable(vim, { + __index = __index +}) --- vim.fn.{func}(...) -local function _fn_index(t, key) - local function _fn(...) - return vim.call(key, ...) +do + local a = vim.api + local validate = vim.validate + local function make_meta_accessor(get, set, del) + validate { + get = {get, 'f'}; + set = {set, 'f'}; + del = {del, 'f', true}; + } + local mt = {} + if del then + function mt:__newindex(k, v) + if v == nil then + return del(k) + end + return set(k, v) + end + else + function mt:__newindex(k, v) + return set(k, v) + end + end + function mt:__index(k) + return get(k) + end + return setmetatable({}, mt) + end + local function pcall_ret(status, ...) + if status then return ... end + end + local function nil_wrap(fn) + return function(...) + return pcall_ret(pcall(fn, ...)) + end + end + vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) + vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) + vim.o = make_meta_accessor(nil_wrap(a.nvim_get_option), a.nvim_set_option) + vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv) + -- TODO(ashkan) if/when these are available from an API, generate them + -- instead of hardcoding. + local window_options = { + arab = true; arabic = true; breakindent = true; breakindentopt = true; + bri = true; briopt = true; cc = true; cocu = true; + cole = true; colorcolumn = true; concealcursor = true; conceallevel = true; + crb = true; cuc = true; cul = true; cursorbind = true; + cursorcolumn = true; cursorline = true; diff = true; fcs = true; + fdc = true; fde = true; fdi = true; fdl = true; + fdm = true; fdn = true; fdt = true; fen = true; + fillchars = true; fml = true; fmr = true; foldcolumn = true; + foldenable = true; foldexpr = true; foldignore = true; foldlevel = true; + foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true; + foldtext = true; lbr = true; lcs = true; linebreak = true; + list = true; listchars = true; nu = true; number = true; + numberwidth = true; nuw = true; previewwindow = true; pvw = true; + relativenumber = true; rightleft = true; rightleftcmd = true; rl = true; + rlc = true; rnu = true; scb = true; scl = true; + scr = true; scroll = true; scrollbind = true; signcolumn = true; + spell = true; statusline = true; stl = true; wfh = true; + wfw = true; winbl = true; winblend = true; winfixheight = true; + winfixwidth = true; winhighlight = true; winhl = true; wrap = true; + } + local function new_buf_opt_accessor(bufnr) + local function get(k) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + return a.nvim_buf_get_option(bufnr, k) + end + local function set(k, v) + if window_options[k] then + return a.nvim_err_writeln(k.." is a window option, not a buffer option") + end + return a.nvim_buf_set_option(bufnr, k, v) + end + return make_meta_accessor(nil_wrap(get), set) + end + vim.bo = new_buf_opt_accessor(0) + getmetatable(vim.bo).__call = function(_, bufnr) + return new_buf_opt_accessor(bufnr) + end + local function new_win_opt_accessor(winnr) + local function get(k) return a.nvim_win_get_option(winnr, k) end + local function set(k, v) return a.nvim_win_set_option(winnr, k, v) end + return make_meta_accessor(nil_wrap(get), set) + end + vim.wo = new_win_opt_accessor(0) + getmetatable(vim.wo).__call = function(_, winnr) + return new_win_opt_accessor(winnr) end - t[key] = _fn - return _fn end -local fn = setmetatable({}, {__index=_fn_index}) - -local module = { - _update_package_paths = _update_package_paths, - _os_proc_children = _os_proc_children, - _os_proc_info = _os_proc_info, - _system = _system, - paste = paste, - schedule_wrap = schedule_wrap, - fn=fn, -} - -setmetatable(module, { - __index = __index -}) return module -- cgit From 8819b5c06eea1f47d6a8f2c00498e55a580d5519 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 24 Nov 2019 23:10:25 -0500 Subject: vim-patch:8.1.0461: quickfix: change comment style #11453 Problem: Quickfix code uses too many /* */ comments. Solution: Change to // comments. (Yegappan Lakshmanan) https://github.com/vim/vim/commit/00bf8cd2115be7c14258aee48c0a7568147c9cd7 --- src/nvim/buffer.c | 6 +- src/nvim/ex_cmds.c | 2 +- src/nvim/fileio.c | 3 +- src/nvim/main.c | 2 +- src/nvim/memline.c | 5 +- src/nvim/quickfix.c | 467 ++++++++++++++++++++++------------------------------ 6 files changed, 209 insertions(+), 276 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 0cd02ce36b..544f3a9bb4 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -411,11 +411,11 @@ bool buf_valid(buf_T *buf) /// caller should get a new buffer very soon! /// The 'bufhidden' option can force freeing and deleting. /// @param abort_if_last -/// If TRUE, do not close the buffer if autocommands cause +/// If true, do not close the buffer if autocommands cause /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. -void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) +void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -2694,7 +2694,7 @@ setfname( buf_T *buf, char_u *ffname, char_u *sfname, - int message // give message when buffer already exists + bool message // give message when buffer already exists ) { buf_T *obuf = NULL; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4725246764..0c3b467612 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1587,7 +1587,7 @@ int rename_buffer(char_u *new_fname) xfname = curbuf->b_fname; curbuf->b_ffname = NULL; curbuf->b_sfname = NULL; - if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) { + if (setfname(curbuf, new_fname, NULL, true) == FAIL) { curbuf->b_ffname = fname; curbuf->b_sfname = sfname; return FAIL; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index fcf15638c7..f518e59acc 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3731,8 +3731,9 @@ static int set_rw_fname(char_u *fname, char_u *sfname) return FAIL; } - if (setfname(curbuf, fname, sfname, FALSE) == OK) + if (setfname(curbuf, fname, sfname, false) == OK) { curbuf->b_flags |= BF_NOTEDITED; + } /* ....and a new named one is created */ apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf); diff --git a/src/nvim/main.c b/src/nvim/main.c index e39eec4038..c8959d9ad1 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1516,7 +1516,7 @@ static void create_windows(mparm_T *parmp) /* We can't close the window, it would disturb what * happens next. Clear the file name and set the arg * index to -1 to delete it later. */ - setfname(curbuf, NULL, NULL, FALSE); + setfname(curbuf, NULL, NULL, false); curwin->w_arg_idx = -1; swap_exists_action = SEA_NONE; } else diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 2824d57f49..e5ba17a0a7 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -540,7 +540,7 @@ void ml_open_file(buf_T *buf) /// file, or reading into an existing buffer, create a swap file now. /// /// @param newfile reading file into new buffer -void check_need_swap(int newfile) +void check_need_swap(bool newfile) { int old_msg_silent = msg_silent; // might be reset by an E325 message msg_silent = 0; // If swap dialog prompts for input, user needs to see it! @@ -937,8 +937,9 @@ void ml_recover(bool checkext) */ if (directly) { expand_env(b0p->b0_fname, NameBuff, MAXPATHL); - if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL) + if (setfname(curbuf, NameBuff, NULL, true) == FAIL) { goto theend; + } } home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ed57b28029..194cc5781b 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1,9 +1,7 @@ // 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 -/* - * quickfix.c: functions for quickfix mode, using a file with error messages - */ +// quickfix.c: functions for quickfix mode, using a file with error messages #include #include @@ -53,9 +51,7 @@ struct dir_stack_T { char_u *dirname; }; -/* - * For each error the next struct is allocated and linked in a list. - */ +// For each error the next struct is allocated and linked in a list. typedef struct qfline_S qfline_T; struct qfline_S { qfline_T *qf_next; ///< pointer to next error in the list @@ -74,9 +70,7 @@ struct qfline_S { char_u qf_valid; ///< valid error message detected }; -/* - * There is a stack of error lists. - */ +// There is a stack of error lists. #define LISTCOUNT 10 #define INVALID_QFIDX (-1) @@ -120,15 +114,13 @@ typedef struct qf_list_S { /// Quickfix/Location list stack definition /// Contains a list of quickfix/location lists (qf_list_T) struct qf_info_S { - /* - * Count of references to this list. Used only for location lists. - * When a location list window reference this list, qf_refcount - * will be 2. Otherwise, qf_refcount will be 1. When qf_refcount - * reaches 0, the list is freed. - */ + // Count of references to this list. Used only for location lists. + // When a location list window reference this list, qf_refcount + // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount + // reaches 0, the list is freed. int qf_refcount; - int qf_listcount; /* current number of lists */ - int qf_curlist; /* current error list */ + int qf_listcount; // current number of lists + int qf_curlist; // current error list qf_list_T qf_lists[LISTCOUNT]; qfltype_T qfl_type; // type of list }; @@ -138,31 +130,29 @@ static unsigned last_qf_id = 0; // Last Used quickfix list id #define FMT_PATTERNS 11 // maximum number of % recognized -/* - * Structure used to hold the info of one part of 'errorformat' - */ +// Structure used to hold the info of one part of 'errorformat' typedef struct efm_S efm_T; struct efm_S { - regprog_T *prog; /* pre-formatted part of 'errorformat' */ - efm_T *next; /* pointer to next (NULL if last) */ - char_u addr[FMT_PATTERNS]; /* indices of used % patterns */ - char_u prefix; /* prefix of this format line: */ - /* 'D' enter directory */ - /* 'X' leave directory */ - /* 'A' start of multi-line message */ - /* 'E' error message */ - /* 'W' warning message */ - /* 'I' informational message */ - /* 'C' continuation line */ - /* 'Z' end of multi-line message */ - /* 'G' general, unspecific message */ - /* 'P' push file (partial) message */ - /* 'Q' pop/quit file (partial) message */ - /* 'O' overread (partial) message */ - char_u flags; /* additional flags given in prefix */ - /* '-' do not include this line */ - /* '+' include whole line in message */ - int conthere; /* %> used */ + regprog_T *prog; // pre-formatted part of 'errorformat' + efm_T *next; // pointer to next (NULL if last) + char_u addr[FMT_PATTERNS]; // indices of used % patterns + char_u prefix; // prefix of this format line: + // 'D' enter directory + // 'X' leave directory + // 'A' start of multi-line message + // 'E' error message + // 'W' warning message + // 'I' informational message + // 'C' continuation line + // 'Z' end of multi-line message + // 'G' general, unspecific message + // 'P' push file (partial) message + // 'Q' pop/quit file (partial) message + // 'O' overread (partial) message + char_u flags; // additional flags given in prefix + // '-' do not include this line + // '+' include whole line in message + int conthere; // %> used }; /// List of location lists to be deleted. @@ -221,7 +211,7 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); // Quickfix window check helper macro #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) -/* Location list window check helper macro */ +// Location list window check helper macro #define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) // Quickfix and location list stack check helper macros @@ -1156,16 +1146,12 @@ qf_init_ext( goto error2; } - /* - * got_int is reset here, because it was probably set when killing the - * ":make" command, but we still want to read the errorfile then. - */ - got_int = FALSE; + // got_int is reset here, because it was probably set when killing the + // ":make" command, but we still want to read the errorfile then. + got_int = false; - /* - * Read the lines in the error file one by one. - * Try to recognize one of the error formats in each line. - */ + // Read the lines in the error file one by one. + // Try to recognize one of the error formats in each line. while (!got_int) { status = qf_init_process_nextline(qfl, fmt_first, &state, &fields); if (status == QF_END_OF_INPUT) { // end of input @@ -1263,10 +1249,8 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title) qf_free(&qi->qf_lists[--qi->qf_listcount]); } - /* - * When the stack is full, remove to oldest entry - * Otherwise, add a new entry. - */ + // When the stack is full, remove to oldest entry + // Otherwise, add a new entry. if (qi->qf_listcount == LISTCOUNT) { qf_free(&qi->qf_lists[0]); for (i = 1; i < LISTCOUNT; i++) { @@ -1714,7 +1698,7 @@ static void ll_free_all(qf_info_T **pqi) qi = *pqi; if (qi == NULL) return; - *pqi = NULL; /* Remove reference to this list */ + *pqi = NULL; // Remove reference to this list qi->qf_refcount--; if (qi->qf_refcount < 1) { @@ -1739,7 +1723,7 @@ void qf_free_all(win_T *wp) qf_info_T *qi = &ql_info; if (wp != NULL) { - /* location list */ + // location list ll_free_all(&wp->w_llist); ll_free_all(&wp->w_llist_ref); } else { @@ -1892,14 +1876,13 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype) static qf_info_T *ll_get_or_alloc_list(win_T *wp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - if (IS_LL_WINDOW(wp)) - /* For a location list window, use the referenced location list */ + if (IS_LL_WINDOW(wp)) { + // For a location list window, use the referenced location list return wp->w_llist_ref; + } - /* - * For a non-location list window, w_llist_ref should not point to a - * location list. - */ + // For a non-location list window, w_llist_ref should not point to a + // location list. ll_free_all(&wp->w_llist_ref); if (wp->w_llist == NULL) { @@ -2133,22 +2116,21 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, { struct dir_stack_T *ds_ptr; - /* allocate new stack element and hook it in */ + // allocate new stack element and hook it in struct dir_stack_T *ds_new = xmalloc(sizeof(struct dir_stack_T)); ds_new->next = *stackptr; *stackptr = ds_new; - /* store directory on the stack */ + // store directory on the stack if (vim_isAbsName(dirbuf) || (*stackptr)->next == NULL - || (*stackptr && is_file_stack)) + || (*stackptr && is_file_stack)) { (*stackptr)->dirname = vim_strsave(dirbuf); - else { - /* Okay we don't have an absolute path. - * dirbuf must be a subdir of one of the directories on the stack. - * Let's search... - */ + } else { + // Okay we don't have an absolute path. + // dirbuf must be a subdir of one of the directories on the stack. + // Let's search... ds_new = (*stackptr)->next; (*stackptr)->dirname = NULL; while (ds_new) { @@ -2161,7 +2143,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, ds_new = ds_new->next; } - /* clean up all dirs we already left */ + // clean up all dirs we already left while ((*stackptr)->next != ds_new) { ds_ptr = (*stackptr)->next; (*stackptr)->next = (*stackptr)->next->next; @@ -2169,7 +2151,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, xfree(ds_ptr); } - /* Nothing found -> it must be on top level */ + // Nothing found -> it must be on top level if (ds_new == NULL) { xfree((*stackptr)->dirname); (*stackptr)->dirname = vim_strsave(dirbuf); @@ -2187,18 +2169,16 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr, } -/* - * pop dirbuf from the directory stack and return previous directory or NULL if - * stack is empty - */ +// pop dirbuf from the directory stack and return previous directory or NULL if +// stack is empty static char_u *qf_pop_dir(struct dir_stack_T **stackptr) { struct dir_stack_T *ds_ptr; - /* TODO: Should we check if dirbuf is the directory on top of the stack? - * What to do if it isn't? */ + // TODO(vim): Should we check if dirbuf is the directory on top of the stack? + // What to do if it isn't? - /* pop top element and free it */ + // pop top element and free it if (*stackptr != NULL) { ds_ptr = *stackptr; *stackptr = (*stackptr)->next; @@ -2206,13 +2186,11 @@ static char_u *qf_pop_dir(struct dir_stack_T **stackptr) xfree(ds_ptr); } - /* return NEW top element as current dir or NULL if stack is empty*/ + // return NEW top element as current dir or NULL if stack is empty return *stackptr ? (*stackptr)->dirname : NULL; } -/* - * clean up directory stack - */ +// clean up directory stack static void qf_clean_dir_stack(struct dir_stack_T **stackptr) { struct dir_stack_T *ds_ptr; @@ -2887,10 +2865,8 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) } } - /* - * If there is a file name, - * read the wanted file if needed, and check autowrite etc. - */ + // If there is a file name, + // read the wanted file if needed, and check autowrite etc. old_curbuf = curbuf; old_lnum = curwin->w_cursor.lnum; @@ -2938,8 +2914,8 @@ theend: qfl->qf_index = qf_index; } if (p_swb != old_swb && opened_window) { - /* Restore old 'switchbuf' value, but not when an autocommand or - * modeline has changed the value. */ + // Restore old 'switchbuf' value, but not when an autocommand or + // modeline has changed the value. if (p_swb == empty_option) { p_swb = old_swb; swb_flags = old_swb_flags; @@ -3038,10 +3014,8 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) ui_flush(); // show one line at a time } -/* - * ":clist": list all errors - * ":llist": list all locations - */ +// ":clist": list all errors +// ":llist": list all locations void qf_list(exarg_T *eap) { qf_list_T *qfl; @@ -3116,10 +3090,8 @@ void qf_list(exarg_T *eap) } } -/* - * Remove newlines and leading whitespace from an error message. - * Put the result in "buf[bufsize]". - */ +// Remove newlines and leading whitespace from an error message. +// Put the result in "buf[bufsize]". static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf, int bufsize) FUNC_ATTR_NONNULL_ALL @@ -3277,9 +3249,7 @@ static void qf_free(qf_list_T *qfl) qfl->qf_changedtick = 0L; } -/* - * qf_mark_adjust: adjust marks - */ +// qf_mark_adjust: adjust marks bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long amount_after) { @@ -3321,21 +3291,19 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, return found_one; } -/* - * Make a nice message out of the error character and the error number: - * char number message - * e or E 0 " error" - * w or W 0 " warning" - * i or I 0 " info" - * 0 0 "" - * other 0 " c" - * e or E n " error n" - * w or W n " warning n" - * i or I n " info n" - * 0 n " error n" - * other n " c n" - * 1 x "" :helpgrep - */ +// Make a nice message out of the error character and the error number: +// char number message +// e or E 0 " error" +// w or W 0 " warning" +// i or I 0 " info" +// 0 0 "" +// other 0 " c" +// e or E n " error n" +// w or W n " warning n" +// i or I n " info n" +// 0 n " error n" +// other n " c n" +// 1 x "" :helpgrep static char_u *qf_types(int c, int nr) { static char_u buf[20]; @@ -3396,12 +3364,10 @@ void qf_view_result(bool split) do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc")); } -/* - * ":cwindow": open the quickfix window if we have errors to display, - * close it if not. - * ":lwindow": open the location list window if we have locations to display, - * close it if not. - */ +// ":cwindow": open the quickfix window if we have errors to display, +// close it if not. +// ":lwindow": open the location list window if we have locations to display, +// close it if not. void ex_cwindow(exarg_T *eap) { qf_info_T *qi; @@ -3414,14 +3380,12 @@ void ex_cwindow(exarg_T *eap) qfl = qf_get_curlist(qi); - /* Look for an existing quickfix window. */ + // Look for an existing quickfix window. win = qf_find_win(qi); - /* - * If a quickfix window is open but we have no errors to display, - * close the window. If a quickfix window is not open, then open - * it if we have errors; otherwise, leave it closed. - */ + // If a quickfix window is open but we have no errors to display, + // close the window. If a quickfix window is not open, then open + // it if we have errors; otherwise, leave it closed. if (qf_stack_empty(qi) || qfl->qf_nonevalid || qf_list_empty(qf_get_curlist(qi))) { @@ -3433,10 +3397,8 @@ void ex_cwindow(exarg_T *eap) } } -/* - * ":cclose": close the window showing the list of errors. - * ":lclose": close the window showing the location list - */ +// ":cclose": close the window showing the list of errors. +// ":lclose": close the window showing the location list void ex_cclose(exarg_T *eap) { win_T *win = NULL; @@ -3446,7 +3408,7 @@ void ex_cclose(exarg_T *eap) return; } - /* Find existing quickfix window and close it. */ + // Find existing quickfix window and close it. win = qf_find_win(qi); if (win != NULL) { win_close(win, false); @@ -3642,38 +3604,32 @@ void ex_cbottom(exarg_T *eap) } } -/* - * Return the number of the current entry (line number in the quickfix - * window). - */ +// Return the number of the current entry (line number in the quickfix +// window). linenr_T qf_current_entry(win_T *wp) { qf_info_T *qi = &ql_info; - if (IS_LL_WINDOW(wp)) - /* In the location list window, use the referenced location list */ + if (IS_LL_WINDOW(wp)) { + // In the location list window, use the referenced location list qi = wp->w_llist_ref; + } return qf_get_curlist(qi)->qf_index; } -/* - * Update the cursor position in the quickfix window to the current error. - * Return TRUE if there is a quickfix window. - */ -static int -qf_win_pos_update ( +// Update the cursor position in the quickfix window to the current error. +// Return TRUE if there is a quickfix window. +static int qf_win_pos_update( qf_info_T *qi, - int old_qf_index /* previous qf_index or zero */ + int old_qf_index // previous qf_index or zero ) { win_T *win; int qf_index = qf_get_curlist(qi)->qf_index; - /* - * Put the cursor on the current error in the quickfix window, so that - * it's viewable. - */ + // Put the cursor on the current error in the quickfix window, so that + // it's viewable. win = qf_find_win(qi); if (win != NULL && qf_index <= win->w_buffer->b_ml.ml_line_count @@ -3725,10 +3681,8 @@ static win_T *qf_find_win(const qf_info_T *qi) return NULL; } -/* - * Find a quickfix buffer. - * Searches in windows opened in all the tabs. - */ +// Find a quickfix buffer. +// Searches in windows opened in all the tabs. static buf_T *qf_find_buf(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3754,16 +3708,14 @@ static void qf_update_win_titlevar(qf_info_T *qi) } } -/* - * Find the quickfix buffer. If it exists, update the contents. - */ +// Find the quickfix buffer. If it exists, update the contents. static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) { buf_T *buf; win_T *win; aco_save_T aco; - /* Check if a buffer for the quickfix list exists. Update it. */ + // Check if a buffer for the quickfix list exists. Update it. buf = qf_find_buf(qi); if (buf != NULL) { linenr_T old_line_count = buf->b_ml.ml_line_count; @@ -3936,7 +3888,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) redraw_curbuf_later(NOT_VALID); } - /* Restore KeyTyped, setting 'filetype' may reset it. */ + // Restore KeyTyped, setting 'filetype' may reset it. KeyTyped = old_KeyTyped; } @@ -3990,9 +3942,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) } } -/* - * Return TRUE when using ":vimgrep" for ":grep". - */ +// Return TRUE when using ":vimgrep" for ":grep". int grep_internal(cmdidx_T cmdidx) { return (cmdidx == CMD_grep @@ -4055,9 +4005,7 @@ static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname) return cmd; } -/* - * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" - */ +// Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" void ex_make(exarg_T *eap) { char_u *fname; @@ -4128,11 +4076,9 @@ cleanup: xfree(cmd); } -/* - * Return the name for the errorfile, in allocated memory. - * Find a new unique name when 'makeef' contains "##". - * Returns NULL for error. - */ +// Return the name for the errorfile, in allocated memory. +// Find a new unique name when 'makeef' contains "##". +// Returns NULL for error. static char_u *get_mef_name(void) { char_u *p; @@ -4154,7 +4100,7 @@ static char_u *get_mef_name(void) if (*p == NUL) return vim_strsave(p_mef); - /* Keep trying until the name doesn't exist yet. */ + // Keep trying until the name doesn't exist yet. for (;; ) { if (start == -1) { start = (int)os_get_pid(); @@ -4703,10 +4649,8 @@ static char_u * cfile_get_auname(cmdidx_T cmdidx) } -/* - * ":cfile"/":cgetfile"/":caddfile" commands. - * ":lfile"/":lgetfile"/":laddfile" commands. - */ +// ":cfile"/":cgetfile"/":caddfile" commands. +// ":lfile"/":lgetfile"/":laddfile" commands. void ex_cfile(exarg_T *eap) { win_T *wp = NULL; @@ -4947,12 +4891,10 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy, } } -/* - * ":vimgrep {pattern} file(s)" - * ":vimgrepadd {pattern} file(s)" - * ":lvimgrep {pattern} file(s)" - * ":lvimgrepadd {pattern} file(s)" - */ +// ":vimgrep {pattern} file(s)" +// ":vimgrepadd {pattern} file(s)" +// ":lvimgrep {pattern} file(s)" +// ":lvimgrepadd {pattern} file(s)" void ex_vimgrep(exarg_T *eap) { regmmatch_T regmatch; @@ -4994,7 +4936,7 @@ void ex_vimgrep(exarg_T *eap) else tomatch = MAXLNUM; - /* Get the search pattern: either white-separated or enclosed in // */ + // Get the search pattern: either white-separated or enclosed in // regmatch.regprog = NULL; char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep)); p = skip_vimgrep_pat(eap->arg, &s, &flags); @@ -5021,9 +4963,10 @@ void ex_vimgrep(exarg_T *eap) qf_new_list(qi, title); } - /* parse the list of arguments */ - if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) + // parse the list of arguments + if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) { goto theend; + } if (fcount == 0) { EMSG(_(e_nomatch)); goto theend; @@ -5032,8 +4975,8 @@ void ex_vimgrep(exarg_T *eap) dirname_start = xmalloc(MAXPATHL); dirname_now = xmalloc(MAXPATHL); - /* Remember the current directory, because a BufRead autocommand that does - * ":lcd %:p:h" changes the meaning of short path names. */ + // Remember the current directory, because a BufRead autocommand that does + // ":lcd %:p:h" changes the meaning of short path names. os_dirname(dirname_start, MAXPATHL); incr_quickfix_busy(); @@ -5046,15 +4989,15 @@ void ex_vimgrep(exarg_T *eap) for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { fname = path_try_shorten_fname(fnames[fi]); if (time(NULL) > seconds) { - /* Display the file name every second or so, show the user we are - * working on it. */ + // Display the file name every second or so, show the user we are + // working on it. seconds = time(NULL); vgr_display_fname(fname); } buf = buflist_findname_exp(fnames[fi]); if (buf == NULL || buf->b_ml.ml_mfp == NULL) { - /* Remember that a buffer with this name already exists. */ + // Remember that a buffer with this name already exists. duplicate_name = (buf != NULL); using_dummy = TRUE; redraw_for_dummy = TRUE; @@ -5087,20 +5030,20 @@ void ex_vimgrep(exarg_T *eap) if (found_match && first_match_buf == NULL) first_match_buf = buf; if (duplicate_name) { - /* Never keep a dummy buffer if there is another buffer - * with the same name. */ + // Never keep a dummy buffer if there is another buffer + // with the same name. wipe_dummy_buffer(buf, dirname_start); buf = NULL; } else if (!cmdmod.hide - || buf->b_p_bh[0] == 'u' /* "unload" */ - || buf->b_p_bh[0] == 'w' /* "wipe" */ - || buf->b_p_bh[0] == 'd') { /* "delete" */ - /* When no match was found we don't need to remember the - * buffer, wipe it out. If there was a match and it - * wasn't the first one or we won't jump there: only - * unload the buffer. - * Ignore 'hidden' here, because it may lead to having too - * many swap files. */ + || buf->b_p_bh[0] == 'u' // "unload" + || buf->b_p_bh[0] == 'w' // "wipe" + || buf->b_p_bh[0] == 'd') { // "delete" + // When no match was found we don't need to remember the + // buffer, wipe it out. If there was a match and it + // wasn't the first one or we won't jump there: only + // unload the buffer. + // Ignore 'hidden' here, because it may lead to having too + // many swap files. if (!found_match) { wipe_dummy_buffer(buf, dirname_start); buf = NULL; @@ -5124,10 +5067,10 @@ void ex_vimgrep(exarg_T *eap) target_dir = vim_strsave(dirname_now); } - /* The buffer is still loaded, the Filetype autocommands - * need to be done now, in that buffer. And the modelines - * need to be done (again). But not the window-local - * options! */ + // The buffer is still loaded, the Filetype autocommands + // need to be done now, in that buffer. And the modelines + // need to be done (again). But not the window-local + // options! aucmd_prepbuf(&aco, buf); apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, TRUE, buf); @@ -5171,8 +5114,8 @@ void ex_vimgrep(exarg_T *eap) decr_quickfix_busy(); - /* If we loaded a dummy buffer into the current window, the autocommands - * may have messed up things, need to redraw and recompute folds. */ + // If we loaded a dummy buffer into the current window, the autocommands + // may have messed up things, need to redraw and recompute folds. if (redraw_for_dummy) { foldUpdateAll(curwin); } @@ -5185,18 +5128,16 @@ theend: vim_regfree(regmatch.regprog); } -/* - * Restore current working directory to "dirname_start" if they differ, taking - * into account whether it is set locally or globally. - */ +// Restore current working directory to "dirname_start" if they differ, taking +// into account whether it is set locally or globally. static void restore_start_dir(char_u *dirname_start) { char_u *dirname_now = xmalloc(MAXPATHL); os_dirname(dirname_now, MAXPATHL); if (STRCMP(dirname_start, dirname_now) != 0) { - /* If the directory has changed, change it back by building up an - * appropriate ex command and executing it. */ + // If the directory has changed, change it back by building up an + // appropriate ex command and executing it. exarg_T ea = { .arg = dirname_start, .cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd, @@ -5206,23 +5147,21 @@ static void restore_start_dir(char_u *dirname_start) xfree(dirname_now); } -/* - * Load file "fname" into a dummy buffer and return the buffer pointer, - * placing the directory resulting from the buffer load into the - * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller - * prior to calling this function. Restores directory to "dirname_start" prior - * to returning, if autocmds or the 'autochdir' option have changed it. - * - * If creating the dummy buffer does not fail, must call unload_dummy_buffer() - * or wipe_dummy_buffer() later! - * - * Returns NULL if it fails. - */ +// Load file "fname" into a dummy buffer and return the buffer pointer, +// placing the directory resulting from the buffer load into the +// "resulting_dir" pointer. "resulting_dir" must be allocated by the caller +// prior to calling this function. Restores directory to "dirname_start" prior +// to returning, if autocmds or the 'autochdir' option have changed it. +// +// If creating the dummy buffer does not fail, must call unload_dummy_buffer() +// or wipe_dummy_buffer() later! +// +// Returns NULL if it fails. static buf_T * load_dummy_buffer ( char_u *fname, - char_u *dirname_start, /* in: old directory */ - char_u *resulting_dir /* out: new directory */ + char_u *dirname_start, // in: old directory + char_u *resulting_dir // out: new directory ) { buf_T *newbuf; @@ -5239,24 +5178,24 @@ load_dummy_buffer ( } set_bufref(&newbufref, newbuf); - /* Init the options. */ + // Init the options. buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP); - /* need to open the memfile before putting the buffer in a window */ + // need to open the memfile before putting the buffer in a window if (ml_open(newbuf) == OK) { // Make sure this buffer isn't wiped out by autocommands. newbuf->b_locked++; // set curwin/curbuf to buf and save a few things aucmd_prepbuf(&aco, newbuf); - /* Need to set the filename for autocommands. */ - (void)setfname(curbuf, fname, NULL, FALSE); + // Need to set the filename for autocommands. + (void)setfname(curbuf, fname, NULL, false); - /* Create swap file now to avoid the ATTENTION message. */ - check_need_swap(TRUE); + // Create swap file now to avoid the ATTENTION message. + check_need_swap(true); - /* Remove the "dummy" flag, otherwise autocommands may not - * work. */ + // Remove the "dummy" flag, otherwise autocommands may not + // work. curbuf->b_flags &= ~BF_DUMMY; newbuf_to_wipe.br_buf = NULL; @@ -5289,11 +5228,9 @@ load_dummy_buffer ( newbuf->b_flags |= BF_DUMMY; } - /* - * When autocommands/'autochdir' option changed directory: go back. - * Let the caller know what the resulting dir was first, in case it is - * important. - */ + // When autocommands/'autochdir' option changed directory: go back. + // Let the caller know what the resulting dir was first, in case it is + // important. os_dirname(resulting_dir, MAXPATHL); restore_start_dir(dirname_start); @@ -5307,42 +5244,38 @@ load_dummy_buffer ( return newbuf; } -/* - * Wipe out the dummy buffer that load_dummy_buffer() created. Restores - * directory to "dirname_start" prior to returning, if autocmds or the - * 'autochdir' option have changed it. - */ +// Wipe out the dummy buffer that load_dummy_buffer() created. Restores +// directory to "dirname_start" prior to returning, if autocmds or the +// 'autochdir' option have changed it. static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) { - if (curbuf != buf) { /* safety check */ + if (curbuf != buf) { // safety check cleanup_T cs; - /* Reset the error/interrupt/exception state here so that aborting() - * returns FALSE when wiping out the buffer. Otherwise it doesn't - * work when got_int is set. */ + // Reset the error/interrupt/exception state here so that aborting() + // returns FALSE when wiping out the buffer. Otherwise it doesn't + // work when got_int is set. enter_cleanup(&cs); wipe_buffer(buf, FALSE); - /* Restore the error/interrupt/exception state if not discarded by a - * new aborting error, interrupt, or uncaught exception. */ + // Restore the error/interrupt/exception state if not discarded by a + // new aborting error, interrupt, or uncaught exception. leave_cleanup(&cs); - /* When autocommands/'autochdir' option changed directory: go back. */ + // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); } } -/* - * Unload the dummy buffer that load_dummy_buffer() created. Restores - * directory to "dirname_start" prior to returning, if autocmds or the - * 'autochdir' option have changed it. - */ +// Unload the dummy buffer that load_dummy_buffer() created. Restores +// directory to "dirname_start" prior to returning, if autocmds or the +// 'autochdir' option have changed it. static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) { - if (curbuf != buf) { /* safety check */ - close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE); + if (curbuf != buf) { // safety check + close_buffer(NULL, buf, DOBUF_UNLOAD, false); - /* When autocommands/'autochdir' option changed directory: go back. */ + // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); } } @@ -6297,14 +6230,12 @@ static int cbuffer_process_args(exarg_T *eap, return OK; } -/* - * ":[range]cbuffer [bufnr]" command. - * ":[range]caddbuffer [bufnr]" command. - * ":[range]cgetbuffer [bufnr]" command. - * ":[range]lbuffer [bufnr]" command. - * ":[range]laddbuffer [bufnr]" command. - * ":[range]lgetbuffer [bufnr]" command. - */ +// ":[range]cbuffer [bufnr]" command. +// ":[range]caddbuffer [bufnr]" command. +// ":[range]cgetbuffer [bufnr]" command. +// ":[range]lbuffer [bufnr]" command. +// ":[range]laddbuffer [bufnr]" command. +// ":[range]lgetbuffer [bufnr]" command. void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; @@ -6405,8 +6336,8 @@ void ex_cexpr(exarg_T *eap) qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); - /* Evaluate the expression. When the result is a string or a list we can - * use it to fill the errorlist. */ + // Evaluate the expression. When the result is a string or a list we can + // use it to fill the errorlist. typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) -- cgit From 7a0a2eb310a5568d6ee743d65f5ae12f60111c6e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 24 Nov 2019 21:50:24 -0500 Subject: vim-patch:8.1.1732: completion in cmdwin does not work for buffer-local commands Problem: Completion in cmdwin does not work for buffer-local commands. Solution: Use the right buffer. (closes vim/vim#4711) https://github.com/vim/vim/commit/f03e328348f87e1fe8ce4aad2a6a4237b9f78ce3 --- src/nvim/ex_docmd.c | 24 +++++++++++++++++------- src/nvim/testdir/test_ins_complete.vim | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 641edf4610..f18ebffa0a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5133,9 +5133,11 @@ static void uc_list(char_u *name, size_t name_len) ucmd_T *cmd; int len; uint32_t a; - garray_T *gap; - gap = &curbuf->b_ucmds; + // In cmdwin, the alternative buffer should be used. + garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? &prevwin->w_buffer->b_ucmds + : &curbuf->b_ucmds; for (;; ) { for (i = 0; i < gap->ga_len; ++i) { cmd = USER_CMD_GA(gap, i); @@ -5984,13 +5986,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx) /* * Function given to ExpandGeneric() to obtain the list of user command names. */ -char_u *get_user_commands(expand_T *xp, int idx) +char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (idx < curbuf->b_ucmds.ga_len) - return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; - idx -= curbuf->b_ucmds.ga_len; - if (idx < ucmds.ga_len) + // In cmdwin, the alternative buffer should be used. + const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL) + ? prevwin->w_buffer + : curbuf; + + if (idx < buf->b_ucmds.ga_len) { + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + idx -= buf->b_ucmds.ga_len; + if (idx < ucmds.ga_len) { return USER_CMD(idx)->uc_name; + } return NULL; } diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 7f52481ba8..52ec281d82 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -285,3 +285,21 @@ func Test_compl_feedkeys() bwipe! set completeopt& endfunc + +func Test_compl_in_cmdwin() + set wildmenu wildchar= + com! -nargs=1 -complete=command GetInput let input = + com! -buffer TestCommand echo 'TestCommand' + + let input = '' + call feedkeys("q:iGetInput T\\\", 'tx!') + call assert_equal('TestCommand', input) + + let input = '' + call feedkeys("q::GetInput T\\:q\", 'tx!') + call assert_equal('T', input) + + delcom TestCommand + delcom GetInput + set wildmenu& wildchar& +endfunc -- cgit From 226ad89a2cf46b9a11a38b3236e23bd3e4c9b94a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 25 Nov 2019 00:34:52 -0500 Subject: vim-patch:8.1.0223: completing shell command finds sub-directories in $PATH Problem: Completing shell command finds sub-directories in $PATH. Solution: Remove EW_DIR when completing an item in $PATH. (Jason Franklin) https://github.com/vim/vim/commit/6ab9e429da18f4d784222a9f7dfafb7c0218b7eb --- src/nvim/ex_getln.c | 17 +++++++++++------ src/nvim/testdir/test_cmdline.vim | 25 ++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9e2671ca5e..7948da5e6b 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5008,19 +5008,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, hashtab_T found_ht; hash_init(&found_ht); for (s = path; ; s = e) { + e = vim_strchr(s, ENV_SEPCHAR); + if (e == NULL) { + e = s + STRLEN(s); + } + if (*s == NUL) { if (did_curdir) { break; } // Find directories in the current directory, path is empty. did_curdir = true; - } else if (*s == '.') { + flags |= EW_DIR; + } else if (STRNCMP(s, ".", e - s) == 0) { did_curdir = true; - } - - e = vim_strchr(s, ENV_SEPCHAR); - if (e == NULL) { - e = s + STRLEN(s); + flags |= EW_DIR; + } else { + // Do not match directories inside a $PATH item. + flags &= ~EW_DIR; } l = (size_t)(e - s); diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 0a3e6ae625..19bf9b18aa 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -301,7 +301,7 @@ func Test_getcompletion() call assert_equal([], l) let l = getcompletion('.', 'shellcmd') - call assert_equal(['./', '../'], l[0:1]) + call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"')) call assert_equal(-1, match(l[2:], '^\.\.\?/$')) let root = has('win32') ? 'C:\\' : '/' let l = getcompletion(root, 'shellcmd') @@ -375,6 +375,29 @@ func Test_getcompletion() call assert_fails('call getcompletion("", "burp")', 'E475:') endfunc +func Test_shellcmd_completion() + let save_path = $PATH + + call mkdir('Xpathdir/Xpathsubdir', 'p') + call writefile([''], 'Xpathdir/Xfile.exe') + call setfperm('Xpathdir/Xfile.exe', 'rwx------') + + " Set PATH to example directory without trailing slash. + let $PATH = getcwd() . '/Xpathdir' + + " Test for the ":!" case. Previously, this would include subdirs of + " dirs in the PATH, even though they won't be executed. We check that only + " subdirs of the PWD and executables from the PATH are included in the + " suggestions. + let actual = getcompletion('X', 'shellcmd') + let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"') + call insert(expected, 'Xfile.exe') + call assert_equal(expected, actual) + + call delete('Xpathdir', 'rf') + let $PATH = save_path +endfunc + func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') -- cgit From 0ee4df4d96751cb329b4f72df71ee4f6e831ff6d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 25 Nov 2019 00:43:28 -0500 Subject: vim-patch:8.1.0836: user completion test can fail on MS-Windows Problem: User completion test can fail on MS-Windows. Solution: Allow for other names befor "Administrator". https://github.com/vim/vim/commit/346d2a359a6874be6cdb683a8d190ba13aa10e94 --- src/nvim/testdir/test_cmdline.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 19bf9b18aa..635ee7984a 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -515,8 +515,9 @@ func Test_cmdline_complete_user_names() let names = system('net user') if names =~ 'Administrator' " Trying completion of :e ~A should complete to Administrator. + " There could be other names starting with "A" before Administrator. call feedkeys(':e ~A' . "\\\"\", 'tx') - call assert_match('^"e \~Administrator', @:) + call assert_match('^"e \~.*Administrator', @:) endif endif endfunc -- cgit From fd5710ae9a3bcbc0f9cbb71de9e39253350ff09c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 25 Nov 2019 01:08:02 -0800 Subject: doc + extmarks tweaks #11421 - nvim_buf_get_extmarks: rename "amount" => "limit" - rename `set_extmark_index_from_obj` --- src/nvim/api/buffer.c | 139 +++++++++++++++++++++++------------------ src/nvim/api/private/helpers.c | 26 ++++---- src/nvim/api/vim.c | 2 +- src/nvim/normal.c | 26 ++++---- src/nvim/ops.c | 3 +- src/nvim/state.c | 2 +- 6 files changed, 108 insertions(+), 90 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a5f8b0974e..448ade5e4b 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1013,10 +1013,10 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// Returns position for a given extmark id /// -/// @param buffer The buffer handle -/// @param namespace a identifier returned previously with nvim_create_namespace -/// @param id the extmark id -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param id Extmark id +/// @param[out] err Error details, if any /// @return (row, col) tuple or empty list () if extmark id was absent ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, Integer id, Error *err) @@ -1044,30 +1044,50 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } -/// List extmarks in a range (inclusive) -/// -/// range ends can be specified as (row, col) tuples, as well as extmark -/// ids in the same namespace. In addition, 0 and -1 works as shorthands -/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be -/// queried as: -/// -/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// -/// If end is a lower position than start, then the range will be traversed -/// backwards. This is mostly useful with limited amount, to be able to get the -/// first marks prior to a given position. -/// -/// @param buffer The buffer handle -/// @param ns_id An id returned previously from nvim_create_namespace -/// @param start One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param end One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param opts additional options. Supports the keys: -/// - amount: Maximum number of marks to return -/// @param[out] err Details of an error that may have occurred -/// @return [[extmark_id, row, col], ...] -Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, - Object start, Object end, Dictionary opts, - Error *err) +/// Gets extmarks in "traversal order" from a |charwise| region defined by +/// buffer positions (inclusive, 0-indexed |api-indexing|). +/// +/// Region can be given as (row,col) tuples, or valid extmark ids (whose +/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) +/// respectively, thus the following are equivalent: +/// +///
+///   nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+///   nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// 
+/// +/// If `end` is less than `start`, traversal works backwards. (Useful +/// with `limit`, to get the first marks prior to a given position.) +/// +/// Example: +/// +///
+///   local a   = vim.api
+///   local pos = a.nvim_win_get_cursor(0)
+///   local ns  = a.nvim_create_namespace('my-plugin')
+///   -- Create new extmark at line 1, column 1.
+///   local m1  = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+///   -- Create new extmark at line 3, column 1.
+///   local m2  = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+///   -- Get extmarks only from line 3.
+///   local ms  = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+///   -- Get all marks in this buffer + namespace.
+///   local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+///   print(vim.inspect(ms))
+/// 
+/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param start Start of range, given as (row, col) or valid extmark id +/// (whose position defines the bound) +/// @param end End of range, given as (row, col) or valid extmark id +/// (whose position defines the bound) +/// @param opts Optional parameters. Keys: +/// - limit: Maximum number of marks to return +/// @param[out] err Error details, if any +/// @return List of [extmark_id, row, col] tuples in "traversal order". +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, + Object end, Dictionary opts, Error *err) FUNC_API_SINCE(7) { Array rv = ARRAY_DICT_INIT; @@ -1081,17 +1101,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); return rv; } - Integer amount = -1; + Integer limit = -1; for (size_t i = 0; i < opts.size; i++) { String k = opts.items[i].key; Object *v = &opts.items[i].value; - if (strequal("amount", k.data)) { + if (strequal("limit", k.data)) { if (v->type != kObjectTypeInteger) { - api_set_error(err, kErrorTypeValidation, "amount is not an integer"); + api_set_error(err, kErrorTypeValidation, "limit is not an integer"); return rv; } - amount = v->data.integer; + limit = v->data.integer; v->data.integer = LUA_NOREF; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); @@ -1099,7 +1119,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, } } - if (amount == 0) { + if (limit == 0) { return rv; } @@ -1108,13 +1128,13 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, linenr_T l_lnum; colnr_T l_col; - if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { + if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { return rv; } linenr_T u_lnum; colnr_T u_col; - if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { + if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { return rv; } @@ -1129,9 +1149,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, } - ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, - u_lnum, u_col, (int64_t)amount, - reverse); + ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, u_lnum, + u_col, (int64_t)limit, reverse); for (size_t i = 0; i < kv_size(marks); i++) { Array mark = ARRAY_DICT_INIT; @@ -1146,26 +1165,23 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, return rv; } -/// Create or update an extmark at a position +/// Creates or updates an extmark. /// -/// If an invalid namespace is given, an error will be raised. -/// -/// To create a new extmark, pass in id=0. The new extmark id will be -/// returned. To move an existing mark, pass in its id. +/// To create a new extmark, pass id=0. The extmark id will be returned. +// To move an existing mark, pass its id. /// /// It is also allowed to create a new mark by passing in a previously unused /// id, but the caller must then keep track of existing and unused ids itself. -/// This is mainly useful over RPC, to avoid needing to wait for the return -/// value. -/// -/// @param buffer The buffer handle -/// @param ns_id a identifier returned previously with nvim_create_namespace -/// @param id The extmark's id or 0 to create a new mark. -/// @param line The row to set the extmark to. -/// @param col The column to set the extmark to. -/// @param opts Optional parameters. Currently not used. -/// @param[out] err Details of an error that may have occurred -/// @return the id of the extmark. +/// (Useful over RPC, to avoid waiting for the return value.) +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param id Extmark id, or 0 to create new +/// @param line Line number where to place the mark +/// @param col Column where to place the mark +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Error details, if any +/// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, Integer line, Integer col, Dictionary opts, Error *err) @@ -1217,13 +1233,13 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, return (Integer)id_num; } -/// Remove an extmark +/// Removes an extmark. /// -/// @param buffer The buffer handle -/// @param ns_id a identifier returned previously with nvim_create_namespace -/// @param id The extmarks's id -/// @param[out] err Details of an error that may have occurred -/// @return true on success, false if the extmark was not found. +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id Namespace id from |nvim_create_namespace()| +/// @param id Extmark id +/// @param[out] err Error details, if any +/// @return true if the extmark was found, else false Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, @@ -1309,7 +1325,8 @@ Integer nvim_buf_add_highlight(Buffer buffer, return ns_id; } -/// Clears namespaced objects, highlights and virtual text, from a line range +/// Clears namespaced objects (highlights, extmarks, virtual text) from +/// a region. /// /// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire /// buffer, specify line_start=0 and line_end=-1. diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fbfdb27827..b8d62e42a1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1512,7 +1512,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) // If throw == true then an error will be raised if nothing // was found // Returns NULL if something went wrong -Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, +Extmark *extmark_from_id_or_pos(Buffer buffer, Integer ns, Object id, Error *err, bool throw) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -1536,7 +1536,7 @@ Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, } return NULL; } - extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col); + extmark = extmark_from_pos(buf, (uint64_t)ns, row, col); } else if (id.type != kObjectTypeInteger) { if (throw) { api_set_error(err, kErrorTypeValidation, @@ -1550,7 +1550,7 @@ Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, return NULL; } else { extmark = extmark_from_id(buf, - (uint64_t)namespace, + (uint64_t)ns, (uint64_t)id.data.integer); } @@ -1572,17 +1572,17 @@ bool ns_initialized(uint64_t ns) return ns < (uint64_t)next_namespace_id; } -/// Get line and column from extmark object +/// Gets the line and column of an extmark. /// -/// Extmarks may be queried from position or name or even special names -/// in the future such as "cursor". This function sets the line and col -/// to make the extmark functions recognize what's required +/// Extmarks may be queried by position, name or even special names +/// in the future such as "cursor". /// -/// @param[out] lnum lnum to be set -/// @param[out] colnr col to be set -bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, - Object obj, linenr_T *lnum, colnr_T *colnr, - Error *err) +/// @param[out] lnum extmark line +/// @param[out] colnr extmark column +/// +/// @return true if the extmark was found, else false +bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T + *lnum, colnr_T *colnr, Error *err) { // Check if it is mark id if (obj.type == kObjectTypeInteger) { @@ -1600,7 +1600,7 @@ bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, return false; } - Extmark *extmark = extmark_from_id(buf, (uint64_t)namespace, (uint64_t)id); + Extmark *extmark = extmark_from_id(buf, (uint64_t)ns, (uint64_t)id); if (extmark) { *lnum = extmark->line->lnum; *colnr = extmark->col; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3535bc3186..9adc61b843 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1291,7 +1291,7 @@ theend: /// @param lines |readfile()|-style list of lines. |channel-lines| /// @param type Edit behavior: any |getregtype()| result, or: /// - "b" |blockwise-visual| mode (may include width, e.g. "b3") -/// - "c" |characterwise| mode +/// - "c" |charwise| mode /// - "l" |linewise| mode /// - "" guess by contents, see |setreg()| /// @param after Insert after cursor (like |p|), or before (like |P|). diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2ef2c3101f..b9dbcc6805 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1422,12 +1422,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (oap->motion_type == kMTLineWise) { oap->inclusive = false; } else if (oap->motion_type == kMTCharWise) { - // If the motion already was characterwise, toggle "inclusive" + // If the motion already was charwise, toggle "inclusive" oap->inclusive = !oap->inclusive; } oap->motion_type = kMTCharWise; } else if (oap->motion_force == Ctrl_V) { - // Change line- or characterwise motion into Visual block mode. + // Change line- or charwise motion into Visual block mode. if (!VIsual_active) { VIsual_active = true; VIsual = oap->start; @@ -1516,7 +1516,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } // In Select mode, a linewise selection is operated upon like a - // characterwise selection. + // charwise selection. // Special case: gH deletes the last line. if (VIsual_select && VIsual_mode == 'V' && cap->oap->op_type != OP_DELETE) { @@ -4588,7 +4588,7 @@ static void nv_colon(cmdarg_T *cap) nv_operator(cap); } else { if (cap->oap->op_type != OP_NOP) { - // Using ":" as a movement is characterwise exclusive. + // Using ":" as a movement is charwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; } else if (cap->count0 && !is_cmdkey) { @@ -6372,8 +6372,8 @@ static void nv_visual(cmdarg_T *cap) if (cap->cmdchar == Ctrl_Q) cap->cmdchar = Ctrl_V; - /* 'v', 'V' and CTRL-V can be used while an operator is pending to make it - * characterwise, linewise, or blockwise. */ + // 'v', 'V' and CTRL-V can be used while an operator is pending to make it + // charwise, linewise, or blockwise. if (cap->oap->op_type != OP_NOP) { motion_force = cap->oap->motion_force = cap->cmdchar; finish_op = false; // operator doesn't finish now but later @@ -7887,15 +7887,17 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) cap->oap->regname = regname; } - /* When deleted a linewise Visual area, put the register as - * lines to avoid it joined with the next line. When deletion was - * characterwise, split a line when putting lines. */ - if (VIsual_mode == 'V') + // When deleted a linewise Visual area, put the register as + // lines to avoid it joined with the next line. When deletion was + // charwise, split a line when putting lines. + if (VIsual_mode == 'V') { flags |= PUT_LINE; - else if (VIsual_mode == 'v') + } else if (VIsual_mode == 'v') { flags |= PUT_LINE_SPLIT; - if (VIsual_mode == Ctrl_V && dir == FORWARD) + } + if (VIsual_mode == Ctrl_V && dir == FORWARD) { flags |= PUT_LINE_FORWARD; + } dir = BACKWARD; if ((VIsual_mode != 'V' && curwin->w_cursor.col < curbuf->b_op_start.col) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index f2d35d5e43..294c65ca03 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5204,8 +5204,7 @@ void write_reg_contents_lst(int name, char_u **strings, /// write_reg_contents_ex - store `str` in register `name` /// -/// If `str` ends in '\n' or '\r', use linewise, otherwise use -/// characterwise. +/// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise. /// /// @warning when `name` is '/', `len` and `must_append` are ignored. This /// means that `str` MUST be NUL-terminated. diff --git a/src/nvim/state.c b/src/nvim/state.c index 81bc078a88..b195c1d96b 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -156,7 +156,7 @@ char *get_mode(void) buf[0] = 'n'; if (finish_op) { buf[1] = 'o'; - // to be able to detect force-linewise/blockwise/characterwise operations + // to be able to detect force-linewise/blockwise/charwise operations buf[2] = (char)motion_force; } else if (restart_edit == 'I' || restart_edit == 'R' || restart_edit == 'V') { -- cgit From 4a77df2e518a51ffd5a5fe311424b4b5305009a7 Mon Sep 17 00:00:00 2001 From: notomo Date: Tue, 26 Nov 2019 00:50:30 +0900 Subject: [RFC] extmark: fix E315 in nvim_buf_set_extmark (#11449) extmark: need to use buf instead of curbuf --- src/nvim/api/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 448ade5e4b..8f5718d97e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1207,7 +1207,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, api_set_error(err, kErrorTypeValidation, "line value outside range"); return 0; } else if (line < buf->b_ml.ml_line_count) { - len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false)); + len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); } if (col == -1) { -- cgit From 36d1335a667d54c26ab7cca23bc60998cb8e0fb2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 25 Nov 2019 08:56:42 -0800 Subject: UI: emit mouse_on/mouse_off on attach #11455 closes #11372 --- src/nvim/mouse.c | 29 ++++++++++++++--------------- src/nvim/option.c | 7 +++++++ 2 files changed, 21 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index d0aa0653cb..deb7ee6342 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -508,31 +508,30 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) return NULL; } -/* - * setmouse() - switch mouse on/off depending on current mode and 'mouse' - */ +/// Set UI mouse depending on current mode and 'mouse'. +/// +/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty). void setmouse(void) { - int checkfor; - ui_cursor_shape(); - /* be quick when mouse is off */ - if (*p_mouse == NUL) + // Be quick when mouse is off. + if (*p_mouse == NUL) { return; + } - if (VIsual_active) + int checkfor = MOUSE_NORMAL; // assume normal mode + if (VIsual_active) { checkfor = MOUSE_VISUAL; - else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) + } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) { checkfor = MOUSE_RETURN; - else if (State & INSERT) + } else if (State & INSERT) { checkfor = MOUSE_INSERT; - else if (State & CMDLINE) + } else if (State & CMDLINE) { checkfor = MOUSE_COMMAND; - else if (State == CONFIRM || State == EXTERNCMD) - checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */ - else - checkfor = MOUSE_NORMAL; /* assume normal mode */ + } else if (State == CONFIRM || State == EXTERNCMD) { + checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd" + } if (mouse_has(checkfor)) { ui_call_mouse_on(); diff --git a/src/nvim/option.c b/src/nvim/option.c index 20351d3908..7c830da981 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5173,6 +5173,13 @@ void ui_refresh_options(void) } ui_call_option_set(name, value); } + if (p_mouse != NULL) { + if (*p_mouse == NUL) { + ui_call_mouse_off(); + } else { + setmouse(); + } + } } /* -- cgit From 3cffd17b2b8d712c435bc19263d848ac190b23a8 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 26 Nov 2019 17:28:09 +0100 Subject: cmake: enable exporting symbols from static libs again Reverts the effect of disabling CMP0065 in ac32426 (#11131) "build: get rid of warnings with `cmake --debug-output`" We need symbols from statically linked libraries to be exported. Otherwise cpath lua modules will not find liblua/libluajit symbols as needed. --- src/nvim/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 988021ca7a..bc8e64dd41 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -465,6 +465,7 @@ install_helper(TARGETS nvim) set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) +set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE) if(ENABLE_LTO AND (POLICY CMP0069)) include(CheckIPOSupported) -- cgit From a76a669ac24ec91144153b65e0a0dc5598802653 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 26 Nov 2019 17:57:53 +0100 Subject: lua: make vim.wo and vim.bo used nested indexing for specified handle Also missing option should be an error. Options are functionality, not arbitrary variable names (as for vim.g) --- src/nvim/lua/vim.lua | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 8019511317..e13b44a8ed 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -314,7 +314,7 @@ do end vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) - vim.o = make_meta_accessor(nil_wrap(a.nvim_get_option), a.nvim_set_option) + vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv) -- TODO(ashkan) if/when these are available from an API, generate them -- instead of hardcoding. @@ -344,29 +344,31 @@ do if window_options[k] then return a.nvim_err_writeln(k.." is a window option, not a buffer option") end - return a.nvim_buf_get_option(bufnr, k) + if bufnr == nil and type(k) == "number" then + return new_buf_opt_accessor(k) + end + return a.nvim_buf_get_option(bufnr or 0, k) end local function set(k, v) if window_options[k] then return a.nvim_err_writeln(k.." is a window option, not a buffer option") end - return a.nvim_buf_set_option(bufnr, k, v) + return a.nvim_buf_set_option(bufnr or 0, k, v) end - return make_meta_accessor(nil_wrap(get), set) - end - vim.bo = new_buf_opt_accessor(0) - getmetatable(vim.bo).__call = function(_, bufnr) - return new_buf_opt_accessor(bufnr) + return make_meta_accessor(get, set) end + vim.bo = new_buf_opt_accessor(nil) local function new_win_opt_accessor(winnr) - local function get(k) return a.nvim_win_get_option(winnr, k) end - local function set(k, v) return a.nvim_win_set_option(winnr, k, v) end - return make_meta_accessor(nil_wrap(get), set) - end - vim.wo = new_win_opt_accessor(0) - getmetatable(vim.wo).__call = function(_, winnr) - return new_win_opt_accessor(winnr) + local function get(k) + if winnr == nil and type(k) == "number" then + return new_win_opt_accessor(k) + end + return a.nvim_win_get_option(winnr or nil, k) + end + local function set(k, v) return a.nvim_win_set_option(winnr or nil, k, v) end + return make_meta_accessor(get, set) end + vim.wo = new_win_opt_accessor(nil) end return module -- cgit From 49a40f425d88cbbac09fdf442c22f725bffc2249 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 24 Nov 2019 10:59:15 +0100 Subject: options: make 'fillchars' and 'listchars' global-local These options were previously global. A global-local window option behaves closer to a global option "per default" (i e with :set), but still supports local behavior via :setl Also this restores back-compat for nvim_set_option("fcs", ...) which are currently broken on 0.4.x but worked in earlier versions --- src/nvim/option.c | 68 ++++++++++++++++++++++++++++++--------- src/nvim/option_defs.h | 2 ++ src/nvim/options.lua | 6 ++-- src/nvim/testdir/test_display.vim | 1 + 4 files changed, 59 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 20351d3908..25933d497e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2205,10 +2205,10 @@ static void didset_options2(void) (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); // Parse default for 'fillchars'. - (void)set_chars_option(curwin, &curwin->w_p_fcs); + (void)set_chars_option(curwin, &curwin->w_p_fcs, true); // Parse default for 'listchars'. - (void)set_chars_option(curwin, &curwin->w_p_lcs); + (void)set_chars_option(curwin, &curwin->w_p_lcs, true); // Parse default for 'wildmode'. check_opt_wim(); @@ -2663,11 +2663,11 @@ did_set_string_option( errmsg = e_invarg; } else { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (set_chars_option(wp, &wp->w_p_lcs) != NULL) { + if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) { errmsg = (char_u *)_("E834: Conflicts with value of 'listchars'"); goto ambw_end; } - if (set_chars_option(wp, &wp->w_p_fcs) != NULL) { + if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) { errmsg = (char_u *)_("E835: Conflicts with value of 'fillchars'"); goto ambw_end; } @@ -2868,10 +2868,26 @@ ambw_end: } s = skip_to_option_part(s); } - } else if (varp == &curwin->w_p_lcs) { // 'listchars' - errmsg = set_chars_option(curwin, varp); - } else if (varp == &curwin->w_p_fcs) { // 'fillchars' - errmsg = set_chars_option(curwin, varp); + } else if (varp == &p_lcs) { // 'listchars' + errmsg = set_chars_option(curwin, varp, false); + if (!errmsg) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + set_chars_option(wp, &wp->w_p_lcs, true); + } + } + redraw_all_later(NOT_VALID); + } else if (varp == &curwin->w_p_lcs) { // local 'listchars' + errmsg = set_chars_option(curwin, varp, true); + } else if (varp == &p_fcs) { // 'fillchars' + errmsg = set_chars_option(curwin, varp, false); + if (!errmsg) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + set_chars_option(wp, &wp->w_p_fcs, true); + } + } + redraw_all_later(NOT_VALID); + } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' + errmsg = set_chars_option(curwin, varp, true); } else if (varp == &p_cedit) { // 'cedit' errmsg = check_cedit(); } else if (varp == &p_vfile) { // 'verbosefile' @@ -3501,7 +3517,7 @@ skip: /// /// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs /// @return error message, NULL if it's OK. -static char_u *set_chars_option(win_T *wp, char_u **varp) +static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) { int round, i, len, entries; char_u *p, *s; @@ -3536,12 +3552,18 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, }; - if (varp == &wp->w_p_lcs) { + if (varp == &p_lcs || varp == &wp->w_p_lcs) { tab = lcs_tab; entries = ARRAY_SIZE(lcs_tab); + if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { + varp = &p_lcs; + } } else { tab = fcs_tab; entries = ARRAY_SIZE(fcs_tab); + if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { + varp = &p_fcs; + } if (*p_ambw == 'd') { // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is // forbidden (TUI limitation?). Set old defaults. @@ -3554,7 +3576,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) } // first round: check for valid value, second round: assign values - for (round = 0; round <= 1; round++) { + for (round = 0; round <= set ? 1 : 0; round++) { if (round > 0) { // After checking that the value is valid: set defaults for (i = 0; i < entries; i++) { @@ -3562,7 +3584,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) *(tab[i].cp) = tab[i].def; } } - if (varp == &wp->w_p_lcs) { + if (varp == &p_lcs || varp == &wp->w_p_lcs) { wp->w_p_lcs_chars.tab1 = NUL; wp->w_p_lcs_chars.tab3 = NUL; } @@ -5562,6 +5584,16 @@ void unset_global_local_option(char *name, void *from) case PV_MENC: clear_string_option(&buf->b_p_menc); break; + case PV_LCS: + clear_string_option(&((win_T *)from)->w_p_lcs); + set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); + redraw_win_later((win_T *)from, NOT_VALID); + break; + case PV_FCS: + clear_string_option(&((win_T *)from)->w_p_fcs); + set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); + redraw_win_later((win_T *)from, NOT_VALID); + break; } } @@ -5598,6 +5630,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_LW: return (char_u *)&(curbuf->b_p_lw); case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); case PV_MENC: return (char_u *)&(curbuf->b_p_menc); + case PV_FCS: return (char_u *)&(curwin->w_p_fcs); + case PV_LCS: return (char_u *)&(curwin->w_p_lcs); } return NULL; // "cannot happen" } @@ -5656,6 +5690,10 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_lw) : p->var; case PV_MENC: return *curbuf->b_p_menc != NUL ? (char_u *)&(curbuf->b_p_menc) : p->var; + case PV_FCS: return *curwin->w_p_fcs != NUL + ? (char_u *)&(curwin->w_p_fcs) : p->var; + case PV_LCS: return *curwin->w_p_lcs != NUL + ? (char_u *)&(curwin->w_p_lcs) : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); case PV_LIST: return (char_u *)&(curwin->w_p_list); @@ -5753,8 +5791,6 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); - case PV_FCS: return (char_u *)&(curwin->w_p_fcs); - case PV_LCS: return (char_u *)&(curwin->w_p_lcs); case PV_WINBL: return (char_u *)&(curwin->w_p_winbl); default: IEMSG(_("E356: get_varp ERROR")); } @@ -5896,8 +5932,8 @@ void didset_window_options(win_T *wp) { check_colorcolumn(wp); briopt_check(wp); - set_chars_option(wp, &wp->w_p_fcs); - set_chars_option(wp, &wp->w_p_lcs); + set_chars_option(wp, &wp->w_p_fcs, true); + set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl wp->w_grid.blending = wp->w_p_winbl > 0; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index e5a3c0bd95..ce0ea19a2f 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -484,6 +484,7 @@ EXTERN long p_linespace; // 'linespace' EXTERN char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' +EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' @@ -652,6 +653,7 @@ EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' +EXTERN char_u *p_fcs; ///< 'fillchar' EXTERN char_u *p_shada; ///< 'shada' EXTERN char *p_shadafile; ///< 'shadafile' EXTERN char_u *p_vdir; ///< 'viewdir' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index d20174466d..0aa367e059 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -804,11 +804,12 @@ return { }, { full_name='fillchars', abbreviation='fcs', - type='string', list='onecomma', scope={'window'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vi_def=true, alloced=true, redraw={'current_window'}, + varname='p_fcs', defaults={if_true={vi=''}} }, { @@ -1420,11 +1421,12 @@ return { }, { full_name='listchars', abbreviation='lcs', - type='string', list='onecomma', scope={'window'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, vim=true, alloced=true, redraw={'current_window'}, + varname='p_lcs', defaults={if_true={vi="eol:$", vim="tab:> ,trail:-,nbsp:+"}} }, { diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 66c13ded82..1c2f5a05ff 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -71,6 +71,7 @@ func! Test_display_foldtext_mbyte() endfunc func Test_display_listchars_precedes() + set fillchars+=vert:\| call NewWindow(10, 10) " Need a physical line that wraps over the complete " window size -- cgit From d697690e24ae5ea9dc5189de504b03823f7fb0f4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 19:49:29 -0500 Subject: vim-patch:8.1.2345: .cjs files are not recognized as Javascript Problem: .cjs files are not recognized as Javascript. Solution: Add the *.cjs pattern. (closes vim/vim#5268) https://github.com/vim/vim/commit/c1faf3dc3879e8a5e486f31445b5a5753dcbc6a3 --- src/nvim/testdir/test_filetype.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 4053746c82..e085f58e56 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -223,7 +223,7 @@ let s:filename_checks = { \ 'jam': ['file.jpl', 'file.jpr'], \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], - \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs'], + \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], -- cgit From f196ab87a1b5a9da1326d4de883ab7e97a0bb1f0 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 20:05:52 -0500 Subject: vim-patch:8.1.2348: :const cannot be followed by "| endif" Problem: :const cannot be followed by "| endif". Solution: Check following command for :const. (closes vim/vim#5269) Also fix completion after :const. https://github.com/vim/vim/commit/8f76e6b12b958f2779444a92234bbaf3f49eeb99 --- src/nvim/eval.c | 2 +- src/nvim/ex_docmd.c | 2 ++ src/nvim/testdir/test_cmdline.vim | 1 + src/nvim/testdir/test_const.vim | 6 ++++++ src/nvim/testdir/test_let.vim | 4 ++++ 5 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9fe92a92cc..5229a81288 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2872,7 +2872,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx) int c; char_u *p; - if (cmdidx == CMD_let) { + if (cmdidx == CMD_let || cmdidx == CMD_const) { xp->xp_context = EXPAND_USER_VARS; if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) { /* ":let var1 var2 ...": find last space. */ diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index f18ebffa0a..b24bf119b3 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2126,6 +2126,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_browse: case CMD_call: case CMD_confirm: + case CMD_const: case CMD_delfunction: case CMD_djump: case CMD_dlist: @@ -3437,6 +3438,7 @@ const char * set_one_cmd_context( case CMD_syntax: set_context_in_syntax_cmd(xp, arg); break; + case CMD_const: case CMD_let: case CMD_if: case CMD_elseif: diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 635ee7984a..23784b0308 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -159,6 +159,7 @@ func Test_expr_completion() endif for cmd in [ \ 'let a = ', + \ 'const a = ', \ 'if', \ 'elseif', \ 'while', diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index 06062c5e58..1d7b0a56b5 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -176,6 +176,12 @@ func Test_cannot_modify_existing_variable() call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:') endfunc +func Test_const_with_condition() + const x = 0 + if 0 | const x = 1 | endif + call assert_equal(0, x) +endfunc + func Test_const_with_index_access() let l = [1, 2, 3] call assert_fails('const l[0] = 4', 'E996:') diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 3c0fefbd25..0b9331ee38 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -24,6 +24,10 @@ func Test_let() let out = execute('let a {0 == 1 ? "a" : "b"}') let s = "\na #1\nb #2" call assert_equal(s, out) + + let x = 0 + if 0 | let x = 1 | endif + call assert_equal(0, x) endfunc func s:set_arg1(a) abort -- cgit From 90f2b1360469821dfd0e7e75d0b43859aa27dac1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 20:40:12 -0500 Subject: vim-patch:8.1.2349: :lockvar and :unlockvar cannot be followed by "| endif" Problem: :lockvar and :unlockvar cannot be followed by "| endif". Solution: Check for following commands. (closes vim/vim#5269) https://github.com/vim/vim/commit/cc4423ae13d78367a3d0b5756783523d3b3a1d31 --- src/nvim/ex_docmd.c | 2 ++ src/nvim/testdir/test_const.vim | 14 ++++++++++++++ 2 files changed, 16 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index b24bf119b3..743286c64a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2151,6 +2151,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_leftabove: case CMD_let: case CMD_lockmarks: + case CMD_lockvar: case CMD_lua: case CMD_match: case CMD_mzscheme: @@ -2179,6 +2180,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, case CMD_tilde: case CMD_topleft: case CMD_unlet: + case CMD_unlockvar: case CMD_verbose: case CMD_vertical: case CMD_wincmd: diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index 1d7b0a56b5..eaf200e9bb 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -182,6 +182,20 @@ func Test_const_with_condition() call assert_equal(0, x) endfunc +func Test_lockvar() + let x = 'hello' + lockvar x + call assert_fails('let x = "there"', 'E741') + if 0 | unlockvar x | endif + call assert_fails('let x = "there"', 'E741') + unlockvar x + let x = 'there' + + if 0 | lockvar x | endif + let x = 'again' +endfunc + + func Test_const_with_index_access() let l = [1, 2, 3] call assert_fails('const l[0] = 4', 'E996:') -- cgit From 278a0b8df3a70e46228b4f41786c38bbd2d91756 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 21:54:48 -0500 Subject: vim-patch:8.1.1252: not all mapping completion is tested Problem: Not all mapping completion is tested. Solution: Add a few more mapping completion tests. https://github.com/vim/vim/commit/1776a28e9c7fd0236927f14e9df807e524b30721 --- src/nvim/testdir/test_cmdline.vim | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 23784b0308..4a9f1ed19a 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -78,19 +78,31 @@ func Test_map_completion() call feedkeys(":map \\"\", 'xt') call assert_equal('"map ', getreg(':')) + map x middle + map ,f commaf map ,g commaf + map left + map x shiftleft call feedkeys(":map ,\\\"\", 'xt') call assert_equal('"map ,f', getreg(':')) call feedkeys(":map ,\\\\"\", 'xt') call assert_equal('"map ,g', getreg(':')) + call feedkeys(":map \\"\", 'xt') + call assert_equal('"map ', getreg(':')) + call feedkeys(":map \\\"\", 'xt') + call assert_equal('"map x', getreg(':')) unmap ,f unmap ,g + unmap + unmap x set cpo-=< cpo-=B cpo-=k map left call feedkeys(":map \\"\", 'xt') call assert_equal('"map ', getreg(':')) + call feedkeys(":map \\"\", 'xt') + call assert_equal('"map " set cpo+=< @@ -113,6 +125,9 @@ func Test_map_completion() call assert_equal('"map ', getreg(':')) unmap " set cpo-=k + + unmap x + set cpo&vim endfunc func Test_match_completion() -- cgit From 5ee6c3bd715b0e9c933bad77e7f8bea1ac19c1a1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 22:02:26 -0500 Subject: vim-patch:8.1.1253: mapping completion test fails Problem: Mapping completion test fails. Solution: Fix expected output. https://github.com/vim/vim/commit/92b9e60cb5775ebe8949b4e112feb9f8565441e0 --- src/nvim/testdir/test_cmdline.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 4a9f1ed19a..e76817630b 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -91,7 +91,7 @@ func Test_map_completion() call feedkeys(":map \\"\", 'xt') call assert_equal('"map ', getreg(':')) call feedkeys(":map \\\"\", 'xt') - call assert_equal('"map x', getreg(':')) + call assert_equal("\"map \", getreg(':')) unmap ,f unmap ,g unmap @@ -102,7 +102,7 @@ func Test_map_completion() call feedkeys(":map \\"\", 'xt') call assert_equal('"map ', getreg(':')) call feedkeys(":map \\"\", 'xt') - call assert_equal('"map ", getreg(':')) unmap " set cpo+=< -- cgit From 006beb73c7734247b4d5d1f25afc18dac28af702 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 22:06:31 -0500 Subject: vim-patch:8.1.1254: mapping completion contains dead code Problem: Mapping completion contains dead code. Solution: Remove the code. https://github.com/vim/vim/commit/61df0c7996d9acc94267735abc214cb176e63ede --- src/nvim/testdir/test_cmdline.vim | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index e76817630b..fe3eaba817 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -107,9 +107,14 @@ func Test_map_completion() " set cpo+=< map left + exe "set t_k6=\[17~" + call feedkeys(":map \[17~x f6x\", 'xt') call feedkeys(":map \\"\", 'xt') call assert_equal('"map ', getreg(':')) + call feedkeys(":map \[17~\\\"\", 'xt') + " call assert_equal("\"map x", getreg(':')) unmap + call feedkeys(":unmap \[17~x\", 'xt') set cpo-=< set cpo+=B -- cgit From 585e3ddfc785abfd0022af4f9c84f13e24c41782 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 26 Nov 2019 22:08:31 -0500 Subject: vim-patch:8.1.1268: map completion test fails in GUI Problem: Map completion test fails in GUI. Solution: Skip the test that fails. https://github.com/vim/vim/commit/510671a055c2d7a329c88bf133ac302139fd3221 --- src/nvim/testdir/test_cmdline.vim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index fe3eaba817..56157bebec 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -111,8 +111,10 @@ func Test_map_completion() call feedkeys(":map \[17~x f6x\", 'xt') call feedkeys(":map \\"\", 'xt') call assert_equal('"map ', getreg(':')) - call feedkeys(":map \[17~\\\"\", 'xt') - " call assert_equal("\"map x", getreg(':')) + if !has('gui_running') + call feedkeys(":map \[17~\\\"\", 'xt') + " call assert_equal("\"map x", getreg(':')) + endif unmap call feedkeys(":unmap \[17~x\", 'xt') set cpo-=< -- cgit From 001e69cd4602e84219fd7cfd8ade62f0cb24097c Mon Sep 17 00:00:00 2001 From: Brian Wignall Date: Tue, 26 Nov 2019 07:15:14 -0500 Subject: doc: fix typos close #11459 --- src/nvim/lua/converter.c | 2 +- src/nvim/mark_extended.c | 4 ++-- src/nvim/os_unix.c | 2 +- src/nvim/screen.c | 2 +- src/nvim/syntax.c | 2 +- src/nvim/testdir/test_search.vim | 4 ++-- src/nvim/ui_compositor.c | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 44fe60e9c8..09d1a68898 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -1225,7 +1225,7 @@ GENERATE_INDEX_FUNCTION(Tabpage) #undef GENERATE_INDEX_FUNCTION -/// Record some auxilary values in vim module +/// Record some auxiliary values in vim module /// /// Assumes that module table is on top of the stack. /// diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index 91c2f919ce..17776d438a 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -4,7 +4,7 @@ // Implements extended marks for plugins. Each mark exists in a btree of // lines containing btrees of columns. // -// The btree provides efficent range lookups. +// The btree provides efficient range lookups. // A map of pointers to the marks is used for fast lookup by mark id. // // Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or @@ -300,7 +300,7 @@ Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col) return NULL; } -// Returns an avaliable id in a namespace +// Returns an available id in a namespace uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns) { if (!buf->b_extmark_ns) { diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index ded575529f..0f44df2188 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -412,7 +412,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, fseek(fd, 0L, SEEK_SET); buffer = xmalloc(len + 1); // fread() doesn't terminate buffer with NUL; - // appropiate termination (not always NUL) is done below. + // appropriate termination (not always NUL) is done below. size_t readlen = fread((char *)buffer, 1, len, fd); fclose(fd); os_remove((char *)tempname); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 7b9601a5a6..e9be7eed38 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -124,7 +124,7 @@ // temporary buffer for rendering a single screenline, so it can be -// comparared with previous contents to calulate smallest delta. +// comparared with previous contents to calculate smallest delta. static size_t linebuf_size = 0; static schar_T *linebuf_char = NULL; static sattr_T *linebuf_attr = NULL; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index bdbc09a87a..61ee225eba 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7316,7 +7316,7 @@ static void set_hl_attr(int idx) sgp->sg_attr = hl_get_syn_attr(idx+1, at_en); - // a cursor style uses this syn_id, make sure its atribute is updated. + // a cursor style uses this syn_id, make sure its attribute is updated. if (cursor_mode_uses_syn_id(idx+1)) { ui_mode_info_set(); } diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 5d4c2a015f..68eb311e3c 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -57,7 +57,7 @@ func Test_search_cmdline() call feedkeys("/the".repeat("\", 6)."\", 'tx') call assert_equal(' 8 them', getline('.')) :1 - " eigth match + " eighth match call feedkeys("/the".repeat("\", 7)."\", 'tx') call assert_equal(' 9 these', getline('.')) :1 @@ -99,7 +99,7 @@ func Test_search_cmdline() call feedkeys("/the".repeat("\", 6)."\", 'tx') call assert_equal(' 8 them', getline('.')) :1 - " eigth match + " eighth match call feedkeys("/the".repeat("\", 7)."\", 'tx') call assert_equal(' 9 these', getline('.')) :1 diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 7d3ecfa0b8..e582d8f859 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -626,7 +626,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, if (covered || curgrid->blending) { // TODO(bfredl): // 1. check if rectangles actually overlap - // 2. calulate subareas that can scroll. + // 2. calculate subareas that can scroll. compose_debug(top, bot, left, right, dbghl_recompose, true); for (int r = (int)(top + MAX(-rows, 0)); r < bot - MAX(rows, 0); r++) { // TODO(bfredl): workaround for win_update() performing two scrolls in a -- cgit From 1bb7ea189e0b1bf402f4733d42dbf3d74ade932e Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 29 Nov 2019 13:09:03 +0900 Subject: win_line: Fix crash with 'rightleft' in :terminal #11460 fixes #11438 Backtrace: 0 schar_from_ascii ( p=0x801cc9e112c3 , c=32 ' ') at ../src/nvim/screen.c:5263 1 0x00007f31460eccc5 in win_line (wp=wp@entry=0x7fffc9df6230, lnum=lnum@entry=11, startrow=startrow@entry=10, endrow=41, nochange=false, number_only=number_only@entry=false) at ../src/nvim/screen.c:4025 2 0x00007f31460eed8e in win_update (wp=wp@entry=0x7fffc9df6230) at ../src/nvim/screen.c:1403 3 0x00007f31460f011f in update_screen (type=) at ../src/nvim/screen.c:502 4 0x00007f3146138ef4 in normal_redraw (s=s@entry=0x7fffd0a5f700) at ../src/nvim/normal.c:1247 5 0x00007f314613b159 in normal_check (state=0x7fffd0a5f700) at ../src/nvim/normal.c:1324 6 0x00007f31460accfe in state_enter (s=0x7fffd0a5f700) at ../src/nvim/state.c:28 7 0x00007f3146143099 in normal_enter (cmdwin=, noexmode=) at ../src/nvim/normal.c:463 8 0x00007f314618b541 in main (argc=, argv=) at ../src/nvim/main.c:580 --- src/nvim/screen.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index e9be7eed38..1d29ae064e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -4021,10 +4021,13 @@ win_line ( if (wp->w_buffer->terminal) { // terminal buffers may need to highlight beyond the end of the // logical line - while (col < grid->Columns) { + int n = wp->w_p_rl ? -1 : 1; + while (col >= 0 && col < grid->Columns) { schar_from_ascii(linebuf_char[off], ' '); - linebuf_attr[off++] = term_attrs[vcol++]; - col++; + linebuf_attr[off] = term_attrs[vcol]; + off += n; + vcol += n; + col += n; } } grid_put_linebuf(grid, row, 0, col, grid->Columns, wp->w_p_rl, wp, -- cgit From f33371c03f526ecbe2d6a1bec744fa37c1b2640c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Nov 2019 18:51:25 +0100 Subject: vim-patch:8.1.2017: cannot execute commands after closing cmdline window #11479 Problem: Cannot execute commands after closing the cmdline window. Solution: Also trigger BufEnter and WinEnter. (closes vim/vim#4762) https://github.com/vim/vim/commit/96e38a86a710fb6daec4550ac1667f019dc3a40e Fixes https://github.com/neovim/neovim/issues/11279. --- src/nvim/ex_getln.c | 41 +++++++++++++++------------------------ src/nvim/testdir/test_cmdline.vim | 35 +++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 7948da5e6b..73353f84cf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -6078,12 +6078,9 @@ static int open_cmdwin(void) set_bufref(&old_curbuf, curbuf); - /* Save current window sizes. */ + // Save current window sizes. win_size_save(&winsizes); - /* Don't execute autocommands while creating the window. */ - block_autocmds(); - // When using completion in Insert mode with = one can open the // command line window, but we don't want the popup menu then. pum_undisplay(true); @@ -6092,10 +6089,9 @@ static int open_cmdwin(void) cmdmod.tab = 0; cmdmod.noswapfile = 1; - /* Create a window for the command-line buffer. */ + // Create a window for the command-line buffer. if (win_split((int)p_cwh, WSP_BOT) == FAIL) { beep_flush(); - unblock_autocmds(); return K_IGNORE; } cmdwin_type = get_cmdline_type(); @@ -6110,13 +6106,11 @@ static int open_cmdwin(void) curbuf->b_p_ma = true; curwin->w_p_fen = false; - // Do execute autocommands for setting the filetype (load syntax). - unblock_autocmds(); - // But don't allow switching to another buffer. + // Don't allow switching to another buffer. curbuf_lock++; - /* Showing the prompt may have set need_wait_return, reset it. */ - need_wait_return = FALSE; + // Showing the prompt may have set need_wait_return, reset it. + need_wait_return = false; const int histtype = hist_char2type(cmdwin_type); if (histtype == HIST_CMD || histtype == HIST_DEBUG) { @@ -6128,11 +6122,11 @@ static int open_cmdwin(void) } curbuf_lock--; - /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin - * sets 'textwidth' to 78). */ + // Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin + // sets 'textwidth' to 78). curbuf->b_p_tw = 0; - /* Fill the buffer with the history. */ + // Fill the buffer with the history. init_history(); if (hislen > 0 && histtype != HIST_INVALID) { i = hisidx[histtype]; @@ -6173,9 +6167,10 @@ static int open_cmdwin(void) // Trigger CmdwinEnter autocommands. typestr[0] = (char_u)cmdwin_type; typestr[1] = NUL; - apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf); - if (restart_edit != 0) /* autocmd with ":startinsert" */ + apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf); + if (restart_edit != 0) { // autocmd with ":startinsert" stuffcharReadbuff(K_NOP); + } i = RedrawingDisabled; RedrawingDisabled = 0; @@ -6192,10 +6187,10 @@ static int open_cmdwin(void) const bool save_KeyTyped = KeyTyped; - /* Trigger CmdwinLeave autocommands. */ - apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf); + // Trigger CmdwinLeave autocommands. + apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf); - /* Restore KeyTyped in case it is modified by autocommands */ + // Restore KeyTyped in case it is modified by autocommands KeyTyped = save_KeyTyped; // Restore the command line info. @@ -6254,9 +6249,7 @@ static int open_cmdwin(void) } } - /* Don't execute autocommands while deleting the window. */ - block_autocmds(); - // Avoid command-line window first character being concealed + // Avoid command-line window first character being concealed. curwin->w_p_cole = 0; wp = curwin; set_bufref(&bufref, curbuf); @@ -6269,10 +6262,8 @@ static int open_cmdwin(void) close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); } - /* Restore window sizes. */ + // Restore window sizes. win_size_restore(&winsizes); - - unblock_autocmds(); } ga_clear(&winsizes); diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 56157bebec..262ea11eb6 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -619,6 +619,8 @@ func Check_cmdline(cmdtype) return '' endfunc +set cpo& + func Test_getcmdtype() call feedkeys(":MyCmd a\=Check_cmdline(':')\\", "xt") @@ -659,6 +661,37 @@ func Test_getcmdwintype() call assert_equal('', getcmdwintype()) endfunc +func Test_getcmdwin_autocmd() + let s:seq = [] + augroup CmdWin + au WinEnter * call add(s:seq, 'WinEnter ' .. win_getid()) + au WinLeave * call add(s:seq, 'WinLeave ' .. win_getid()) + au BufEnter * call add(s:seq, 'BufEnter ' .. bufnr()) + au BufLeave * call add(s:seq, 'BufLeave ' .. bufnr()) + au CmdWinEnter * call add(s:seq, 'CmdWinEnter ' .. win_getid()) + au CmdWinLeave * call add(s:seq, 'CmdWinLeave ' .. win_getid()) + + let org_winid = win_getid() + let org_bufnr = bufnr() + call feedkeys("q::let a = getcmdwintype()\:let s:cmd_winid = win_getid()\:let s:cmd_bufnr = bufnr()\:q\", 'x!') + call assert_equal(':', a) + call assert_equal([ + \ 'WinLeave ' .. org_winid, + \ 'WinEnter ' .. s:cmd_winid, + \ 'BufLeave ' .. org_bufnr, + \ 'BufEnter ' .. s:cmd_bufnr, + \ 'CmdWinEnter ' .. s:cmd_winid, + \ 'CmdWinLeave ' .. s:cmd_winid, + \ 'BufLeave ' .. s:cmd_bufnr, + \ 'WinLeave ' .. s:cmd_winid, + \ 'WinEnter ' .. org_winid, + \ 'BufEnter ' .. org_bufnr, + \ ], s:seq) + + au! + augroup END +endfunc + func Test_verbosefile() set verbosefile=Xlog echomsg 'foo' @@ -717,5 +750,3 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc - -set cpo& -- cgit From 0a9ecf460a33ad1ee2b756d2ef0417e99bdfb988 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 29 Nov 2019 18:57:27 +0100 Subject: vim-patch:8.1.2315: switchbuf=uselast #11480 Problem: Not always using the right window when jumping to an error. Solution: Add the "uselast" flag in 'switchbuf'. (closes vim/vim#1652) https://github.com/vim/vim/commit/539aa6b25eaea91dfd1a175cd053c0f259fa2e58 --- src/nvim/option_defs.h | 3 ++- src/nvim/quickfix.c | 7 +++++-- src/nvim/testdir/test_quickfix.vim | 8 ++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index ce0ea19a2f..c6e3f71016 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -612,13 +612,14 @@ EXTERN char_u *p_swb; // 'switchbuf' EXTERN unsigned swb_flags; #ifdef IN_OPTION_C static char *(p_swb_values[]) = - { "useopen", "usetab", "split", "newtab", "vsplit", NULL }; + { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL }; #endif #define SWB_USEOPEN 0x001 #define SWB_USETAB 0x002 #define SWB_SPLIT 0x004 #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 +#define SWB_USELAST 0x020 EXTERN int p_tbs; ///< 'tagbsearch' EXTERN char_u *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 194cc5781b..5d30ca624f 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2595,8 +2595,11 @@ static void qf_goto_win_with_qfl_file(int qf_fnum) if (IS_QF_WINDOW(win)) { // Didn't find it, go to the window before the quickfix - // window. - if (altwin != NULL) { + // window, unless 'switchbuf' contains 'uselast': in this case we + // try to jump to the previously used window first. + if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) { + win = prevwin; + } else if (altwin != NULL) { win = altwin; } else if (curwin->w_prev != NULL) { win = curwin->w_prev; diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 15cbf52cb5..60ffb11793 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1642,6 +1642,14 @@ func Test_switchbuf() call assert_equal(3, tabpagenr('$')) tabfirst | enew | tabonly | only + set switchbuf=uselast + split + let last_winid = win_getid() + copen + exe "normal 1G\" + call assert_equal(last_winid, win_getid()) + enew | only + set switchbuf= edit Xqftestfile1 let file1_winid = win_getid() -- cgit From f6e7857c54a015cdfac9ce65ec0b65d65d590aeb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 29 Nov 2019 23:48:14 -0800 Subject: floatwin: show error if window is closed immediately #11476 Autocmds may close window while it is being entered, then win_set_minimal_style(wp) operates on an invalid pointer. We could silently ignore this instead, but it is unlikely to be intentional, so it is more useful to show an error. fix #11383 --- src/nvim/api/vim.c | 4 ++++ src/nvim/window.c | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9adc61b843..e4a9bd64ff 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1096,6 +1096,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, if (enter) { win_enter(wp, false); } + if (!win_valid(wp)) { + api_set_error(err, kErrorTypeException, "Window was closed immediately"); + return 0; + } if (buffer > 0) { nvim_win_set_buf(wp->handle, buffer, err); } diff --git a/src/nvim/window.c b/src/nvim/window.c index dee36df433..76fc36607c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4372,9 +4372,10 @@ static void win_goto_hor(bool left, long count) } } -/* - * Make window "wp" the current window. - */ +/// Make window `wp` the current window. +/// +/// @warning Autocmds may close the window immediately, so caller must check +/// win_valid(wp). void win_enter(win_T *wp, bool undo_sync) { win_enter_ext(wp, undo_sync, false, false, true, true); -- cgit From 7646e73a27bd3cdd343880ea5127b7bed330340b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 29 Nov 2019 05:45:54 -0500 Subject: vim-patch:8.1.2355: test with "man" fails on FreeBSD Problem: Test with "man" fails on FreeBSD. Solution: Use "-P" instead of "--pager". https://github.com/vim/vim/commit/c7d2a57b3a076f6ecb16f93c0b09280c4b3b4175 Cherry-picked "has('bsd')" from vim-patch:8.1.0846. Cherry-picked test_normal.vim fix from vim-patch:8.1.2358 --- src/nvim/eval.c | 3 +++ src/nvim/testdir/test_normal.vim | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5229a81288..1ab25b33b2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -11503,6 +11503,9 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static const char *const has_list[] = { +#if defined(BSD) && !defined(__APPLE__) + "bsd", +#endif #ifdef UNIX "unix", #endif diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index eab638d19a..ad6d325510 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1367,8 +1367,9 @@ func Test_normal23_K() return endif - if has('mac') - " In MacOS, the option for specifying a pager is different + let not_gnu_man = has('mac') || has('bsd') + if not_gnu_man + " In MacOS and BSD, the option for specifying a pager is different set keywordprg=man\ -P\ cat else set keywordprg=man\ --pager=cat @@ -1376,7 +1377,7 @@ func Test_normal23_K() " Test for using man 2 let a = execute('unsilent norm! K') - if has('mac') + if not_gnu_man call assert_match("man -P cat 'man'", a) else call assert_match("man --pager=cat 'man'", a) -- cgit From 5b60023a8c19fd77c426b0070057b95cae89cd42 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 30 Nov 2019 15:29:01 -0500 Subject: vim-patch:8.1.2363: ml_get error when accessing Visual area in 'statusline' Problem: ml_get error when accessing Visual area in 'statusline'. Solution: Disable Visual mode when using another window. (closes vim/vim#5278) https://github.com/vim/vim/commit/dee50a518007b3a59f54b8ad018b6a83993593e7 --- src/nvim/buffer.c | 6 ++++++ src/nvim/testdir/test_statusline.vim | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e3b8e9cc6d..33ffff39f6 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3801,14 +3801,20 @@ int build_stl_str_hl( buf_T *const save_curbuf = curbuf; win_T *const save_curwin = curwin; + const int save_VIsual_active = VIsual_active; curwin = wp; curbuf = wp->w_buffer; + // Visual mode is only valid in the current window. + if (curwin != save_curwin) { + VIsual_active = false; + } // Note: The result stored in `t` is unused. str = eval_to_string_safe(out_p, &t, use_sandbox); curwin = save_curwin; curbuf = save_curbuf; + VIsual_active = save_VIsual_active; // Remove the variable we just stored do_unlet(S_LEN("g:actual_curbuf"), true); diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index b86340a23a..48ec777ffd 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -347,3 +347,25 @@ func Test_statusline() set laststatus& set splitbelow& endfunc + +func Test_statusline_visual() + func CallWordcount() + call wordcount() + endfunc + new x1 + setl statusline=count=%{CallWordcount()} + " buffer must not be empty + call setline(1, 'hello') + + " window with more lines than x1 + new x2 + call setline(1, range(10)) + $ + " Visual mode in line below liast line in x1 should not give ml_get error + call feedkeys("\", "xt") + redraw + + delfunc CallWordcount + bwipe! x1 + bwipe! x2 +endfunc -- cgit From 590082a58fa623adf6cb5a2f7ac756d366e3119c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 1 Dec 2019 00:41:40 -0500 Subject: vim-patch:8.1.2367: registers are not sufficiently tested (#11489) Problem: Registers are not sufficiently tested. Solution: Add a few more test cases. (Yegappan Lakshmanan, closes vim/vim#5288) https://github.com/vim/vim/commit/71136db1bfbc67c2e55f8070cdf0a241c643e45b --- src/nvim/testdir/test_registers.vim | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 298268a994..d4f58af10a 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -1,3 +1,16 @@ +" +" Tests for register operations +" + +" This test must be executed first to check for empty and unset registers. +func Test_aaa_empty_reg_test() + call assert_fails('normal @@', 'E748:') + call assert_fails('normal @%', 'E354:') + call assert_fails('normal @#', 'E354:') + call assert_fails('normal @!', 'E354:') + call assert_fails('normal @:', 'E30:') + call assert_fails('normal @.', 'E29:') +endfunc func Test_yank_shows_register() enew @@ -82,3 +95,76 @@ func Test_recording_esc_sequence() let &t_F2 = save_F2 endif endfunc + +" Test for executing the last used register (@) +func Test_last_used_exec_reg() + " Test for the @: command + let a = '' + call feedkeys(":let a ..= 'Vim'\", 'xt') + normal @: + call assert_equal('VimVim', a) + + " Test for the @= command + let x = '' + let a = ":let x ..= 'Vim'\" + exe "normal @=a\" + normal @@ + call assert_equal('VimVim', x) + + " Test for the @. command + let a = '' + call feedkeys("i:let a ..= 'Edit'\", 'xt') + normal @. + normal @@ + call assert_equal('EditEdit', a) + + enew! +endfunc + +func Test_get_register() + enew + edit Xfile1 + edit Xfile2 + call assert_equal('Xfile2', getreg('%')) + call assert_equal('Xfile1', getreg('#')) + + call feedkeys("iTwo\", 'xt') + call assert_equal('Two', getreg('.')) + call assert_equal('', getreg('_')) + call assert_beeps('normal ":yy') + call assert_beeps('normal "%yy') + call assert_beeps('normal ".yy') + + call assert_equal('', getreg("\")) + call assert_equal('', getreg("\")) + call assert_equal('', getreg("\")) + + call assert_equal('', getregtype('!')) + + enew! +endfunc + +func Test_set_register() + call assert_fails("call setreg('#', 200)", 'E86:') + + edit Xfile_alt_1 + let b1 = bufnr('') + edit Xfile_alt_2 + let b2 = bufnr('') + edit Xfile_alt_3 + let b3 = bufnr('') + call setreg('#', 'alt_1') + call assert_equal('Xfile_alt_1', getreg('#')) + call setreg('#', b2) + call assert_equal('Xfile_alt_2', getreg('#')) + + let ab = 'regwrite' + call setreg('=', '') + call setreg('=', 'a', 'a') + call setreg('=', 'b', 'a') + call assert_equal('regwrite', getreg('=')) + + enew! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From fe927b84decdb8e68208d85e071bd8c2595c7673 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 30 Nov 2019 04:05:20 +0100 Subject: terminfo_is_bsd_console: fallback to false Ref: https://github.com/neovim/neovim/commit/ab7da4c53138768#r36200515 --- src/nvim/tui/terminfo.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index 14023ce2cb..03173afe07 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -47,9 +47,8 @@ bool terminfo_is_bsd_console(const char *term) // like cursor-shaping. Assume that TERM=xterm is degraded. #8644 return strequal(term, "xterm") && !!os_getenv("XTERM_VERSION"); # endif -#else - return false; #endif + return false; } /// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo -- cgit From edca84cfc91e637e97fcadd484a57911b20cfe75 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 1 Dec 2019 05:04:57 -0800 Subject: Return nil instead of NIL for vim.env (#11486) --- src/nvim/lua/vim.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index e13b44a8ed..00f7c8a48b 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -315,7 +315,14 @@ do vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var) vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar) vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) - vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv) + local function getenv(k) + local v = vim.fn.getenv(k) + if v == vim.NIL then + return nil + end + return v + end + vim.env = make_meta_accessor(getenv, vim.fn.setenv) -- TODO(ashkan) if/when these are available from an API, generate them -- instead of hardcoding. local window_options = { -- cgit From e6da21d12895e2f34c6ce41bb16400d5eef3ea12 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 1 Dec 2019 05:28:53 -0800 Subject: Add vim.cmd as an alias for nvim_command (#11446) --- src/nvim/lua/vim.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 00f7c8a48b..090869c68e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -277,6 +277,10 @@ setmetatable(vim, { __index = __index }) +-- An easier alias for commands. +vim.cmd = vim.api.nvim_command + +-- These are the vim.env/v/g/o/bo/wo variable magic accessors. do local a = vim.api local validate = vim.validate -- cgit From 6aa03e86da041284b5f27a59f73cef0991fc577e Mon Sep 17 00:00:00 2001 From: Siddhant Gupta Date: Sun, 6 Oct 2019 13:37:54 -0700 Subject: API: nvim_source --- src/nvim/api/vim.c | 10 ++++++++++ src/nvim/ex_cmds2.c | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e4a9bd64ff..ee36ae28e6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -25,6 +25,7 @@ #include "nvim/highlight.h" #include "nvim/window.h" #include "nvim/types.h" +#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/screen.h" #include "nvim/memline.h" @@ -72,6 +73,15 @@ void api_vim_free_all_mem(void) map_free(String, handle_T)(namespace_ids); } +void nvim_source(String command, Error *err) + FUNC_API_SINCE(5) +{ + try_start(); + do_source_str((char_u *)command.data); + update_screen(VALID); + try_end(err); +} + /// Executes an ex-command. /// /// On execution error: fails with VimL error, does not update v:errmsg. diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 84291b3637..904a3d10e5 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1193,7 +1193,7 @@ static void script_dump_profile(FILE *fd) /// profiled. bool prof_def_func(void) { - if (current_sctx.sc_sid > 0) { + if (current_sctx.sc_sid > 0 && current_SID < 999999) { return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force; } return false; @@ -3015,6 +3015,44 @@ static FILE *fopen_noinh_readbin(char *filename) return fdopen(fd_tmp, READBIN); } +typedef struct { + char_u *buf; + size_t offset; +} GetStrLineCookie; + +static char_u *get_str_line(int c, void *cookie, int ident) +{ + GetStrLineCookie *p = cookie; + size_t i = p->offset; + if (strlen((char *)p->buf) <= p->offset) { + return NULL; + } + while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) { + i++; + } + char buf[2046]; + char *dst; + dst = xstpncpy(buf, (char *)p->buf+p->offset, i - p->offset); + if ((uint32_t)(dst - buf) != i - p->offset) { + smsg(_("nvim_source error parsing command %s"), p->buf); + } + buf[i-p->offset]='\0'; + p->offset = i + 1; + return (char_u *)xstrdup(buf); +} + +int do_source_str(char_u *cmd) +{ + int retval; + GetStrLineCookie cookie = { + .buf = cmd, + .offset = 0, + }; + current_SID = 999999; + retval = do_cmdline(NULL, get_str_line, (void *)&cookie, + DOCMD_NOWAIT); + return retval; +} /// Read the file "fname" and execute its lines as EX commands. /// -- cgit From 0a8d145075d3ce5fffe2df190992f624ae931809 Mon Sep 17 00:00:00 2001 From: Vikram Pal Date: Sat, 5 Oct 2019 20:07:27 +0530 Subject: API: nvim_source: save/restore script context #11159 Use a constant for the script id. --- src/nvim/api/vim.c | 9 ++++----- src/nvim/ex_cmds2.c | 8 ++++++-- src/nvim/globals.h | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ee36ae28e6..95f6de94a4 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -74,12 +74,11 @@ void api_vim_free_all_mem(void) } void nvim_source(String command, Error *err) - FUNC_API_SINCE(5) + FUNC_API_SINCE(7) { - try_start(); - do_source_str((char_u *)command.data); - update_screen(VALID); - try_end(err); + try_start(); + do_source_str((char_u *)command.data); + try_end(err); } /// Executes an ex-command. diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 904a3d10e5..edfa3fea9a 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1193,7 +1193,7 @@ static void script_dump_profile(FILE *fd) /// profiled. bool prof_def_func(void) { - if (current_sctx.sc_sid > 0 && current_SID < 999999) { + if (current_sctx.sc_sid > 0) { return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force; } return false; @@ -3048,9 +3048,13 @@ int do_source_str(char_u *cmd) .buf = cmd, .offset = 0, }; - current_SID = 999999; + const sctx_T save_current_sctx = current_sctx; + current_sctx.sc_sid = SID_STR; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = 0; retval = do_cmdline(NULL, get_str_line, (void *)&cookie, DOCMD_NOWAIT); + current_sctx = save_current_sctx; return retval; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 15ad6d8767..172c190df2 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -331,6 +331,7 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_NONE -6 // don't set scriptID #define SID_LUA -7 // for Lua scripts/chunks #define SID_API_CLIENT -8 // for API clients +#define SID_STR -9 // for sourcing a string // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); -- cgit From 276c2da28616d7a4f504c328dbb8857d38ab7a4a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 6 Oct 2019 19:40:36 -0700 Subject: API: nvim_source: fix multiline input - DOCMD_REPEAT is needed to source all lines of input. - Fix ":verbose set {option}?" by handling SID_STR in get_scriptname(). closes #8722 --- src/nvim/api/vim.c | 2 +- src/nvim/ex_cmds2.c | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 95f6de94a4..10ece6bc22 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -77,7 +77,7 @@ void nvim_source(String command, Error *err) FUNC_API_SINCE(7) { try_start(); - do_source_str((char_u *)command.data); + do_source_str(command.data); try_end(err); } diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index edfa3fea9a..eec689a28d 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3020,6 +3020,11 @@ typedef struct { size_t offset; } GetStrLineCookie; +/// Get one full line from a sourced string (in-memory, no file). +/// Called by do_cmdline() when it's called from do_source_str(). +/// +/// @return pointer to allocated line, or NULL for end-of-file or +/// some error. static char_u *get_str_line(int c, void *cookie, int ident) { GetStrLineCookie *p = cookie; @@ -3034,18 +3039,19 @@ static char_u *get_str_line(int c, void *cookie, int ident) char *dst; dst = xstpncpy(buf, (char *)p->buf+p->offset, i - p->offset); if ((uint32_t)(dst - buf) != i - p->offset) { - smsg(_("nvim_source error parsing command %s"), p->buf); + smsg(_(":source error parsing command %s"), p->buf); + return NULL; } buf[i-p->offset]='\0'; p->offset = i + 1; return (char_u *)xstrdup(buf); } -int do_source_str(char_u *cmd) +int do_source_str(const char *cmd) { int retval; GetStrLineCookie cookie = { - .buf = cmd, + .buf = (char_u *)cmd, .offset = 0, }; const sctx_T save_current_sctx = current_sctx; @@ -3053,7 +3059,7 @@ int do_source_str(char_u *cmd) current_sctx.sc_seq = 0; current_sctx.sc_lnum = 0; retval = do_cmdline(NULL, get_str_line, (void *)&cookie, - DOCMD_NOWAIT); + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); current_sctx = save_current_sctx; return retval; } @@ -3402,6 +3408,8 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) _("API client (channel id %" PRIu64 ")"), last_set.channel_id); return IObuff; + case SID_STR: + return (char_u *)_(":source (no file)"); default: *should_free = true; return home_replace_save(NULL, -- cgit From bd43e011b5b0feba644ec5feae6c174def31a9e4 Mon Sep 17 00:00:00 2001 From: Vikram Pal Date: Wed, 9 Oct 2019 18:34:37 +0530 Subject: API: nvim_source_output - Similar to nvim_source but will capture the output - Add meaningful VimL tracebacks for nvim_source - Handle got_int - Add error reporting --- src/nvim/api/vim.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/nvim/ex_cmds2.c | 36 +++++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 10ece6bc22..bf722b4f4e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -73,14 +73,63 @@ void api_vim_free_all_mem(void) map_free(String, handle_T)(namespace_ids); } -void nvim_source(String command, Error *err) - FUNC_API_SINCE(7) +/// Executes a multiline block of ex-commands from a string. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param src String containing the ex-commands +/// @param[out] err Error details (Vim error), if any +void nvim_source(String src, Error *err) FUNC_API_SINCE(7) { try_start(); - do_source_str(command.data); + do_source_str(src.data, "nvim_source(..)"); try_end(err); } +/// Executes a multiline block of ex-commands from a string and returns its +/// (non-error) output. Shell |:!| output is not captured. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param src String containing the ex-commands +/// @param[out] err Error details (Vim error), if any +String nvim_source_output(String src, Error *err) FUNC_API_SINCE(7) +{ + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_source_str(src.data, "nvim_source_output(..)"); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); + + if (ERROR_SET(err)) { + goto theend; + } + + if (capture_local.ga_len > 1) { + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size - 1); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. + } +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; +} + /// Executes an ex-command. /// /// On execution error: fails with VimL error, does not update v:errmsg. diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index eec689a28d..bda7b4e8b5 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3025,7 +3025,7 @@ typedef struct { /// /// @return pointer to allocated line, or NULL for end-of-file or /// some error. -static char_u *get_str_line(int c, void *cookie, int ident) +static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) { GetStrLineCookie *p = cookie; size_t i = p->offset; @@ -3037,19 +3037,31 @@ static char_u *get_str_line(int c, void *cookie, int ident) } char buf[2046]; char *dst; - dst = xstpncpy(buf, (char *)p->buf+p->offset, i - p->offset); + dst = xstpncpy(buf, (char *)p->buf + p->offset, i - p->offset); if ((uint32_t)(dst - buf) != i - p->offset) { smsg(_(":source error parsing command %s"), p->buf); return NULL; } - buf[i-p->offset]='\0'; + buf[i - p->offset] = '\0'; p->offset = i + 1; return (char_u *)xstrdup(buf); } -int do_source_str(const char *cmd) +int do_source_str(const char *cmd, const char *traceback_name) { - int retval; + char_u *save_sourcing_name = sourcing_name; + linenr_T save_sourcing_lnum = sourcing_lnum; + char_u sourcing_name_buf[256]; + if (save_sourcing_name == NULL) { + sourcing_name = (char_u *)traceback_name; + } else { + snprintf((char *)sourcing_name_buf, sizeof sourcing_name_buf, + "%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name, + save_sourcing_lnum); + sourcing_name = sourcing_name_buf; + } + sourcing_lnum = 0; + GetStrLineCookie cookie = { .buf = (char_u *)cmd, .offset = 0, @@ -3057,10 +3069,18 @@ int do_source_str(const char *cmd) const sctx_T save_current_sctx = current_sctx; current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; - current_sctx.sc_lnum = 0; - retval = do_cmdline(NULL, get_str_line, (void *)&cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + current_sctx.sc_lnum = save_sourcing_lnum; + int retval = FAIL; + do_cmdline(NULL, get_str_line, (void *)&cookie, + DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); + retval = OK; + if (got_int) { + EMSG(_(e_interr)); + } + current_sctx = save_current_sctx; + sourcing_lnum = save_sourcing_lnum; + sourcing_name = save_sourcing_name; return retval; } -- cgit From b1991f66d5845ccb72c73fdf39153a0e1fbb1124 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 1 Dec 2019 22:26:36 -0800 Subject: API: rename nvim_source => nvim_exec - Eliminate nvim_source_output(): add boolean `output` param to nvim_exec() instead. --- src/nvim/api/vim.c | 39 +++++++++++++++++++++------------------ src/nvim/ex_cmds2.c | 19 +++++++++---------- src/nvim/ex_docmd.c | 9 ++++----- 3 files changed, 34 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index bf722b4f4e..6763a3a936 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -73,28 +73,29 @@ void api_vim_free_all_mem(void) map_free(String, handle_T)(namespace_ids); } -/// Executes a multiline block of ex-commands from a string. +/// Executes Vimscript (multiline block of Ex-commands), like anonymous +/// |:source|. /// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @param src String containing the ex-commands -/// @param[out] err Error details (Vim error), if any -void nvim_source(String src, Error *err) FUNC_API_SINCE(7) -{ - try_start(); - do_source_str(src.data, "nvim_source(..)"); - try_end(err); -} - -/// Executes a multiline block of ex-commands from a string and returns its -/// (non-error) output. Shell |:!| output is not captured. +/// Optionally returns (non-error, non-shell |:!|) output. /// /// On execution error: fails with VimL error, does not update v:errmsg. /// -/// @param src String containing the ex-commands +/// @see |execute()| +/// @see |nvim_command()| +/// +/// @param src Vimscript code +/// @param output Capture and return all (non-error, non-shell |:!|) output /// @param[out] err Error details (Vim error), if any -String nvim_source_output(String src, Error *err) FUNC_API_SINCE(7) +String nvim_exec(String src, Boolean output, Error *err) + FUNC_API_SINCE(7) { + if (!output) { + try_start(); + do_source_str(src.data, "nvim_exec()"); + try_end(err); + return (String)STRING_INIT; + } + const int save_msg_silent = msg_silent; garray_T *const save_capture_ga = capture_ga; garray_T capture_local; @@ -103,7 +104,7 @@ String nvim_source_output(String src, Error *err) FUNC_API_SINCE(7) try_start(); msg_silent++; capture_ga = &capture_local; - do_source_str(src.data, "nvim_source_output(..)"); + do_source_str(src.data, "nvim_exec()"); capture_ga = save_capture_ga; msg_silent = save_msg_silent; try_end(err); @@ -134,6 +135,8 @@ theend: /// /// On execution error: fails with VimL error, does not update v:errmsg. /// +/// @see |nvim_exec()| +/// /// @param command Ex-command string /// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) @@ -436,7 +439,7 @@ theend: return (String)STRING_INIT; } -/// Evaluates a VimL expression (:help expression). +/// Evaluates a VimL |expression|. /// Dictionaries and Lists are recursively expanded. /// /// On execution error: fails with VimL error, does not update v:errmsg. diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index bda7b4e8b5..8479c15b40 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3047,6 +3047,9 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)xstrdup(buf); } +/// Executes lines in `src` as Ex commands. +/// +/// @see do_source() int do_source_str(const char *cmd, const char *traceback_name) { char_u *save_sourcing_name = sourcing_name; @@ -3055,7 +3058,7 @@ int do_source_str(const char *cmd, const char *traceback_name) if (save_sourcing_name == NULL) { sourcing_name = (char_u *)traceback_name; } else { - snprintf((char *)sourcing_name_buf, sizeof sourcing_name_buf, + snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf), "%s called at %s:%"PRIdLINENR, traceback_name, save_sourcing_name, save_sourcing_lnum); sourcing_name = sourcing_name_buf; @@ -3070,24 +3073,20 @@ int do_source_str(const char *cmd, const char *traceback_name) current_sctx.sc_sid = SID_STR; current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; - int retval = FAIL; - do_cmdline(NULL, get_str_line, (void *)&cookie, - DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); - retval = OK; - if (got_int) { - EMSG(_(e_interr)); - } - + int retval = do_cmdline(NULL, get_str_line, (void *)&cookie, + DOCMD_VERBOSE | DOCMD_NOWAIT | DOCMD_REPEAT); current_sctx = save_current_sctx; sourcing_lnum = save_sourcing_lnum; sourcing_name = save_sourcing_name; return retval; } -/// Read the file "fname" and execute its lines as EX commands. +/// Reads the file `fname` and executes its lines as Ex commands. /// /// This function may be called recursively! /// +/// @see do_source_str +/// /// @param fname /// @param check_other check for .vimrc and _vimrc /// @param is_vimrc DOSO_ value diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 743286c64a..dc2726709f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -421,13 +421,12 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, // If force_abort is set, we cancel everything. did_emsg = false; - /* - * KeyTyped is only set when calling vgetc(). Reset it here when not - * calling vgetc() (sourced command lines). - */ + // KeyTyped is only set when calling vgetc(). Reset it here when not + // calling vgetc() (sourced command lines). if (!(flags & DOCMD_KEYTYPED) - && !getline_equal(fgetline, cookie, getexline)) + && !getline_equal(fgetline, cookie, getexline)) { KeyTyped = false; + } /* * Continue executing command lines: -- cgit From ab860cb5f6c749b937e0986167702a36e45000ca Mon Sep 17 00:00:00 2001 From: erw7 Date: Mon, 2 Dec 2019 19:09:30 +0900 Subject: dictwatcher: fix use-after-free #11495 fixes #11494 --- src/nvim/eval/typval.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 106b8f6eed..72ee45a03a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1200,6 +1200,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, typval_T rettv; + dict->dv_refcount++; QUEUE *w; QUEUE_FOREACH(w, &dict->watchers) { DictWatcher *watcher = tv_dict_watcher_node_data(w); @@ -1211,6 +1212,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, tv_clear(&rettv); } } + tv_dict_unref(dict); for (size_t i = 1; i < ARRAY_SIZE(argv); i++) { tv_clear(argv + i); -- cgit From 3b0191e3302612ee0e7d17d63cef0146d8fc0e20 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 2 Dec 2019 17:12:26 +0100 Subject: src/nvim/testdir/test_quickfix.vim: align with Vim (#11502) --- src/nvim/testdir/test_quickfix.vim | 859 +++++++++++++++++++------------------ 1 file changed, 430 insertions(+), 429 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 60ffb11793..d7b387c2c9 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2597,94 +2597,6 @@ func Test_resize_from_copen() endtry endfunc -" Test for aborting quickfix commands using QuickFixCmdPre -func Xtest_qfcmd_abort(cchar) - call s:setup_commands(a:cchar) - - call g:Xsetlist([], 'f') - - " cexpr/lexpr - let e = '' - try - Xexpr ["F1:10:Line10", "F2:20:Line20"] - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - - " cfile/lfile - call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1') - let e = '' - try - Xfile Xfile1 - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - call delete('Xfile1') - - " cgetbuffer/lgetbuffer - enew! - call append(0, ["F1:10:Line10", "F2:20:Line20"]) - let e = '' - try - Xgetbuffer - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - enew! - - " vimgrep/lvimgrep - let e = '' - try - Xvimgrep /func/ test_quickfix.vim - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - - " helpgrep/lhelpgrep - let e = '' - try - Xhelpgrep quickfix - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - - " grep/lgrep - if has('unix') - let e = '' - try - silent Xgrep func test_quickfix.vim - catch /.*/ - let e = v:exception - endtry - call assert_equal('AbortCmd', e) - call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) - endif -endfunc - -func Test_qfcmd_abort() - augroup QF_Test - au! - autocmd QuickFixCmdPre * throw "AbortCmd" - augroup END - - call Xtest_qfcmd_abort('c') - call Xtest_qfcmd_abort('l') - - augroup QF_Test - au! - augroup END -endfunc - " Tests for the quickfix buffer b:changedtick variable func Xchangedtick_tests(cchar) call s:setup_commands(a:cchar) @@ -3041,191 +2953,107 @@ func Test_qf_id() call Xqfid_tests('l') endfunc -func Test_getqflist_invalid_nr() - " The following commands used to crash Vim - cexpr "" - call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX}) - - " Cleanup - call setqflist([], 'r') -endfunc - -" Test for shortening/simplifying the file name when opening the -" quickfix window or when displaying the quickfix list -func Test_shorten_fname() - if !has('unix') - return - endif - %bwipe - " Create a quickfix list with a absolute path filename - let fname = getcwd() . '/test_quickfix.vim' - call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) - call assert_equal(fname, bufname('test_quickfix.vim')) - " Opening the quickfix window should simplify the file path - cwindow - call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) - cclose - %bwipe - " Create a quickfix list with a absolute path filename - call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) - call assert_equal(fname, bufname('test_quickfix.vim')) - " Displaying the quickfix list should simplify the file path - silent! clist - call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) -endfunc +func Xqfjump_tests(cchar) + call s:setup_commands(a:cchar) -" Quickfix title tests -" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands. -" Otherwise due to indentation, the title is set with spaces at the beginning -" of the command. -func Test_qftitle() - call writefile(["F1:1:Line1"], 'Xerr') + call writefile(["Line1\tFoo", "Line2"], 'F1') + call writefile(["Line1\tBar", "Line2"], 'F2') + call writefile(["Line1\tBaz", "Line2"], 'F3') - " :cexpr - exe "cexpr readfile('Xerr')" - call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title) + call g:Xsetlist([], 'f') - " :cgetexpr - exe "cgetexpr readfile('Xerr')" - call assert_equal(":cgetexpr readfile('Xerr')", - \ getqflist({'title' : 1}).title) + " Tests for + " Jumping to a line using a pattern + " Jumping to a column greater than the last column in a line + " Jumping to a line greater than the last line in the file + let l = [] + for i in range(1, 7) + call add(l, {}) + endfor + let l[0].filename='F1' + let l[0].pattern='Line1' + let l[1].filename='F2' + let l[1].pattern='Line1' + let l[2].filename='F3' + let l[2].pattern='Line1' + let l[3].filename='F3' + let l[3].lnum=1 + let l[3].col=9 + let l[3].vcol=1 + let l[4].filename='F3' + let l[4].lnum=99 + let l[5].filename='F3' + let l[5].lnum=1 + let l[5].col=99 + let l[5].vcol=1 + let l[6].filename='F3' + let l[6].pattern='abcxyz' - " :caddexpr - call setqflist([], 'f') - exe "caddexpr readfile('Xerr')" - call assert_equal(":caddexpr readfile('Xerr')", - \ getqflist({'title' : 1}).title) + call g:Xsetlist([], ' ', {'items' : l}) + Xopen | only + 2Xnext + call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) + call assert_equal('F3', bufname('%')) + Xnext + call assert_equal(7, col('.')) + Xnext + call assert_equal(2, line('.')) + Xnext + call assert_equal(9, col('.')) + 2 + Xnext + call assert_equal(2, line('.')) - " :cbuffer - new Xerr - exe "cbuffer" - call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title) + if a:cchar == 'l' + " When jumping to a location list entry in the location list window and + " no usable windows are available, then a new window should be opened. + enew! | new | only + call g:Xsetlist([], 'f') + setlocal buftype=nofile + new + call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) + Xopen + let winid = win_getid() + wincmd p + close + call win_gotoid(winid) + Xnext + call assert_equal(3, winnr('$')) + call assert_equal(1, winnr()) + call assert_equal(2, line('.')) - " :cgetbuffer - edit Xerr - exe "cgetbuffer" - call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title) + " When jumping to an entry in the location list window and the window + " associated with the location list is not present and a window containing + " the file is already present, then that window should be used. + close + belowright new + call g:Xsetlist([], 'f') + edit F3 + call win_gotoid(winid) + Xlast + call assert_equal(3, winnr()) + call assert_equal(6, g:Xgetlist({'size' : 1}).size) + call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid) + endif - " :caddbuffer - call setqflist([], 'f') - edit Xerr - exe "caddbuffer" - call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title) + " Cleanup + enew! + new | only - " :cfile - exe "cfile Xerr" - call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title) + call delete('F1') + call delete('F2') + call delete('F3') +endfunc - " :cgetfile - exe "cgetfile Xerr" - call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title) +func Test_qfjump() + call Xqfjump_tests('c') + call Xqfjump_tests('l') +endfunc - " :caddfile - call setqflist([], 'f') - exe "caddfile Xerr" - call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title) - - " :grep - set grepprg=internal - exe "grep F1 Xerr" - call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title) - - " :grepadd - call setqflist([], 'f') - exe "grepadd F1 Xerr" - call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title) - set grepprg&vim - - " :vimgrep - exe "vimgrep F1 Xerr" - call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title) - - " :vimgrepadd - call setqflist([], 'f') - exe "vimgrepadd F1 Xerr" - call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title) - - call setqflist(['F1:10:L10'], ' ') - call assert_equal(':setqflist()', getqflist({'title' : 1}).title) - - call setqflist([], 'f') - call setqflist(['F1:10:L10'], 'a') - call assert_equal(':setqflist()', getqflist({'title' : 1}).title) - - call setqflist([], 'f') - call setqflist(['F1:10:L10'], 'r') - call assert_equal(':setqflist()', getqflist({'title' : 1}).title) - - close - call delete('Xerr') - - call setqflist([], ' ', {'title' : 'Errors'}) - copen - call assert_equal('Errors', w:quickfix_title) - call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]}) - call assert_equal('Errors', w:quickfix_title) - cclose -endfunc - -" Test for the position of the quickfix and location list window -func Test_qfwin_pos() - " Open two windows - new | only - new - cexpr ['F1:10:L10'] - copen - " Quickfix window should be the bottom most window - call assert_equal(3, winnr()) - close - " Open at the very top - wincmd t - topleft copen - call assert_equal(1, winnr()) - close - " open left of the current window - wincmd t - below new - leftabove copen - call assert_equal(2, winnr()) - close - " open right of the current window - rightbelow copen - call assert_equal(3, winnr()) - close -endfunc - -" The following test used to crash Vim -func Test_lhelpgrep_autocmd() - lhelpgrep quickfix - autocmd QuickFixCmdPost * call setloclist(0, [], 'f') - lhelpgrep buffer - call assert_equal('help', &filetype) - call assert_equal(0, getloclist(0, {'nr' : '$'}).nr) - lhelpgrep tabpage - call assert_equal('help', &filetype) - call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) - au! QuickFixCmdPost - new | only -endfunc - -" Test to make sure that an empty quickfix buffer is not reused for loading -" a normal buffer. -func Test_empty_qfbuf() - enew | only - call writefile(["Test"], 'Xfile1') - call setqflist([], 'f') - copen | only - let qfbuf = bufnr('') - edit Xfile1 - call assert_notequal(qfbuf, bufnr('')) - enew - call delete('Xfile1') -endfunc - -" Tests for the getqflist() and getloclist() functions when the list is not -" present or is empty -func Xgetlist_empty_tests(cchar) - call s:setup_commands(a:cchar) +" Tests for the getqflist() and getloclist() functions when the list is not +" present or is empty +func Xgetlist_empty_tests(cchar) + call s:setup_commands(a:cchar) " Empty quickfix stack call g:Xsetlist([], 'f') @@ -3319,6 +3147,16 @@ func Test_getqflist() call Xgetlist_empty_tests('l') endfunc + +func Test_getqflist_invalid_nr() + " The following commands used to crash Vim + cexpr "" + call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX}) + + " Cleanup + call setqflist([], 'r') +endfunc + " Tests for the quickfix/location list changedtick func Xqftick_tests(cchar) call s:setup_commands(a:cchar) @@ -3377,6 +3215,41 @@ func Test_qf_tick() call Xqftick_tests('l') endfunc +" Test helpgrep with lang specifier +func Xtest_helpgrep_with_lang_specifier(cchar) + call s:setup_commands(a:cchar) + Xhelpgrep Vim@en + call assert_equal('help', &filetype) + call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr) + new | only +endfunc + +func Test_helpgrep_with_lang_specifier() + call Xtest_helpgrep_with_lang_specifier('c') + call Xtest_helpgrep_with_lang_specifier('l') +endfunc + +" The following test used to crash Vim. +" Open the location list window and close the regular window associated with +" the location list. When the garbage collection runs now, it incorrectly +" marks the location list context as not in use and frees the context. +func Test_ll_window_ctx() + call setloclist(0, [], 'f') + call setloclist(0, [], 'a', {'context' : []}) + lopen | only + call test_garbagecollect_now() + echo getloclist(0, {'context' : 1}).context + enew | only +endfunc + +" The following test used to crash vim +func Test_lfile_crash() + sp Xtest + au QuickFixCmdPre * bw + call assert_fails('lfile', 'E40') + au! QuickFixCmdPre +endfunc + " The following test used to crash vim func Test_lbuffer_crash() sv Xtest @@ -3438,177 +3311,203 @@ func Test_lvimgrep_crash() enew | only endfunc -func Xqfjump_tests(cchar) - call s:setup_commands(a:cchar) - - call writefile(["Line1\tFoo", "Line2"], 'F1') - call writefile(["Line1\tBar", "Line2"], 'F2') - call writefile(["Line1\tBaz", "Line2"], 'F3') +" Test for the position of the quickfix and location list window +func Test_qfwin_pos() + " Open two windows + new | only + new + cexpr ['F1:10:L10'] + copen + " Quickfix window should be the bottom most window + call assert_equal(3, winnr()) + close + " Open at the very top + wincmd t + topleft copen + call assert_equal(1, winnr()) + close + " open left of the current window + wincmd t + below new + leftabove copen + call assert_equal(2, winnr()) + close + " open right of the current window + rightbelow copen + call assert_equal(3, winnr()) + close +endfunc - call g:Xsetlist([], 'f') +" Tests for quickfix/location lists changed by autocommands when +" :vimgrep/:lvimgrep commands are running. +func Test_vimgrep_autocmd() + call setqflist([], 'f') + call writefile(['stars'], 'Xtest1.txt') + call writefile(['stars'], 'Xtest2.txt') - " Tests for - " Jumping to a line using a pattern - " Jumping to a column greater than the last column in a line - " Jumping to a line greater than the last line in the file - let l = [] - for i in range(1, 7) - call add(l, {}) - endfor - let l[0].filename='F1' - let l[0].pattern='Line1' - let l[1].filename='F2' - let l[1].pattern='Line1' - let l[2].filename='F3' - let l[2].pattern='Line1' - let l[3].filename='F3' - let l[3].lnum=1 - let l[3].col=9 - let l[3].vcol=1 - let l[4].filename='F3' - let l[4].lnum=99 - let l[5].filename='F3' - let l[5].lnum=1 - let l[5].col=99 - let l[5].vcol=1 - let l[6].filename='F3' - let l[6].pattern='abcxyz' + " Test 1: + " When searching for a pattern using :vimgrep, if the quickfix list is + " changed by an autocmd, the results should be added to the correct quickfix + " list. + autocmd BufRead Xtest2.txt cexpr '' | cexpr '' + silent vimgrep stars Xtest*.txt + call assert_equal(1, getqflist({'nr' : 0}).nr) + call assert_equal(3, getqflist({'nr' : '$'}).nr) + call assert_equal('Xtest2.txt', bufname(getqflist()[1].bufnr)) + au! BufRead Xtest2.txt - call g:Xsetlist([], ' ', {'items' : l}) - Xopen | only - 2Xnext - call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) - call assert_equal('F3', bufname('%')) - Xnext - call assert_equal(7, col('.')) - Xnext - call assert_equal(2, line('.')) - Xnext - call assert_equal(9, col('.')) - 2 - Xnext - call assert_equal(2, line('.')) + " Test 2: + " When searching for a pattern using :vimgrep, if the quickfix list is + " freed, then a error should be given. + silent! %bwipe! + call setqflist([], 'f') + autocmd BufRead Xtest2.txt for i in range(10) | cexpr '' | endfor + call assert_fails('vimgrep stars Xtest*.txt', 'E925:') + au! BufRead Xtest2.txt - if a:cchar == 'l' - " When jumping to a location list entry in the location list window and - " no usable windows are available, then a new window should be opened. - enew! | new | only - call g:Xsetlist([], 'f') - setlocal buftype=nofile - new - call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) - Xopen - let winid = win_getid() - wincmd p - close - call win_gotoid(winid) - Xnext - call assert_equal(3, winnr('$')) - call assert_equal(1, winnr()) - call assert_equal(2, line('.')) + " Test 3: + " When searching for a pattern using :lvimgrep, if the location list is + " freed, then the command should error out. + silent! %bwipe! + let g:save_winid = win_getid() + autocmd BufRead Xtest2.txt call setloclist(g:save_winid, [], 'f') + call assert_fails('lvimgrep stars Xtest*.txt', 'E926:') + au! BufRead Xtest2.txt - " When jumping to an entry in the location list window and the window - " associated with the location list is not present and a window containing - " the file is already present, then that window should be used. - close - belowright new - call g:Xsetlist([], 'f') - edit F3 - call win_gotoid(winid) - Xlast - call assert_equal(3, winnr()) - call assert_equal(6, g:Xgetlist({'size' : 1}).size) - call assert_equal(winid, g:Xgetlist({'winid' : 1}).winid) - endif + call delete('Xtest1.txt') + call delete('Xtest2.txt') + call setqflist([], 'f') +endfunc - " Cleanup - enew! +" The following test used to crash Vim +func Test_lhelpgrep_autocmd() + lhelpgrep quickfix + autocmd QuickFixCmdPost * call setloclist(0, [], 'f') + lhelpgrep buffer + call assert_equal('help', &filetype) + call assert_equal(0, getloclist(0, {'nr' : '$'}).nr) + lhelpgrep tabpage + call assert_equal('help', &filetype) + call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) + au! QuickFixCmdPost new | only +endfunc - call delete('F1') - call delete('F2') - call delete('F3') +" Test for shortening/simplifying the file name when opening the +" quickfix window or when displaying the quickfix list +func Test_shorten_fname() + if !has('unix') + return + endif + %bwipe + " Create a quickfix list with a absolute path filename + let fname = getcwd() . '/test_quickfix.vim' + call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) + call assert_equal(fname, bufname('test_quickfix.vim')) + " Opening the quickfix window should simplify the file path + cwindow + call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) + cclose + %bwipe + " Create a quickfix list with a absolute path filename + call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) + call assert_equal(fname, bufname('test_quickfix.vim')) + " Displaying the quickfix list should simplify the file path + silent! clist + call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) endfunc -func Test_qfjump() - call Xqfjump_tests('c') - call Xqfjump_tests('l') -endfunc +" Quickfix title tests +" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands. +" Otherwise due to indentation, the title is set with spaces at the beginning +" of the command. +func Test_qftitle() + call writefile(["F1:1:Line1"], 'Xerr') + + " :cexpr + exe "cexpr readfile('Xerr')" + call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title) + + " :cgetexpr + exe "cgetexpr readfile('Xerr')" + call assert_equal(":cgetexpr readfile('Xerr')", + \ getqflist({'title' : 1}).title) + + " :caddexpr + call setqflist([], 'f') + exe "caddexpr readfile('Xerr')" + call assert_equal(":caddexpr readfile('Xerr')", + \ getqflist({'title' : 1}).title) + + " :cbuffer + new Xerr + exe "cbuffer" + call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title) + + " :cgetbuffer + edit Xerr + exe "cgetbuffer" + call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title) -" Test helpgrep with lang specifier -func Xtest_helpgrep_with_lang_specifier(cchar) - call s:setup_commands(a:cchar) - Xhelpgrep Vim@en - call assert_equal('help', &filetype) - call assert_notequal(0, g:Xgetlist({'nr' : '$'}).nr) - new | only -endfunc + " :caddbuffer + call setqflist([], 'f') + edit Xerr + exe "caddbuffer" + call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title) -func Test_helpgrep_with_lang_specifier() - call Xtest_helpgrep_with_lang_specifier('c') - call Xtest_helpgrep_with_lang_specifier('l') -endfunc + " :cfile + exe "cfile Xerr" + call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title) -" The following test used to crash Vim. -" Open the location list window and close the regular window associated with -" the location list. When the garbage collection runs now, it incorrectly -" marks the location list context as not in use and frees the context. -func Test_ll_window_ctx() - call setloclist(0, [], 'f') - call setloclist(0, [], 'a', {'context' : []}) - lopen | only - call test_garbagecollect_now() - echo getloclist(0, {'context' : 1}).context - enew | only -endfunc + " :cgetfile + exe "cgetfile Xerr" + call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title) -" The following test used to crash vim -func Test_lfile_crash() - sp Xtest - au QuickFixCmdPre * bw - call assert_fails('lfile', 'E40') - au! QuickFixCmdPre -endfunc + " :caddfile + call setqflist([], 'f') + exe "caddfile Xerr" + call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title) -" Tests for quickfix/location lists changed by autocommands when -" :vimgrep/:lvimgrep commands are running. -func Test_vimgrep_autocmd() + " :grep + set grepprg=internal + exe "grep F1 Xerr" + call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title) + + " :grepadd call setqflist([], 'f') - call writefile(['stars'], 'Xtest1.txt') - call writefile(['stars'], 'Xtest2.txt') + exe "grepadd F1 Xerr" + call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title) + set grepprg&vim - " Test 1: - " When searching for a pattern using :vimgrep, if the quickfix list is - " changed by an autocmd, the results should be added to the correct quickfix - " list. - autocmd BufRead Xtest2.txt cexpr '' | cexpr '' - silent vimgrep stars Xtest*.txt - call assert_equal(1, getqflist({'nr' : 0}).nr) - call assert_equal(3, getqflist({'nr' : '$'}).nr) - call assert_equal('Xtest2.txt', bufname(getqflist()[1].bufnr)) - au! BufRead Xtest2.txt + " :vimgrep + exe "vimgrep F1 Xerr" + call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title) - " Test 2: - " When searching for a pattern using :vimgrep, if the quickfix list is - " freed, then a error should be given. - silent! %bwipe! + " :vimgrepadd call setqflist([], 'f') - autocmd BufRead Xtest2.txt for i in range(10) | cexpr '' | endfor - call assert_fails('vimgrep stars Xtest*.txt', 'E925:') - au! BufRead Xtest2.txt + exe "vimgrepadd F1 Xerr" + call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title) - " Test 3: - " When searching for a pattern using :lvimgrep, if the location list is - " freed, then the command should error out. - silent! %bwipe! - let g:save_winid = win_getid() - autocmd BufRead Xtest2.txt call setloclist(g:save_winid, [], 'f') - call assert_fails('lvimgrep stars Xtest*.txt', 'E926:') - au! BufRead Xtest2.txt + call setqflist(['F1:10:L10'], ' ') + call assert_equal(':setqflist()', getqflist({'title' : 1}).title) - call delete('Xtest1.txt') - call delete('Xtest2.txt') call setqflist([], 'f') + call setqflist(['F1:10:L10'], 'a') + call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + + call setqflist([], 'f') + call setqflist(['F1:10:L10'], 'r') + call assert_equal(':setqflist()', getqflist({'title' : 1}).title) + + close + call delete('Xerr') + + call setqflist([], ' ', {'title' : 'Errors'}) + copen + call assert_equal('Errors', w:quickfix_title) + call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]}) + call assert_equal('Errors', w:quickfix_title) + cclose endfunc func Test_lbuffer_with_bwipe() @@ -3623,23 +3522,6 @@ func Test_lbuffer_with_bwipe() augroup END endfunc -" Tests for the ':filter /pat/ clist' command -func Test_filter_clist() - cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15'] - call assert_equal([' 2 Xfile2:15 col 15: Line 15'], - \ split(execute('filter /Line 15/ clist'), "\n")) - call assert_equal([' 1 Xfile1:10 col 10: Line 10'], - \ split(execute('filter /Xfile1/ clist'), "\n")) - call assert_equal([], split(execute('filter /abc/ clist'), "\n")) - - call setqflist([{'module' : 'abc', 'pattern' : 'pat1'}, - \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ') - call assert_equal([' 2 pqr:pat2: '], - \ split(execute('filter /pqr/ clist'), "\n")) - call assert_equal([' 1 abc:pat1: '], - \ split(execute('filter /pat1/ clist'), "\n")) -endfunc - " Test for an autocmd freeing the quickfix/location list when cexpr/lexpr is " running func Xexpr_acmd_freelist(cchar) @@ -3789,6 +3671,23 @@ func Test_autocmd_changelist() call Xautocmd_changelist('l') endfunc +" Tests for the ':filter /pat/ clist' command +func Test_filter_clist() + cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15'] + call assert_equal([' 2 Xfile2:15 col 15: Line 15'], + \ split(execute('filter /Line 15/ clist'), "\n")) + call assert_equal([' 1 Xfile1:10 col 10: Line 10'], + \ split(execute('filter /Xfile1/ clist'), "\n")) + call assert_equal([], split(execute('filter /abc/ clist'), "\n")) + + call setqflist([{'module' : 'abc', 'pattern' : 'pat1'}, + \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ') + call assert_equal([' 2 pqr:pat2: '], + \ split(execute('filter /pqr/ clist'), "\n")) + call assert_equal([' 1 abc:pat1: '], + \ split(execute('filter /pat1/ clist'), "\n")) +endfunc + " Tests for the "CTRL-W " command. func Xview_result_split_tests(cchar) call s:setup_commands(a:cchar) @@ -3872,6 +3771,20 @@ func Test_viscol() call delete('Xfile1') endfunc +" Test to make sure that an empty quickfix buffer is not reused for loading +" a normal buffer. +func Test_empty_qfbuf() + enew | only + call writefile(["Test"], 'Xfile1') + call setqflist([], 'f') + copen | only + let qfbuf = bufnr('') + edit Xfile1 + call assert_notequal(qfbuf, bufnr('')) + enew + call delete('Xfile1') +endfunc + " Test for the :cbelow, :cabove, :lbelow and :labove commands. func Xtest_below(cchar) call s:setup_commands(a:cchar) @@ -3978,4 +3891,92 @@ func Test_cbelow() call Xtest_below('l') endfunc +" Test for aborting quickfix commands using QuickFixCmdPre +func Xtest_qfcmd_abort(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + + " cexpr/lexpr + let e = '' + try + Xexpr ["F1:10:Line10", "F2:20:Line20"] + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " cfile/lfile + call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1') + let e = '' + try + Xfile Xfile1 + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + call delete('Xfile1') + + " cgetbuffer/lgetbuffer + enew! + call append(0, ["F1:10:Line10", "F2:20:Line20"]) + let e = '' + try + Xgetbuffer + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + enew! + + " vimgrep/lvimgrep + let e = '' + try + Xvimgrep /func/ test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " helpgrep/lhelpgrep + let e = '' + try + Xhelpgrep quickfix + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " grep/lgrep + if has('unix') + let e = '' + try + silent Xgrep func test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + endif +endfunc + +func Test_qfcmd_abort() + augroup QF_Test + au! + autocmd QuickFixCmdPre * throw "AbortCmd" + augroup END + + call Xtest_qfcmd_abort('c') + call Xtest_qfcmd_abort('l') + + augroup QF_Test + au! + augroup END +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 0b7a7b23cce8c9eeeaac1a38190fd49e1033625c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 2 Dec 2019 17:18:37 +0100 Subject: oldtest: support for running by filename (#11473) Follow-up to 8969efca8 (Vim patch 8.1.0723) NOTE: This changes the main entrypoint for running single oldtest files to not use/require the ".res" extension anymore. But it is handled for B/C. Adds a phony rule to run oldtest by filename. Not going through "$(MAKE)" avoids GNUmakefile being used then (which I use for WIP things), and it seems like SINGLE_MAKE should be used anyway probably. --- src/nvim/testdir/Makefile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 1e9031e098..6bf070a7e2 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -34,22 +34,23 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. NEW_TESTS_ALOT := test_alot_utf8 test_alot -NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim) +NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT))) +NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim) # Ignored tests. # test_alot_latin: Nvim does not allow setting encoding. # test_autochdir: ported to Lua, but kept for easier merging. # test_eval_func: used as include in old-style test (test_eval.in). # test_listlbr: Nvim does not allow setting encoding. # test_largefile: uses too much resources to run on CI. -NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \ - test_alot_latin \ +NEW_TESTS_IGNORE := \ + test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \ test_autochdir \ test_eval_func \ test_listlbr \ test_largefile \ -NEW_TESTS ?= $(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT) -NEW_TESTS_RES ?= $(addsuffix .res,$(NEW_TESTS)) +NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim)))) +NEW_TESTS_RES := $(addsuffix .res,$(filter-out $(NEW_TESTS_ALOT) $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_IGNORE),$(NEW_TESTS)) $(NEW_TESTS_ALOT)) ifdef VALGRIND_GDB @@ -114,7 +115,7 @@ fixff: dotest.in # Execute an individual new style test, e.g.: -# make test_largefile +# make test_largefile $(NEW_TESTS): rm -f $@.res test.log messages @MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res -- cgit From 56be9fbde1ec27fd8ca3fdb8eb6adb94b8c33b53 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 2 Dec 2019 18:26:40 +0100 Subject: testdir/runnvim.sh: create messages file always (#11503) --- src/nvim/testdir/runnvim.sh | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh index 2dcd9150be..72f9254635 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -82,6 +82,11 @@ main() {( fi if test "$FAILED" = 1 ; then echo "Test $test_name failed, see output above and summary for more details" >> test.log + # When Neovim crashed/aborted it might not have created messages. + # test.log itself is used as an indicator to exit non-zero in the Makefile. + if ! test -f message; then + cp -a test.log messages + fi fi )} -- cgit From 22b52dd462e5dc9d5429305215bfb20aa20517c5 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 2 Dec 2019 22:21:15 +0100 Subject: log_init: call log_path_init (#11501) This has to be done after `init_homedir` for XDG default and `set_init_1` for lookup from env, which could be done earlier likely (to help with https://github.com/neovim/neovim/issues/10937), but this keeps it in sync with Vim. Fixes https://github.com/neovim/neovim/issues/11499. --- src/nvim/log.c | 1 + src/nvim/main.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/log.c b/src/nvim/log.c index db36611933..225e40cdb4 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -93,6 +93,7 @@ static bool log_path_init(void) void log_init(void) { uv_mutex_init(&mutex); + log_path_init(); } void log_lock(void) diff --git a/src/nvim/main.c b/src/nvim/main.c index c8959d9ad1..c7011f4f4e 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -144,7 +144,6 @@ static const char *err_extra_cmd = void event_init(void) { - log_init(); loop_init(&main_loop, NULL); resize_events = multiqueue_new_child(main_loop.events); @@ -220,6 +219,7 @@ void early_init(void) // First find out the home directory, needed to expand "~" in options. init_homedir(); // find real value of $HOME set_init_1(); + log_init(); TIME_MSG("inits 1"); set_lang_var(); // set v:lang and v:ctype -- cgit From c34130d13a842ae0c0c1724d05800a954547d327 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 1 Dec 2019 22:43:16 -0800 Subject: API: deprecate nvim_command_output --- src/nvim/api/vim.c | 73 +++++++++++++---------------------------------------- src/nvim/context.c | 2 +- src/nvim/ex_cmds2.c | 2 +- src/nvim/ex_docmd.c | 36 +++++++++++++------------- 4 files changed, 37 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 6763a3a936..5eca267d61 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -76,7 +76,8 @@ void api_vim_free_all_mem(void) /// Executes Vimscript (multiline block of Ex-commands), like anonymous /// |:source|. /// -/// Optionally returns (non-error, non-shell |:!|) output. +/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), +/// etc. /// /// On execution error: fails with VimL error, does not update v:errmsg. /// @@ -86,24 +87,21 @@ void api_vim_free_all_mem(void) /// @param src Vimscript code /// @param output Capture and return all (non-error, non-shell |:!|) output /// @param[out] err Error details (Vim error), if any +/// @return Output (non-error, non-shell |:!|) if `output` is true, +/// else empty string. String nvim_exec(String src, Boolean output, Error *err) FUNC_API_SINCE(7) { - if (!output) { - try_start(); - do_source_str(src.data, "nvim_exec()"); - try_end(err); - return (String)STRING_INIT; - } - const int save_msg_silent = msg_silent; garray_T *const save_capture_ga = capture_ga; garray_T capture_local; - ga_init(&capture_local, 1, 80); + if (output) { + ga_init(&capture_local, 1, 80); + capture_ga = &capture_local; + } try_start(); msg_silent++; - capture_ga = &capture_local; do_source_str(src.data, "nvim_exec()"); capture_ga = save_capture_ga; msg_silent = save_msg_silent; @@ -113,10 +111,10 @@ String nvim_exec(String src, Boolean output, Error *err) goto theend; } - if (capture_local.ga_len > 1) { + if (output && capture_local.ga_len > 1) { String s = (String){ - .data = capture_local.ga_data, - .size = (size_t)capture_local.ga_len, + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, }; // redir usually (except :echon) prepends a newline. if (s.data[0] == '\n') { @@ -127,7 +125,9 @@ String nvim_exec(String src, Boolean output, Error *err) return s; // Caller will free the memory. } theend: - ga_clear(&capture_local); + if (output) { + ga_clear(&capture_local); + } return (String)STRING_INIT; } @@ -393,50 +393,13 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -/// Executes an ex-command and returns its (non-error) output. -/// Shell |:!| output is not captured. -/// -/// On execution error: fails with VimL error, does not update v:errmsg. -/// -/// @param command Ex-command string -/// @param[out] err Error details (Vim error), if any +/// @deprecated +/// @see nvim_exec String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(7) { - const int save_msg_silent = msg_silent; - garray_T *const save_capture_ga = capture_ga; - garray_T capture_local; - ga_init(&capture_local, 1, 80); - - try_start(); - msg_silent++; - capture_ga = &capture_local; - do_cmdline_cmd(command.data); - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - try_end(err); - - if (ERROR_SET(err)) { - goto theend; - } - - if (capture_local.ga_len > 1) { - String s = (String){ - .data = capture_local.ga_data, - .size = (size_t)capture_local.ga_len, - }; - // redir usually (except :echon) prepends a newline. - if (s.data[0] == '\n') { - memmove(s.data, s.data + 1, s.size - 1); - s.data[s.size - 1] = '\0'; - s.size = s.size - 1; - } - return s; // Caller will free the memory. - } - -theend: - ga_clear(&capture_local); - return (String)STRING_INIT; + return nvim_exec(command, true, err); } /// Evaluates a VimL |expression|. diff --git a/src/nvim/context.c b/src/nvim/context.c index 2f872ff2bf..1ae0510762 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -254,7 +254,7 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) size_t cmd_len = sizeof("func! ") + STRLEN(name); char *cmd = xmalloc(cmd_len); snprintf(cmd, cmd_len, "func! %s", name); - String func_body = nvim_command_output(cstr_as_string(cmd), &err); + String func_body = nvim_exec(cstr_as_string(cmd), true, &err); xfree(cmd); if (!ERROR_SET(&err)) { ADD(ctx->funcs, STRING_OBJ(func_body)); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 8479c15b40..496aecfb27 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3428,7 +3428,7 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) last_set.channel_id); return IObuff; case SID_STR: - return (char_u *)_(":source (no file)"); + return (char_u *)_("anonymous :source"); default: *should_free = true; return home_replace_save(NULL, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index dc2726709f..30c1373445 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -296,25 +296,23 @@ int do_cmdline_cmd(const char *cmd) DOCMD_NOWAIT|DOCMD_KEYTYPED); } -/* - * do_cmdline(): execute one Ex command line - * - * 1. Execute "cmdline" when it is not NULL. - * If "cmdline" is NULL, or more lines are needed, fgetline() is used. - * 2. Split up in parts separated with '|'. - * - * This function can be called recursively! - * - * flags: - * DOCMD_VERBOSE - The command will be included in the error message. - * DOCMD_NOWAIT - Don't call wait_return() and friends. - * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. - * DOCMD_KEYTYPED - Don't reset KeyTyped. - * DOCMD_EXCRESET - Reset the exception environment (used for debugging). - * DOCMD_KEEPLINE - Store first typed line (for repeating with "."). - * - * return FAIL if cmdline could not be executed, OK otherwise - */ +/// do_cmdline(): execute one Ex command line +/// +/// 1. Execute "cmdline" when it is not NULL. +/// If "cmdline" is NULL, or more lines are needed, fgetline() is used. +/// 2. Split up in parts separated with '|'. +/// +/// This function can be called recursively! +/// +/// flags: +/// DOCMD_VERBOSE - The command will be included in the error message. +/// DOCMD_NOWAIT - Don't call wait_return() and friends. +/// DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. +/// DOCMD_KEYTYPED - Don't reset KeyTyped. +/// DOCMD_EXCRESET - Reset the exception environment (used for debugging). +/// DOCMD_KEEPLINE - Store first typed line (for repeating with "."). +/// +/// @return FAIL if cmdline could not be executed, OK otherwise int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, /* argument for fgetline() */ int flags) -- cgit From a3b6c2a3dc5576db45fe4e893cfb8482af591c92 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 2 Dec 2019 00:46:46 -0800 Subject: API: rename nvim_execute_lua => nvim_exec_lua - We already find ourselves renaming nvim_execute_lua in tests and scripts, which suggests "exec" is the verb we actually want. - Add "exec" verb to `:help dev-api`. --- src/nvim/api/vim.c | 22 ++++++++++++++++------ src/nvim/eval.c | 2 +- src/nvim/lua/executor.c | 4 ++-- 3 files changed, 19 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5eca267d61..19601b6539 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -447,6 +447,15 @@ Object nvim_eval(String expr, Error *err) return rv; } +/// @deprecated Use nvim_exec_lua() instead. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) + FUNC_API_DEPRECATED_SINCE(7) + FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + /// Execute Lua code. Parameters (if any) are available as `...` inside the /// chunk. The chunk can return a value. /// @@ -459,8 +468,9 @@ Object nvim_eval(String expr, Error *err) /// or executing the Lua code. /// /// @return Return value of Lua code if present or NIL. -Object nvim_execute_lua(String code, Array args, Error *err) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +Object nvim_exec_lua(String code, Array args, Error *err) + FUNC_API_SINCE(7) + FUNC_API_REMOTE_ONLY { return executor_exec_lua_api(code, args, err); } @@ -1275,8 +1285,8 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) Array lines = string_to_array(data, crlf); ADD(args, ARRAY_OBJ(lines)); ADD(args, INTEGER_OBJ(phase)); - rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args, - err); + rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args, + err); if (ERROR_SET(err)) { draining = true; goto theend; @@ -2410,7 +2420,7 @@ Array nvim_get_proc_children(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); - Object o = nvim_execute_lua(s, a, err); + Object o = nvim_exec_lua(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { @@ -2456,7 +2466,7 @@ Object nvim_get_proc(Integer pid, Error *err) Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); - Object o = nvim_execute_lua(s, a, err); + Object o = nvim_exec_lua(s, a, err); api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray && o.data.array.size == 0) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1ab25b33b2..e99c41a915 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20527,7 +20527,7 @@ static hashtab_T *get_funccal_local_ht(void) return &get_funccal()->l_vars.dv_hashtab; } -/// Find the dict and hashtable used for a variable +/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// /// @param[in] name Variable name, possibly with scope prefix. /// @param[in] name_len Variable name length. diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5450f62f54..25f4be1c4d 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -798,9 +798,9 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, } } -/// Execute lua string +/// Execute Lua string /// -/// Used for nvim_execute_lua(). +/// Used for nvim_exec_lua(). /// /// @param[in] str String to execute. /// @param[in] args array of ... args -- cgit From 3beef8ee1ca9699d75b8877ec9b77493b58b0ed4 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Tue, 1 Oct 2019 23:14:00 +0900 Subject: defaults: set nostartofline Having the cursor change column can be surprising. Force startofline in functional and old tests. Remove the functional breakindent test, as it's a subset of the oldtest one. --- src/nvim/options.lua | 4 ++-- src/nvim/testdir/setup.vim | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/options.lua b/src/nvim/options.lua index d20174466d..903b7e982a 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2323,9 +2323,9 @@ return { full_name='startofline', abbreviation='sol', type='bool', scope={'global'}, vi_def=true, - vim=true, + vim=false, varname='p_sol', - defaults={if_true={vi=true}} + defaults={if_true={vi=false}} }, { full_name='statusline', abbreviation='stl', diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index ea28f328ae..d032c9a739 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -19,6 +19,7 @@ set sidescroll=0 set tags=./tags,tags set undodir^=. set wildoptions= +set startofline " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' -- cgit From bcd5fc9764bba22cbfa6c1f2bf8849d5c712c0d9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 3 Dec 2019 23:54:53 -0500 Subject: vim-patch:8.1.2384: test 48 is old style #11509 Problem: Test 48 is old style. Solution: Merge test cases into new style test. (Yegappan Lakshmanan, closes vim/vim#5307) https://github.com/vim/vim/commit/079119babe1cbba3f9234927e62fd75465f2d6b4 --- src/nvim/testdir/Makefile | 1 - src/nvim/testdir/test48.in | 82 --------------------- src/nvim/testdir/test48.ok | 23 ------ src/nvim/testdir/test_virtualedit.vim | 135 ++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 106 deletions(-) delete mode 100644 src/nvim/testdir/test48.in delete mode 100644 src/nvim/testdir/test48.ok (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 6bf070a7e2..b470dbf8f6 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -15,7 +15,6 @@ export TMPDIR := $(abspath Xtest-tmpdir) SCRIPTS_DEFAULT = \ test42.out \ - test48.out \ test64.out \ ifneq ($(OS),Windows_NT) diff --git a/src/nvim/testdir/test48.in b/src/nvim/testdir/test48.in deleted file mode 100644 index 1df5a3c46a..0000000000 --- a/src/nvim/testdir/test48.in +++ /dev/null @@ -1,82 +0,0 @@ -This is a test of 'virtualedit'. - -STARTTEST -:set noswf -:set ve=all -j-dgg -:" -:" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". -:" Repeating CTRL-N fixes it. (Mary Ellen Foster) -2/w -C -:" -:" Using "C" then then moves the last remaining character to the next -:" line. (Mary Ellen Foster) -j^/are -C are belong to vim -:" -:" When past the end of a line that ends in a single character "b" skips -:" that word. -^$15lbC7 -:" -:" Make sure 'i' works -$4li<-- should be 3 ' ' -:" -:" Make sure 'C' works -$4lC<-- should be 3 ' ' -:" -:" Make sure 'a' works -$4la<-- should be 4 ' ' -:" -:" Make sure 'A' works -$4lA<-- should be 0 ' ' -:" -:" Make sure 'D' works -$4lDi<-- 'D' should be intact -:" -:" Test for yank bug reported by Mark Waggoner. -:set ve=block -^2w3jyGp -:" -:" Test "r" beyond the end of the line -:set ve=all -/^"r" -$5lrxa<-- should be 'x' -:" -:" Test "r" on a tab -:" Note that for this test, 'ts' must be 8 (the default). -^5lrxA<-- should be ' x ' -:" -:" Test to make sure 'x' can delete control characters -:set display=uhex -^xxxxxxi[This line should contain only the text between the brackets.] -:set display= -:" -:" Test for ^Y/^E due to bad w_virtcol value, reported by -:" Roy . -^O3li4li4li <-- should show the name of a noted text editor -^o4li4li4li <-- and its version number-dd -:" -:" Test for yanking and pasting using the small delete register -gg/^foo -dewve"-p -:wq! test.out -ENDTEST -foo, bar -keyword keyw -all your base are belong to us -1 2 3 4 5 6 -'i' -'C' -'a' -'A' -'D' -this is a test -this is a test -this is a test -"r" -"r" -ab sd -abcv6efi.him0kl - - diff --git a/src/nvim/testdir/test48.ok b/src/nvim/testdir/test48.ok deleted file mode 100644 index 14cd9b12ec..0000000000 --- a/src/nvim/testdir/test48.ok +++ /dev/null @@ -1,23 +0,0 @@ -, foo -keyword keyword -all your base -are belong to vim -1 2 3 4 5 7 -'i' <-- should be 3 ' ' -'C' <-- should be 3 ' ' -'a' <-- should be 4 ' ' -'A'<-- should be 0 ' ' -'D' <-- 'D' should be intact -this is a test -this is a test -this is a test -"r" x<-- should be 'x' -"r" x <-- should be ' x ' -[This line should contain only the text between the brackets.] - v i m <-- should show the name of a noted text editor - 6 . 0 <-- and its version number - -a -a -a - diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 1e6b26a057..8f992f7501 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -82,3 +82,138 @@ func Test_edit_change() call assert_equal('x', getline(1)) bwipe! endfunc + +" Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". +" Repeating CTRL-N fixes it. (Mary Ellen Foster) +func Test_ve_completion() + new + set completeopt&vim + set virtualedit=all + exe "normal ikeyword keyw\C\" + call assert_equal('keyword keyword', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Using "C" then then moves the last remaining character to the next +" line. (Mary Ellen Foster) +func Test_ve_del_to_eol() + new + set virtualedit=all + call append(0, 'all your base are belong to us') + call search('are', 'w') + exe "normal C\are belong to vim" + call assert_equal(['all your base ', 'are belong to vim'], getline(1, 2)) + bwipe! + set virtualedit= +endfunc + +" When past the end of a line that ends in a single character "b" skips +" that word. +func Test_ve_b_past_eol() + new + set virtualedit=all + call append(0, '1 2 3 4 5 6') + normal gg^$15lbC7 + call assert_equal('1 2 3 4 5 7', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Make sure 'i', 'C', 'a', 'A' and 'D' works +func Test_ve_ins_del() + new + set virtualedit=all + call append(0, ["'i'", "'C'", "'a'", "'A'", "'D'"]) + call cursor(1, 1) + normal $4lix + call assert_equal("'i' x", getline(1)) + call cursor(2, 1) + normal $4lCx + call assert_equal("'C' x", getline(2)) + call cursor(3, 1) + normal $4lax + call assert_equal("'a' x", getline(3)) + call cursor(4, 1) + normal $4lAx + call assert_equal("'A'x", getline(4)) + call cursor(5, 1) + normal $4lDix + call assert_equal("'D' x", getline(5)) + bwipe! + set virtualedit= +endfunc + +" Test for yank bug reported by Mark Waggoner. +func Test_yank_block() + new + set virtualedit=block + call append(0, repeat(['this is a test'], 3)) + exe "normal gg^2w\3jy" + call assert_equal("a\na\na\n ", @") + bwipe! + set virtualedit= +endfunc + +" Test "r" beyond the end of the line +func Test_replace_after_eol() + new + set virtualedit=all + call append(0, '"r"') + normal gg$5lrxa + call assert_equal('"r" x', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Test "r" on a tab +" Note that for this test, 'ts' must be 8 (the default). +func Test_replace_on_tab() + new + set virtualedit=all + call append(0, "'r'\t") + normal gg^5lrxAy + call assert_equal("'r' x y", getline(1)) + bwipe! + set virtualedit= +endfunc + +" Test to make sure 'x' can delete control characters +func Test_ve_del_ctrl_chars() + new + set virtualedit=all + call append(0, "a\b\sd") + set display=uhex + normal gg^xxxxxxi[text] + set display= + call assert_equal('[text]', getline(1)) + bwipe! + set virtualedit= +endfunc + +" Test for ^Y/^E due to bad w_virtcol value, reported by +" Roy . +func Test_ins_copy_char() + new + set virtualedit=all + call append(0, 'abcv8efi.him2kl') + exe "normal gg^O\3li\\4li\\4li\ <--" + exe "normal j^o\4li\\4li\\4li\ <--" + call assert_equal(' v i m <--', getline(1)) + call assert_equal(' 8 . 2 <--', getline(3)) + bwipe! + set virtualedit= +endfunc + +" Test for yanking and pasting using the small delete register +func Test_yank_paste_small_del_reg() + new + set virtualedit=all + call append(0, "foo, bar") + normal ggdewve"-p + call assert_equal(', foo', getline(1)) + bwipe! + set virtualedit= +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From ed424655bef3169dc5452c5a8212e250dc483f9e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 5 Dec 2019 02:04:19 -0500 Subject: vim-patch:8.1.2385: open cmdline window with feedkeys() #11516 Problem: Opening cmdline window with feedkeys() does not work. (Yegappan Lakshmanan) Solution: Recognize K_CMDWIN also when ex_normal_busy is set. https://github.com/vim/vim/commit/85db5475982e166ec5bb1c8c9a5c8bf062d49ed1 --- src/nvim/ex_getln.c | 4 +++- src/nvim/testdir/test_cmdline.vim | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 73353f84cf..8065d764b9 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -878,7 +878,9 @@ static int command_line_execute(VimState *state, int key) } if (s->c == cedit_key || s->c == K_CMDWIN) { - if (ex_normal_busy == 0 && got_int == false) { + // TODO(vim): why is ex_normal_busy checked here? + if ((s->c == K_CMDWIN || ex_normal_busy == 0) + && got_int == false) { // Open a window to edit the command line (and history). s->c = open_cmdwin(); s->some_key_typed = true; diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 262ea11eb6..9c3c33a943 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -750,3 +750,8 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc + +func Test_cmdwin_feedkeys() + " This should not generate E488 + call feedkeys("q:\", 'x') +endfunc -- cgit From 0e6c6261e1b5a518995f53ca3898a68b9bb02012 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sat, 7 Dec 2019 03:34:02 -0800 Subject: Fix access on vim.wo (#11517) * Add more tests for vim.wo --- src/nvim/lua/vim.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 090869c68e..e7c5458102 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -374,9 +374,9 @@ do if winnr == nil and type(k) == "number" then return new_win_opt_accessor(k) end - return a.nvim_win_get_option(winnr or nil, k) + return a.nvim_win_get_option(winnr or 0, k) end - local function set(k, v) return a.nvim_win_set_option(winnr or nil, k, v) end + local function set(k, v) return a.nvim_win_set_option(winnr or 0, k, v) end return make_meta_accessor(get, set) end vim.wo = new_win_opt_accessor(nil) -- cgit From 7d8f32338f4bbb4b2addeacc17aceca9f73fe644 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 6 Dec 2019 20:45:17 -0500 Subject: vim-patch:8.1.2402: typos and other small things Problem: Typos and other small things. Solution: Small fixes. https://github.com/vim/vim/commit/f48ee3c28488f7c361732316f905ac420b3d8087 --- src/nvim/message.c | 4 ++-- src/nvim/testdir/shared.vim | 3 +++ src/nvim/testdir/test49.vim | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/message.c b/src/nvim/message.c index 03cbe8ec18..067f1c3283 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1378,7 +1378,7 @@ static void msg_home_replace_attr(char_u *fname, int attr) /* * Output 'len' characters in 'str' (including NULs) with translation - * if 'len' is -1, output upto a NUL character. + * if 'len' is -1, output up to a NUL character. * Use attributes 'attr'. * Return the number of characters it takes on the screen. */ @@ -1501,7 +1501,7 @@ void msg_make(char_u *arg) } } -/// Output the string 'str' upto a NUL character. +/// Output the string 'str' up to a NUL character. /// Return the number of characters it takes on the screen. /// /// If K_SPECIAL is encountered, then it is taken in conjunction with the diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index b0b59db686..3875ffc056 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -314,6 +314,9 @@ func RunVimPiped(before, after, arguments, pipecmd) let args .= ' -S Xafter.vim' endif + " Optionally run Vim under valgrind + " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd + exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments if len(a:before) > 0 diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 837e55ebca..fc79f57d2e 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -178,7 +178,7 @@ endif " next Xpath value. No new Xnext value is prepared. The argument " should be 2^(n-1) for the nth Xloop command inside the loop. " If the loop has only one Xloop command, the argument can be -" ommitted (default: 1). +" omitted (default: 1). " " - Use XloopNEXT before ":continue" and ":endwhile". This computes a new " Xnext value for the next execution of the loop by multiplying the old -- cgit From 39094b3faedde9601160806901941e4808925410 Mon Sep 17 00:00:00 2001 From: butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Tue, 10 Dec 2019 00:56:56 -0800 Subject: jumplist: browser-style (or 'tagstack') navigation #11530 Traditionally, when navigating to a specific location from the middle of the jumplist results in shifting the current location to the bottom of the list and adding the new location after it. This behavior is not desireable to all users--see, for example https://vi.stackexchange.com/questions/18344/how-to-change-jumplist-behavior. Here, another jumplist behavior is introduced. When jumpoptions (a new option set added here) includes stack, the jumplist behaves like the tagstack or like history in a web browser. That is, when navigating to a location from the middle of the jumplist 2 first 1 second 0 third <-- current location 1 fourth 2 fifth to a new location the locations after the current location in the jump list are discarded 2 first 1 second 0 third <-- current location The result is that when moving forward from that location, the new location will be appended to the jumplist: 3 first 2 second 1 third 0 new If the new location is the same new == second as some previous (but not immediately prior) entry in the jumplist, 2 first 1 second 0 third <-- current location 1 fourth 2 fifth both occurrences preserved 3 first 2 second 1 third 0 second (new) when moving forward from that location. It would be desireable to go farther and, when the new location is the same as the location that is currently next in the jumplist, new == fourth make the result of navigating to the new location by jumping (e.g. 50gg) be the same as moving forward in the jumplist 2 first 1 second 0 third 1 new <-- current location 2 fifth and simply increment the jumplist index. That change is NOT part of this patch because it would require passing the new cursor location to the function (setpcmark) from all of its callees. That in turn would require those callees to know *before* calling what the new cursor location is, which do they do not currently. --- src/nvim/mark.c | 27 ++++++++++++++++++++++++--- src/nvim/option.c | 5 +++++ src/nvim/option_defs.h | 6 ++++++ src/nvim/options.lua | 8 ++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e5070f23ff..93bc497cf0 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -178,6 +178,16 @@ void setpcmark(void) curwin->w_pcmark.lnum = 1; } + if (jop_flags & JOP_STACK) { + // If we're somewhere in the middle of the jumplist discard everything + // after the current index. + if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) { + // Discard the rest of the jumplist by cutting the length down to + // contain nothing beyond the current index. + curwin->w_jumplistlen = curwin->w_jumplistidx + 1; + } + } + /* If jumplist is full: remove oldest entry */ if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; @@ -1204,7 +1214,20 @@ void cleanup_jumplist(win_T *wp, bool checktail) break; } } - if (i >= wp->w_jumplistlen) { // no duplicate + bool mustfree; + if (i >= wp->w_jumplistlen) { // not duplicate + mustfree = false; + } else if (i > from + 1) { // non-adjacent duplicate + // When the jump options include "stack", duplicates are only removed from + // the jumplist when they are adjacent. + mustfree = !(jop_flags & JOP_STACK); + } else { // adjacent duplicate + mustfree = true; + } + + if (mustfree) { + xfree(wp->w_jumplist[from].fname); + } else { if (to != from) { // Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because // this way valgrind complains about overlapping source and destination @@ -1212,8 +1235,6 @@ void cleanup_jumplist(win_T *wp, bool checktail) wp->w_jumplist[to] = wp->w_jumplist[from]; } to++; - } else { - xfree(wp->w_jumplist[from].fname); } } if (wp->w_jumplistidx == wp->w_jumplistlen) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 68ab310329..e48ed201e8 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2184,6 +2184,7 @@ static void didset_options(void) (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); + (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true); (void)spell_check_msm(); (void)spell_check_sps(); (void)compile_cap_prog(curwin->w_s); @@ -2632,6 +2633,10 @@ did_set_string_option( if (strcmp((char *)(*varp), HIGHLIGHT_INIT) != 0) { errmsg = e_unsupportedoption; } + } else if (varp == &p_jop) { // 'jumpoptions' + if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { + errmsg = e_invarg; + } } else if (gvarp == &p_nf) { // 'nrformats' if (check_opt_strings(*varp, p_nf_values, true) != OK) { errmsg = e_invarg; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index c6e3f71016..4f9f32794b 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -473,6 +473,12 @@ EXTERN char_u *p_isf; // 'isfname' EXTERN char_u *p_isi; // 'isident' EXTERN char_u *p_isp; // 'isprint' EXTERN int p_js; // 'joinspaces' +EXTERN char_u *p_jop; // 'jumpooptions' +EXTERN unsigned jop_flags; +#ifdef IN_OPTION_C +static char *(p_jop_values[]) = { "stack", NULL }; +#endif +#define JOP_STACK 0x01 EXTERN char_u *p_kp; // 'keywordprg' EXTERN char_u *p_km; // 'keymodel' EXTERN char_u *p_langmap; // 'langmap' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 1e7105219f..93bfc1c0b1 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1299,6 +1299,14 @@ return { varname='p_js', defaults={if_true={vi=true}} }, + { + full_name='jumpoptions', abbreviation='jop', + type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, + varname='p_jop', + vim=true, + defaults={if_true={vim=''}} + }, { full_name='keymap', abbreviation='kmp', type='string', scope={'buffer'}, -- cgit From 3383f1ce26eddcd0311f5b5ab340cf101b704ec6 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 11 Dec 2019 02:21:39 -0800 Subject: PVS/V1049: fix numerous "DEFINE_FUNC_ATTRIBUTES" warnings #11544 PVS erroneously flags our DEFINE_FUNC_ATTRIBUTES guard: V1049 The 'DEFINE_FUNC_ATTRIBUTES' include guard is already defined in the 'lang.h.generated.h' header. The 'profile.h.generated.h' header will be excluded from compilation. To satisfy PVS, just remove the `#ifndef` check. It's not needed anyway: C allows to redundantly #define a macro. https://gcc.gnu.org/onlinedocs/cpp/Undefining-and-Redefining-Macros.html > if an identifier which is currently a macro is redefined, then the new > definition must be effectively the same as the old one > ... > If a macro is redefined with a definition that is not effectively the > same as the old one, the preprocessor issues a warning and changes the > macro to use the new definition. If the new definition is effectively > the same, the redefinition is silently ignored. This allows, for > instance, two different headers to define a common macro. The > preprocessor will only complain if the definitions do not match. --- src/nvim/generators/gen_declarations.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index ad44613f42..0782c8115d 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -207,9 +207,7 @@ preproc_f:close() local header = [[ -#ifndef DEFINE_FUNC_ATTRIBUTES -# define DEFINE_FUNC_ATTRIBUTES -#endif +#define DEFINE_FUNC_ATTRIBUTES #include "nvim/func_attr.h" #undef DEFINE_FUNC_ATTRIBUTES ]] -- cgit From 19b6237087ebcf45427ceb6943d23ce33b39567f Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Fri, 9 Nov 2018 23:57:00 +0900 Subject: jobstart now supports env/clear_env to modify the environment of the launched job. --- src/nvim/channel.c | 39 ++++++++++++++++++++++++++++++- src/nvim/eval.c | 52 ++++++++++++++++++++++++++++++++++++++---- src/nvim/event/libuv_process.c | 2 +- src/nvim/event/process.h | 1 + src/nvim/os/env.c | 15 ++++++++---- 5 files changed, 99 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index f9102fa0e2..f8b480adcb 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -272,11 +272,44 @@ static void close_cb(Stream *stream, void *data) channel_decref(data); } +static inline void free_env(char **env) +{ + if (!env) { return; } + for (char **it = env; *it; it++) { + XFREE_CLEAR(*it); + } + xfree(env); +} + + +/// Starts a job and returns the associated channel +/// +/// @param[in] argv Arguments vector specifying the command to run, +/// NULL-terminated +/// @param[in] on_stdout Callback to read the job's stdout +/// @param[in] on_stderr Callback to read the job's stderr +/// @param[in] on_exit Callback to receive the job's exit status +/// @param[in] pty True if the job should run attached to a pty +/// @param[in] rpc True to communicate with the job using msgpack-rpc, +/// `on_stdout` is ignored +/// @param[in] detach True if the job should not be killed when nvim exits, +/// ignored if `pty` is true +/// @param[in] cwd Initial working directory for the job. Nvim's working +/// directory if `cwd` is NULL +/// @param[in] pty_width Width of the pty, ignored if `pty` is false +/// @param[in] pty_height Height of the pty, ignored if `pty` is false +/// @param[in] term_name `$TERM` for the pty +/// @param[in] env Nvim's configured environment is used if this is NULL, +/// otherwise defines all environment variables +/// @param[out] status_out 0 for invalid arguments, > 0 for the channel id, +/// < 0 if the job can't start +/// +/// @returns [allocated] channel 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) + char *term_name, char **env, varnumber_T *status_out) { assert(cwd == NULL || os_isdir_executable(cwd)); @@ -314,6 +347,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, proc->events = chan->events; proc->detach = detach; proc->cwd = cwd; + proc->env = env; char *cmd = xstrdup(proc->argv[0]); bool has_out, has_err; @@ -328,6 +362,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (status) { EMSG3(_(e_jobspawn), os_strerror(status), cmd); xfree(cmd); + free_env(proc->env); if (proc->type == kProcessTypePty) { xfree(chan->stream.pty.term_name); } @@ -336,6 +371,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, return NULL; } xfree(cmd); + free_env(proc->env); + wstream_init(&proc->in, 0); if (has_out) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e99c41a915..00a0aecf4b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12577,6 +12577,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool executable = true; char **argv = tv_to_argv(&argvars[0], NULL, &executable); + char **env = NULL; if (!argv) { rettv->vval.v_number = executable ? 0 : -1; return; // Did error message in tv_to_argv. @@ -12594,6 +12595,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool detach = false; bool rpc = false; bool pty = false; + bool clear_env = false; CallbackReader on_stdout = CALLBACK_READER_INIT, on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; @@ -12604,6 +12606,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) detach = tv_dict_get_number(job_opts, "detach") != 0; rpc = tv_dict_get_number(job_opts, "rpc") != 0; pty = tv_dict_get_number(job_opts, "pty") != 0; + clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; if (pty && rpc) { EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); shell_free_argv(argv); @@ -12620,6 +12623,47 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } + dictitem_T *item; + item = tv_dict_find(job_opts, S_LEN("env")); + if (item) { + size_t custom_env_size = (size_t)tv_dict_len(item->di_tv.vval.v_dict); + size_t i = 0; + size_t env_size = 0; + if (item->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + return; + } + + if (clear_env) { + // + 1 for last null entry + env = xmalloc((custom_env_size + 1) * sizeof(*env)); + env_size = 0; + } else { + char **genv = os_getfullenv(); + for (env = genv; *env; env++) { + env_size++; + } + env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); + + for (i = 0; i < env_size; i++) { + env[i] = xstrdup(genv[i]); + } + } + assert(env); // env must be allocated at this point + + TV_DICT_ITER(item->di_tv.vval.v_dict, var, { + const char *str = tv_get_string(&var->di_tv); + assert(str); + size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + env[i] = xmalloc(len); + snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); + i++; + }); + + // must be null terminated + env[env_size + custom_env_size] = NULL; + } + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); @@ -12637,8 +12681,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, - rpc, detach, cwd, width, height, term_name, - &rettv->vval.v_number); + rpc, detach, cwd, width, height, + term_name, env, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); } @@ -14933,7 +14977,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT, CALLBACK_READER_INIT, CALLBACK_NONE, - false, true, false, NULL, 0, 0, NULL, + false, true, false, NULL, 0, 0, NULL, NULL, &rettv->vval.v_number); if (chan) { channel_create_event(chan, NULL); @@ -18320,7 +18364,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, true, false, false, cwd, term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), + xstrdup("xterm-256color"), NULL, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 63efee59a8..37b9f73ba4 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -41,7 +41,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; - uvproc->uvopts.env = NULL; // Inherits the parent (nvim) env. + uvproc->uvopts.env = proc->env; uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; uvproc->uvstdio[0].flags = UV_IGNORE; diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index ef9d953ab7..b677b80bfe 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -23,6 +23,7 @@ struct process { uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; + char **env; Stream in, out, err; process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index eb86cb8ac7..15153e9bd7 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -184,7 +184,7 @@ int os_unsetenv(const char *name) return r == 0 ? 0 : -1; } -char *os_getenvname_at_index(size_t index) +char **os_getfullenv(void) { #ifdef _WIN32 wchar_t *env = GetEnvironmentStringsW(); @@ -224,13 +224,20 @@ char *os_getenvname_at_index(size_t index) # else extern char **environ; # endif - // Check if index is inside the environ array and is not the last element. + return environ; +} + +char *os_getenvname_at_index(size_t index) +{ + char **env = os_getfullenv(); + // check if index is inside the environ array for (size_t i = 0; i <= index; i++) { - if (environ[i] == NULL) { + if (env[i] == NULL) { return NULL; } } - char *str = environ[index]; + char *str = env[index]; + assert(str != NULL); size_t namesize = 0; while (str[namesize] != '=' && str[namesize] != NUL) { namesize++; -- cgit From 6dc10057876d1bf75c5cf1ea45cb4312160f13f0 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Sun, 2 Jun 2019 14:36:17 -0400 Subject: Add os_getfullenv_size/os_copyfullenv --- src/nvim/channel.c | 13 +---- src/nvim/eval.c | 38 ++++++++------ src/nvim/os/env.c | 152 +++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 146 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index f8b480adcb..2bb568f025 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -272,15 +272,6 @@ static void close_cb(Stream *stream, void *data) channel_decref(data); } -static inline void free_env(char **env) -{ - if (!env) { return; } - for (char **it = env; *it; it++) { - XFREE_CLEAR(*it); - } - xfree(env); -} - /// Starts a job and returns the associated channel /// @@ -362,7 +353,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (status) { EMSG3(_(e_jobspawn), os_strerror(status), cmd); xfree(cmd); - free_env(proc->env); + os_free_fullenv(proc->env); if (proc->type == kProcessTypePty) { xfree(chan->stream.pty.term_name); } @@ -371,7 +362,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, return NULL; } xfree(cmd); - free_env(proc->env); + os_free_fullenv(proc->env); wstream_init(&proc->in, 0); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 00a0aecf4b..e498253db1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8716,18 +8716,25 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); - for (int i = 0; ; i++) { - // TODO(justinmk): use os_copyfullenv from #7202 ? - char *envname = os_getenvname_at_index((size_t)i); - if (envname == NULL) { - break; - } - const char *value = os_getenv(envname); + size_t env_size = os_get_fullenv_size(); + char **env = xmalloc(sizeof(*env) * (env_size + 1)); + env[env_size] = NULL; + + os_copy_fullenv(env, env_size); + + for (size_t i = 0; i < env_size; i++) { + const char * str = env[i]; + const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), + '='); + assert(end != NULL); + ptrdiff_t len = end - str; + assert(len > 0); + const char * value = str + len + 1; tv_dict_add_str(rettv->vval.v_dict, - (char *)envname, STRLEN((char *)envname), - value == NULL ? "" : value); - xfree(envname); + str, len, + value); } + os_free_fullenv(env); } /* @@ -12639,15 +12646,12 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) env = xmalloc((custom_env_size + 1) * sizeof(*env)); env_size = 0; } else { - char **genv = os_getfullenv(); - for (env = genv; *env; env++) { - env_size++; - } + env_size = os_get_fullenv_size(); + env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); - for (i = 0; i < env_size; i++) { - env[i] = xstrdup(genv[i]); - } + os_copy_fullenv(env, env_size); + i = env_size; } assert(env); // env must be allocated at this point diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 15153e9bd7..ac442ee2e8 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -184,39 +184,139 @@ int os_unsetenv(const char *name) return r == 0 ? 0 : -1; } -char **os_getfullenv(void) +/// Returns number of variables in the current environment variables block +size_t os_get_fullenv_size(void) { + size_t len = 0; #ifdef _WIN32 - wchar_t *env = GetEnvironmentStringsW(); - if (!env) { + wchar_t *envstrings = GetEnvironmentStringsW(); + wchar_t *p = envstrings; + size_t l; + if (!envstrings) { + return len; + } + // GetEnvironmentStringsW() result has this format: + // var1=value1\0var2=value2\0...varN=valueN\0\0 + while ((l = wcslen(p)) != 0) { + p += l + 1; + len++; + } + + FreeEnvironmentStringsW(envstrings); +#else +# if defined(HAVE__NSGETENVIRON) + char **environ = *_NSGetEnviron(); +# else + extern char **environ; +# endif + + while (environ[len] != NULL) { + len++; + } + +#endif + return len; +} + +void os_free_fullenv(char **env) +{ + if (!env) { return; } + for (char **it = env; *it; it++) { + XFREE_CLEAR(*it); + } + xfree(env); +} + +/// Copies the current environment variables into the given array, `env`. Each +/// array element is of the form "NAME=VALUE". +/// Result must be freed by the caller. +/// +/// @param[out] env array to populate with environment variables +/// @param env_size size of `env`, @see os_fullenv_size +void os_copy_fullenv(char **env, size_t env_size) +{ +#ifdef _WIN32 + wchar_t *envstrings = GetEnvironmentStringsW(); + if (!envstrings) { + return; + } + wchar_t *p = envstrings; + size_t i = 0; + size_t l; + // GetEnvironmentStringsW() result has this format: + // var1=value1\0var2=value2\0...varN=valueN\0\0 + while ((l = wcslen(p)) != 0 && i < env_size) { + char *utf8_str; + int conversion_result = utf16_to_utf8(p, -1, &utf8_str); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %d", conversion_result); + break; + } + p += l + 1; + + env[i] = utf8_str; + i++; + } + + FreeEnvironmentStringsW(envstrings); +#else +# if defined(HAVE__NSGETENVIRON) + char **environ = *_NSGetEnviron(); +# else + extern char **environ; +# endif + + size_t i = 0; + while (environ[i] != NULL && i < env_size) { + env[i] = xstrdup(environ[i]); + i++; + } +#endif +} + +/// Copy value of the environment variable at `index` in the current +/// environment variables block. +/// Result must be freed by the caller. +/// +/// @param index nth item in environment variables block +/// @return [allocated] environment variable's value, or NULL +char *os_getenvname_at_index(size_t index) +{ +#ifdef _WIN32 + wchar_t *envstrings = GetEnvironmentStringsW(); + if (!envstrings) { return NULL; } + wchar_t *p = envstrings; char *name = NULL; - size_t current_index = 0; + size_t i = 0; + size_t l; // GetEnvironmentStringsW() result has this format: // var1=value1\0var2=value2\0...varN=valueN\0\0 - for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) { - if (index == current_index) { + while ((l = wcslen(p)) != 0 && i <= index) { + if (i == index) { char *utf8_str; - int conversion_result = utf16_to_utf8(it, -1, &utf8_str); + int conversion_result = utf16_to_utf8(p, -1, &utf8_str); if (conversion_result != 0) { EMSG2("utf16_to_utf8 failed: %d", conversion_result); break; } - size_t namesize = 0; - while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) { - namesize++; - } - name = (char *)vim_strnsave((char_u *)utf8_str, namesize); + + const char * const end = strchr(utf8_str, '='); + assert(end != NULL); + ptrdiff_t len = end - utf8_str; + assert(len > 0); + name = xstrndup(utf8_str, (size_t)len); xfree(utf8_str); break; } - if (*it == L'\0') { - current_index++; - } + + // Advance past the name and NUL + p += l + 1; + i++; } - FreeEnvironmentStringsW(env); + FreeEnvironmentStringsW(envstrings); return name; #else # if defined(HAVE__NSGETENVIRON) @@ -224,26 +324,20 @@ char **os_getfullenv(void) # else extern char **environ; # endif - return environ; -} -char *os_getenvname_at_index(size_t index) -{ - char **env = os_getfullenv(); // check if index is inside the environ array for (size_t i = 0; i <= index; i++) { - if (env[i] == NULL) { + if (environ[i] == NULL) { return NULL; } } - char *str = env[index]; + char *str = environ[index]; assert(str != NULL); - size_t namesize = 0; - while (str[namesize] != '=' && str[namesize] != NUL) { - namesize++; - } - char *name = (char *)vim_strnsave((char_u *)str, namesize); - return name; + const char * const end = strchr(str, '='); + assert(end != NULL); + ptrdiff_t len = end - str; + assert(len > 0); + return xstrndup(str, (size_t)len); #endif } -- cgit From 39963c6a04afc417a821b2255a5caea4b7955d7d Mon Sep 17 00:00:00 2001 From: James McCoy Date: Wed, 4 Dec 2019 08:01:05 -0500 Subject: os_getenvname_at_index: Handle Windows env vars whose name starts with = --- src/nvim/os/env.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index ac442ee2e8..360609c50d 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -302,7 +302,10 @@ char *os_getenvname_at_index(size_t index) break; } - const char * const end = strchr(utf8_str, '='); + // Some Windows env vars start with =, so skip over that to find the + // separator between name/value + const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0), + '='); assert(end != NULL); ptrdiff_t len = end - utf8_str; assert(len > 0); -- cgit From 914555b14c073e03396beaa05f2a1dc3af3b4a33 Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Thu, 12 Dec 2019 09:37:23 +0100 Subject: version.c: update [ci skip] #11415 vim-patch:8.1.0121: crash when using ballooneval related to 'vartabstop' vim-patch:8.1.0132: lua tests are old style vim-patch:8.1.0134: Lua interface does not support funcref vim-patch:8.1.0758: font number is always one instead of the actual vim-patch:8.1.0808: MS-Windows: build error with GUI vim-patch:8.1.1767: FEAT_SESSION defined separately vim-patch:8.1.1958: old style comments taking up space vim-patch:8.1.2327: cannot build with Hangul input vim-patch:8.1.2328: a few hangul input pieces remain vim-patch:8.1.2352: CI doesn't cover FreeBSD vim-patch:8.1.2354: Cirrus CI runs on another repository vim-patch:8.1.2359: cannot build without FEAT_FLOAT vim-patch:8.1.2370: build problems on VMS vim-patch:8.1.2372: VMS: failing realloc leaks memory vim-patch:8.1.2374: unused parts of libvterm are included vim-patch:8.1.2376: preprocessor indents are incorrect vim-patch:8.1.2383: using old C style comments vim-patch:8.1.2404: channel test fails under valgrind vim-patch:8.1.2407: proto files, dependencies and build instructions outdated vim-patch:8.1.2409: creating the distribution doesn't work as documented vim-patch:8.1.2410: MS-Windows: test_iminsert fails without IME support vim-patch:8.1.2413: cannot update ex_cmdidxs.h on MS-Windows vim-patch:8.1.2414: MS-Windows: properties dialog box shows wrong character vim-patch:8.1.2417: MinGW/Cygwin build does not clean up all files vim-patch:8.1.2423: MS-Windows properties shows version as "8, 1, 0" --- src/nvim/version.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index f678b743c2..4cadc9fd6c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -125,7 +125,7 @@ static const int included_patches[] = { 1796, 1795, // 1794, - // 1793, + 1793, 1792, 1791, 1790, @@ -469,7 +469,7 @@ static const int included_patches[] = { 1452, 1451, 1450, - // 1449, + 1449, 1448, 1447, 1446, @@ -513,7 +513,7 @@ static const int included_patches[] = { 1408, 1407, 1406, - // 1405, + 1405, 1404, 1403, 1402, -- cgit From 91b313a904c039a9b6a53a7afc9f66e67a1e12fc Mon Sep 17 00:00:00 2001 From: James McCoy Date: Thu, 12 Dec 2019 07:26:39 -0500 Subject: Add negative test for type of job's env option --- src/nvim/eval.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e498253db1..c14b7f513d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12630,17 +12630,18 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - dictitem_T *item; - item = tv_dict_find(job_opts, S_LEN("env")); - if (item) { - size_t custom_env_size = (size_t)tv_dict_len(item->di_tv.vval.v_dict); - size_t i = 0; - size_t env_size = 0; - if (item->di_tv.v_type != VAR_DICT) { + dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env) { + if (job_env->di_tv.v_type != VAR_DICT) { EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); return; } + size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); + size_t i = 0; + size_t env_size = 0; + if (clear_env) { // + 1 for last null entry env = xmalloc((custom_env_size + 1) * sizeof(*env)); @@ -12655,7 +12656,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } assert(env); // env must be allocated at this point - TV_DICT_ITER(item->di_tv.vval.v_dict, var, { + TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { const char *str = tv_get_string(&var->di_tv); assert(str); size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; -- cgit From 251b20e5334e1ff8af7fdd37ca1770ad485f031b Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Mon, 16 Dec 2019 14:08:55 -0500 Subject: Add support for the pum_getpos() API (#11562) Add support for the pum_getpos() API --- src/nvim/eval.c | 7 +++++++ src/nvim/eval.lua | 1 + src/nvim/testdir/test_popup.vim | 42 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a556f0bbbe..4c76b1b2e8 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -13972,6 +13972,13 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "pum_getpos()" function +static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + pum_set_event_info(rettv->vval.v_dict); +} + /* * "pumvisible()" function */ diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 0ae250e626..efeac70816 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -243,6 +243,7 @@ return { pow={args=2}, prevnonblank={args=1}, printf={args=varargs(1)}, + pum_getpos={}, pumvisible={}, py3eval={args=1}, pyeval={args=1}, diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 8083672808..9db6112eeb 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -918,6 +918,20 @@ func Test_popup_complete_info_02() bwipe! endfunc +func Test_popup_complete_info_no_pum() + new + call assert_false( pumvisible() ) + let no_pum_info = complete_info() + let d = { + \ 'mode': '', + \ 'pum_visible': 0, + \ 'items': [], + \ 'selected': -1, + \ } + call assert_equal( d, complete_info() ) + bwipe! +endfunc + func Test_CompleteChanged() new call setline(1, ['foo', 'bar', 'foobar', '']) @@ -952,4 +966,32 @@ func Test_CompleteChanged() bw! endfunc +function! GetPumPosition() + call assert_true( pumvisible() ) + let g:pum_pos = pum_getpos() + return '' +endfunction + +func Test_pum_getpos() + new + inoremap =GetPumPosition() + setlocal completefunc=UserDefinedComplete + + let d = { + \ 'height': 5, + \ 'width': 15, + \ 'row': 1, + \ 'col': 0, + \ 'size': 5, + \ 'scrollbar': v:false, + \ } + call feedkeys("i\\\", 'tx') + call assert_equal(d, g:pum_pos) + + call assert_false( pumvisible() ) + call assert_equal( {}, pum_getpos() ) + bw! + unlet g:pum_pos +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 63cd5dd0ae3e079e0b69382f905637978a1e2300 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 13 Dec 2019 21:17:57 -0500 Subject: vim-patch:8.2.0002: "dj" only deletes first line of closed fold Problem: "dj" only deletes first line of closed fold. Solution: Adjust last line of operator for linewise motion. (closes vim/vim#5354) https://github.com/vim/vim/commit/3b68123cd271fb781da4055cf1a1cf52f4fee6a5 --- src/nvim/normal.c | 4 +++- src/nvim/testdir/test_fold.vim | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index b9dbcc6805..9c5434a0dd 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1553,7 +1553,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (!VIsual_active) { if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) oap->start.col = 0; - if ((curwin->w_cursor.col > 0 || oap->inclusive) + if ((curwin->w_cursor.col > 0 + || oap->inclusive + || oap->motion_type == kMTLineWise) && hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 324f3f8cf2..56ed543d4b 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -769,3 +769,28 @@ func Test_fold_delete_with_marker_and_whichwrap() set fdm& ww& bwipe! endfunc + +func Test_fold_delete_first_line() + new + call setline(1, [ + \ '" x {{{1', + \ '" a', + \ '" aa', + \ '" x {{{1', + \ '" b', + \ '" bb', + \ '" x {{{1', + \ '" c', + \ '" cc', + \ ]) + set foldmethod=marker + 1 + normal dj + call assert_equal([ + \ '" x {{{1', + \ '" c', + \ '" cc', + \ ], getline(1,'$')) + bwipe! + set foldmethod& +endfunc -- cgit From 06f37709e7c1190da126478992d8bd9f7fb0c5b4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 14 Dec 2019 20:57:31 -0500 Subject: vim-patch:8.2.0008: test72 is old style Problem: Test72 is old style. Solution: Convert to new style test. (Yegappan Lakshmanan, closes vim/vim#5362) https://github.com/vim/vim/commit/3e2d1c8cd61ca073e680f3b221ce887e05ba39cf --- src/nvim/testdir/test_undo.vim | 187 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 86674889ef..e8aaecedc3 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -443,3 +443,190 @@ func Test_undo_0() bwipe! endfunc + +" Tests for the undo file +" Explicitly break changes up in undo-able pieces by setting 'undolevels'. +func Test_undofile_2() + set undolevels=100 undofile + edit Xtestfile + call append(0, 'this is one line') + call cursor(1, 1) + + " first a simple one-line change. + set undolevels=100 + s/one/ONE/ + set undolevels=100 + write + bwipe! + edit Xtestfile + undo + call assert_equal('this is one line', getline(1)) + + " change in original file fails check + set noundofile + edit! Xtestfile + s/line/Line/ + write + set undofile + bwipe! + edit Xtestfile + undo + call assert_equal('this is ONE Line', getline(1)) + + " add 10 lines, delete 6 lines, undo 3 + set undofile + call setbufline(0, 1, ['one', 'two', 'three', 'four', 'five', 'six', + \ 'seven', 'eight', 'nine', 'ten']) + set undolevels=100 + normal 3Gdd + set undolevels=100 + normal dd + set undolevels=100 + normal dd + set undolevels=100 + normal dd + set undolevels=100 + normal dd + set undolevels=100 + normal dd + set undolevels=100 + write + bwipe! + edit Xtestfile + normal uuu + call assert_equal(['one', 'two', 'six', 'seven', 'eight', 'nine', 'ten'], + \ getline(1, '$')) + + " Test that reading the undofiles when setting undofile works + set noundofile undolevels=0 + exe "normal i\n" + undo + edit! Xtestfile + set undofile undolevels=100 + normal uuuuuu + call assert_equal(['one', 'two', 'three', 'four', 'five', 'six', 'seven', + \ 'eight', 'nine', 'ten'], getline(1, '$')) + + bwipe! + call delete('Xtestfile') + let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~' + call delete(ufile) + set undofile& undolevels& +endfunc + +" Test 'undofile' using a file encrypted with 'zip' crypt method +func Test_undofile_cryptmethod_zip() + throw 'skipped: Nvim does not support cryptmethod' + edit Xtestfile + set undofile cryptmethod=zip + call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']) + call cursor(5, 1) + + set undolevels=100 + normal kkkdd + set undolevels=100 + normal dd + set undolevels=100 + normal dd + set undolevels=100 + " encrypt the file using key 'foobar' + call feedkeys("foobar\nfoobar\n") + X + write! + bwipe! + + call feedkeys("foobar\n") + edit Xtestfile + set key= + normal uu + call assert_equal(['monday', 'wednesday', 'thursday', 'friday', ''], + \ getline(1, '$')) + + bwipe! + call delete('Xtestfile') + let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~' + call delete(ufile) + set undofile& undolevels& cryptmethod& +endfunc + +" Test 'undofile' using a file encrypted with 'blowfish' crypt method +func Test_undofile_cryptmethod_blowfish() + throw 'skipped: Nvim does not support cryptmethod' + edit Xtestfile + set undofile cryptmethod=blowfish + call append(0, ['jan', 'feb', 'mar', 'apr', 'jun']) + call cursor(5, 1) + + set undolevels=100 + exe 'normal kk0ifoo ' + set undolevels=100 + normal dd + set undolevels=100 + exe 'normal ibar ' + set undolevels=100 + " encrypt the file using key 'foobar' + call feedkeys("foobar\nfoobar\n") + X + write! + bwipe! + + call feedkeys("foobar\n") + edit Xtestfile + set key= + call search('bar') + call assert_equal('bar apr', getline('.')) + undo + call assert_equal('apr', getline('.')) + undo + call assert_equal('foo mar', getline('.')) + undo + call assert_equal('mar', getline('.')) + + bwipe! + call delete('Xtestfile') + let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~' + call delete(ufile) + set undofile& undolevels& cryptmethod& +endfunc + +" Test 'undofile' using a file encrypted with 'blowfish2' crypt method +func Test_undofile_cryptmethod_blowfish2() + throw 'skipped: Nvim does not support cryptmethod' + edit Xtestfile + set undofile cryptmethod=blowfish2 + call append(0, ['jan', 'feb', 'mar', 'apr', 'jun']) + call cursor(5, 1) + + set undolevels=100 + exe 'normal kk0ifoo ' + set undolevels=100 + normal dd + set undolevels=100 + exe 'normal ibar ' + set undolevels=100 + " encrypt the file using key 'foo2bar' + call feedkeys("foo2bar\nfoo2bar\n") + X + write! + bwipe! + + call feedkeys("foo2bar\n") + edit Xtestfile + set key= + call search('bar') + call assert_equal('bar apr', getline('.')) + normal u + call assert_equal('apr', getline('.')) + normal u + call assert_equal('foo mar', getline('.')) + normal u + call assert_equal('mar', getline('.')) + + bwipe! + call delete('Xtestfile') + let ufile = has('vms') ? '_un_Xtestfile' : '.Xtestfile.un~' + call delete(ufile) + set undofile& undolevels& cryptmethod& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From ad5049aa60c918dba0f1094118a09f265091f6c1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 15 Dec 2019 10:57:43 -0500 Subject: vim-patch:8.2.0010: test64 is old style Problem: Test64 is old style. Solution: Convert to new style test. (Yegappan Lakshmanan, closes vim/vim#5363) https://github.com/vim/vim/commit/f9cb05c14753d984f002c0c090688f8510147e6b --- src/nvim/testdir/Makefile | 1 - src/nvim/testdir/test64.in | 654 ------------------- src/nvim/testdir/test64.ok | 1107 -------------------------------- src/nvim/testdir/test_regexp_latin.vim | 668 ++++++++++++++++++- 4 files changed, 667 insertions(+), 1763 deletions(-) delete mode 100644 src/nvim/testdir/test64.in delete mode 100644 src/nvim/testdir/test64.ok (limited to 'src') diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index b470dbf8f6..c36458930f 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -15,7 +15,6 @@ export TMPDIR := $(abspath Xtest-tmpdir) SCRIPTS_DEFAULT = \ test42.out \ - test64.out \ ifneq ($(OS),Windows_NT) SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ diff --git a/src/nvim/testdir/test64.in b/src/nvim/testdir/test64.in deleted file mode 100644 index ec11e15e35..0000000000 --- a/src/nvim/testdir/test64.in +++ /dev/null @@ -1,654 +0,0 @@ -Test for regexp patterns without multi-byte support. -See test95 for multi-byte tests. - -A pattern that gives the expected result produces OK, so that we know it was -actually tried. - -STARTTEST -:" tl is a List of Lists with: -:" regexp engine -:" regexp pattern -:" text to test the pattern on -:" expected match (optional) -:" expected submatch 1 (optional) -:" expected submatch 2 (optional) -:" etc. -:" When there is no match use only the first two items. -:let tl = [] -:" -:"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -:"""" Previously written tests """""""""""""""""""""""""""""""" -:"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -:" -:set noautoindent -:call add(tl, [2, 'ab', 'aab', 'ab']) -:call add(tl, [2, 'b', 'abcdef', 'b']) -:call add(tl, [2, 'bc*', 'abccccdef', 'bcccc']) -:call add(tl, [2, 'bc\{-}', 'abccccdef', 'b']) -:call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd']) -:call add(tl, [2, 'bc*', 'abbdef', 'b']) -:call add(tl, [2, 'c*', 'ccc', 'ccc']) -:call add(tl, [2, 'bc*', 'abdef', 'b']) -:call add(tl, [2, 'c*', 'abdef', '']) -:call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) -:call add(tl, [2, 'bc\+', 'abdef']) "no match -:" -:"operator \| -:call add(tl, [2, 'a\|ab', 'cabd', 'a']) "alternation is ordered -:" -:call add(tl, [2, 'c\?', 'ccb', 'c']) -:call add(tl, [2, 'bc\?', 'abd', 'b']) -:call add(tl, [2, 'bc\?', 'abccd', 'bc']) -:" -:call add(tl, [2, '\va{1}', 'ab', 'a']) -:" -:call add(tl, [2, '\va{2}', 'aa', 'aa']) -:call add(tl, [2, '\va{2}', 'caad', 'aa']) -:call add(tl, [2, '\va{2}', 'aba']) -:call add(tl, [2, '\va{2}', 'ab']) -:call add(tl, [2, '\va{2}', 'abaa', 'aa']) -:call add(tl, [2, '\va{2}', 'aaa', 'aa']) -:" -:call add(tl, [2, '\vb{1}', 'abca', 'b']) -:call add(tl, [2, '\vba{2}', 'abaa', 'baa']) -:call add(tl, [2, '\vba{3}', 'aabaac']) -:" -:call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab']) -:call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab']) -:call add(tl, [2, '\v(ab){1}', 'acb']) -:" -:call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""]) -:call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab']) -:call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab']) -:call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab']) -:call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab']) -:call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab']) -:" -:call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab']) -:call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab']) -:call add(tl, [2, '\v(ab){2}', 'abac']) -:call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab']) -:call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab']) -:call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab']) -:" -:call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a']) -:call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa']) -:call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa']) -:call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa']) -:call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a']) -:call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a']) -:call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa']) -:call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa']) -:call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa']) -:call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a']) -:call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a']) -:call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa']) -:call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa']) -:" -:call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a']) -:call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa']) -:" -:call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a']) -:call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a']) -:call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa']) -:call add(tl, [2, '\v(a{1,3}){3}', 'daac']) -:call add(tl, [2, '\v(a{1,2}){2}', 'dac']) -:call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa']) -:call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa']) -:call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa']) -:call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a']) -:call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa']) -:call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b']) -:call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b']) -:call add(tl, [2, '\v(abc){2}', 'abcabd', ]) -:call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc']) -:" -:call add(tl, [2, 'a*', 'cc', '']) -:call add(tl, [2, '\v(a*)+', 'cc', '']) -:call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab']) -:call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab']) -:call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab']) -:call add(tl, [2, '\v(a{0,2})+', 'cc', '']) -:call add(tl, [2, '\v(a*)+', '', '']) -:call add(tl, [2, '\v((a*)+)+', '', '']) -:call add(tl, [2, '\v((ab)*)+', '', '']) -:call add(tl, [2, '\va{1,3}', 'aab', 'aa']) -:call add(tl, [2, '\va{2,3}', 'abaa', 'aa']) -:" -:call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab']) -:call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb']) -:call add(tl, [2, '\va{2}|b{2}', 'abab']) -:call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a']) -:call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc']) -:call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc']) -:call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde']) -:call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ]) -:call add(tl, [2, '\va*a{2}', 'a', ]) -:call add(tl, [2, '\va*a{2}', 'aa', 'aa' ]) -:call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ]) -:call add(tl, [2, '\va*a{2}', 'bbbabcc', ]) -:call add(tl, [2, '\va*b*|a*c*', 'a', 'a']) -:call add(tl, [2, '\va{1}b{1}|a{1}b{1}', '']) -:" -:"submatches -:call add(tl, [2, '\v(a)', 'ab', 'a', 'a']) -:call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b']) -:call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c']) -:call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b']) -:call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a']) -:" -:call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', '']) -:call add(tl, [2, 'x', 'abcdef']) -:" -:"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -:""""" Simple tests """"""""""""""""""""""""""""""""""""""""""" -:"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -:" -:" Search single groups -:call add(tl, [2, 'ab', 'aab', 'ab']) -:call add(tl, [2, 'ab', 'baced']) -:call add(tl, [2, 'ab', ' ab ', 'ab']) -:" -:" Search multi-modifiers -:call add(tl, [2, 'x*', 'xcd', 'x']) -:call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx']) -:" empty match is good -:call add(tl, [2, 'x*', 'abcdoij', '']) -:" no match here -:call add(tl, [2, 'x\+', 'abcdoin']) -:call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx']) -:call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx']) -:call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x']) -:call add(tl, [2, 'x\=', 'x sdfoij', 'x']) -:call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good -:call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x']) -:call add(tl, [2, 'x\?', 'x sdfoij', 'x']) -:" empty match is good -:call add(tl, [2, 'x\?', 'abc sfoij', '']) -:call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x']) -:" -:call add(tl, [2, 'a\{0,0}', 'abcdfdoij', '']) -:" same thing as 'a?' -:call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a']) -:" same thing as 'a\{0,1}' -:call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a']) -:call add(tl, [2, 'a\{3,6}', 'aa siofuh']) -:call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa']) -:call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa']) -:call add(tl, [2, 'a\{0}', 'asoiuj', '']) -:call add(tl, [2, 'a\{2}', 'aaaa', 'aa']) -:call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa']) -:call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890']) -:" same thing as 'a*' -:call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', '']) -:call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa']) -:call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg']) -:call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa']) -:call add(tl, [2, 'a\{5,}', 'xxaaaaxxx ']) -:call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa']) -:call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', '']) -:call add(tl, [2, 'a\{,5}', 'abcd', 'a']) -:call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa']) -:" leading star as normal char when \{} follows -:call add(tl, [2, '^*\{4,}$', '***']) -:call add(tl, [2, '^*\{4,}$', '****', '****']) -:call add(tl, [2, '^*\{4,}$', '*****', '*****']) -:" same thing as 'a*' -:call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', '']) -:call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa']) -:" -:call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', '']) -:" anti-greedy version of 'a?' -:call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', '']) -:call add(tl, [2, 'a\{-3,6}', 'aa siofuh']) -:call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa']) -:call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa']) -:call add(tl, [2, 'a\{-0}', 'asoiuj', '']) -:call add(tl, [2, 'a\{-2}', 'aaaa', 'aa']) -:call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890']) -:call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', '']) -:call add(tl, [2, 'a\{-0,}', 'aaaaa aa', '']) -:call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg']) -:call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa']) -:call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', '']) -:call add(tl, [2, 'a\{-,5}', 'abcd', '']) -:call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', '']) -:" anti-greedy version of 'a*' -:call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', '']) -:call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', '']) -:" -:" Test groups of characters and submatches -:call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc']) -:call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab']) -:call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', '']) -:call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', '']) -:call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443']) -:call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2']) -:call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz']) -:call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab']) -:call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab']) -:call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', '']) -:call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', '']) -:call add(tl, [2, '\p*', 'aá ', 'aá ']) -:" -:" Test greedy-ness and lazy-ness -:call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa']) -:call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax']) -:call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa']) -:call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax']) -:call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz']) -:call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa','']) -:call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa']) -:call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a']) -:call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x']) -:call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x']) -:call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x']) -:call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x']) -:" -:" Test Character classes -:call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23']) -:" -:" Test collections and character range [] -:call add(tl, [2, '\v[a]', 'abcd', 'a']) -:call add(tl, [2, 'a[bcd]', 'abcd', 'ab']) -:call add(tl, [2, 'a[b-d]', 'acbd', 'ac']) -:call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd']) -:call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz']) -:call add(tl, [2, '[[:alpha:]\+]', '6x8','x']) -:call add(tl, [2, '[^abc]\+','abcabcabc']) -:call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d']) -:call add(tl, [2, '[^abc]\+','ddddddda','ddddddd']) -:call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC']) -:call add(tl, [2, '[a-f]*','iiiiiiii','']) -:call add(tl, [2, '[a-f]*','abcdefgh','abcdef']) -:call add(tl, [2, '[^a-f]\+','abcdefgh','gh']) -:call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc']) -:call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787']) -:call add(tl, [2, '[-a]', '-', '-']) -:call add(tl, [2, '[a-]', '-', '-']) -:call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF']) -:call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY']) -:" filename regexp -:call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file']) -:" special chars -:call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^']) -:" collation elem -:call add(tl, [2, '[[.a.]]\+', 'aa', 'aa']) -:" middle of regexp -:call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii']) -:call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd']) -:call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888']) -:call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888']) -:call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"]) -:call add(tl, [2, '\_f', " \na ", "\n"]) -:call add(tl, [2, '\_f\+', " \na ", "\na"]) -:call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"]) -:" -:"""" Test start/end of line, start/end of file -:call add(tl, [2, '^a.', "a_\nb ", "a_"]) -:call add(tl, [2, '^a.', "b a \na_"]) -:call add(tl, [2, '.a$', " a\n "]) -:call add(tl, [2, '.a$', " a b\n_a", "_a"]) -:call add(tl, [2, '\%^a.', "a a\na", "a "]) -:call add(tl, [2, '\%^a', " a \na "]) -:call add(tl, [2, '.a\%$', " a\n "]) -:call add(tl, [2, '.a\%$', " a\n_a", "_a"]) -:" -:"""" Test recognition of character classes -:call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567']) -:call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89']) -:call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789']) -:call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% ']) -:call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef']) -:call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% ']) -:call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij']) -:call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% ']) -:call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ']) -:call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% ']) -:call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz']) -:call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz']) -:call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% ']) -:call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%']) -:call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ']) -:call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ ']) -:call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ']) -:call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%']) -:call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ']) -:call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ']) -:call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ ']) -:call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ ']) -:call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa']) -:" -:"""" Tests for \z features -:" match ends at \ze -:call add(tl, [2, 'xx \ze test', 'xx ']) -:call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc']) -:call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa']) -:call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx']) -:call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb']) -:call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa']) -:" match starts at \zs -:call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd']) -:call add(tl, [2, 'aa \zsax', ' ax']) -:call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match']) -:call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last']) -:call add(tl, [2, '\>\zs.', 'aword. ', '.']) -:call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' ']) -:" -:"""" Tests for \@= and \& features -:call add(tl, [2, 'abc\@=', 'abc', 'ab']) -:call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd']) -:call add(tl, [2, 'abc\@=', 'ababc', 'ab']) -:" will never match, no matter the input text -:call add(tl, [2, 'abcd\@=e', 'abcd']) -:" will never match -:call add(tl, [2, 'abcd\@=e', 'any text in here ... ']) -:call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc']) -:call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B']) -:call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend']) -:call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))']) -:call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B']) -:call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob']) -:call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1']) -:call add(tl, [2, 'foo\(bar\)\@!', 'foobar']) -:call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo']) -:call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else']) -:call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' ']) -:call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar']) -:call add(tl, [2, '\(foo\)\@!...bar', 'foobar']) -:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo ']) -:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar ']) -:call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo']) -:call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:']) -:call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's']) -:call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe']) -:call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR']) -:" -:"""" Combining different tests and features -:call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab']) -:call add(tl, [2, '', 'abcd', '']) -:call add(tl, [2, '\v(())', 'any possible text', '']) -:call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz']) -:call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', '']) -:call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a']) -:" -:"""" \%u and friends -:call add(tl, [2, '\%d32', 'yes no', ' ']) -:call add(tl, [2, '\%o40', 'yes no', ' ']) -:call add(tl, [2, '\%x20', 'yes no', ' ']) -:call add(tl, [2, '\%u0020', 'yes no', ' ']) -:call add(tl, [2, '\%U00000020', 'yes no', ' ']) -:call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"]) -:" -:""""" \%[abc] -:call add(tl, [2, 'foo\%[bar]', 'fobar']) -:call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar']) -:call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo']) -:call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob']) -:call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba']) -:call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar']) -:call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx']) -:call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx']) -:call add(tl, [2, '\%[bar]x', 'barxx', 'barx']) -:call add(tl, [2, '\%[bar]x', 'bxx', 'bx']) -:call add(tl, [2, '\%[bar]x', 'xxx', 'x']) -:call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar']) -:call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r']) -:call add(tl, [2, '@\%[\w\-]*', '[@pandoc]', '@pandoc']) -:" -:"""" Alternatives, must use first longest match -:call add(tl, [2, 'goo\|go', 'google', 'goo']) -:call add(tl, [2, '\', 'foobar']) -:call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo']) -:call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo']) -:" -:" complicated look-behind match -:call add(tl, [2, '\(r\@<=\|\w\@ -:call add(tl, [2, '\(a*\)\@>a', 'aaaa']) -:call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa']) -:call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab']) -:call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', '']) -:" TODO: BT engine does not restore submatch after failure -:call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa']) -:" -:"""" "\_" prepended negated collection matches EOL -:call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"]) -:call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"]) -:" -:"""" Requiring lots of states. -:call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"]) -:" -:"""" Skip adding state twice -:call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO']) -:" -:""" Test \%V atom -:call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt']) -:" -:"""" Run the tests -:" -:for t in tl -: let re = t[0] -: let pat = t[1] -: let text = t[2] -: let matchidx = 3 -: for engine in [0, 1, 2] -: if engine == 2 && re == 0 || engine == 1 && re == 1 -: continue -: endif -: let ®expengine = engine -: try -: let l = matchlist(text, pat) -: catch -: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", caused an exception: \"' . v:exception . '\"' -: endtry -:" check the match itself -: if len(l) == 0 && len(t) > matchidx -: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", did not match, expected: \"' . t[matchidx] . '\"' -: elseif len(l) > 0 && len(t) == matchidx -: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", match: \"' . l[0] . '\", expected no match' -: elseif len(t) > matchidx && l[0] != t[matchidx] -: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", match: \"' . l[0] . '\", expected: \"' . t[matchidx] . '\"' -: else -: $put ='OK ' . engine . ' - ' . pat -: endif -: if len(l) > 0 -:" check all the nine submatches -: for i in range(1, 9) -: if len(t) <= matchidx + i -: let e = '' -: else -: let e = t[matchidx + i] -: endif -: if l[i] != e -: $put ='ERROR ' . engine . ': pat: \"' . pat . '\", text: \"' . text . '\", submatch ' . i . ': \"' . l[i] . '\", expected: \"' . e . '\"' -: endif -: endfor -: unlet i -: endif -: endfor -:endfor -:unlet t tl e l -:" -:"""""" multi-line tests """""""""""""""""""" -:let tl = [] -:" -:"""" back references -:call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']]) -:call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']]) -:" -:"""" line breaks -:call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']]) -:" -:" Check that \_[0-9] matching EOL does not break a following \> -:call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']]) -:" -:" Check a pattern with a line break and ^ and $ -:call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']]) -:" -:call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']]) -:" -:"""" Run the multi-line tests -:" -:$put ='multi-line tests' -:for t in tl -: let re = t[0] -: let pat = t[1] -: let before = t[2] -: let after = t[3] -: for engine in [0, 1, 2] -: if engine == 2 && re == 0 || engine == 1 && re ==1 -: continue -: endif -: let ®expengine = engine -: new -: call setline(1, before) -: exe '%s/' . pat . '/XX/' -: let result = getline(1, '$') -: q! -: if result != after -: $put ='ERROR: pat: \"' . pat . '\", text: \"' . string(before) . '\", expected: \"' . string(after) . '\", got: \"' . string(result) . '\"' -: else -: $put ='OK ' . engine . ' - ' . pat -: endif -: endfor -:endfor -:unlet t tl -:" -:" Check that using a pattern on two lines doesn't get messed up by using -:" matchstr() with \ze in between. -:set re=0 -/^Substitute here -:.+1,.+2s/""/\='"'.matchstr(getline("."), '\d\+\ze<').'"' -/^Substitute here -:.+1,.+2yank -Gop:" -:" -:" Check a pattern with a look beind crossing a line boundary -/^Behind: -/\(<\_[xy]\+\)\@3<=start -:.yank -Gop:" -:" -:" Check matching Visual area -/^Visual: -jfxvfx:s/\%Ve/E/g -jV:s/\%Va/A/g -jfxfxj:s/\%Vo/O/g -:/^Visual/+1,/^Visual/+4yank -Gop:" -:" -:" Check matching marks -/^Marks: -jfSmsfEme:.-4,.+6s/.\%>'s.*\%<'e../here/ -jfSmsj0fEme:.-4,.+6s/.\%>'s\_.*\%<'e../again/ -:/^Marks:/+1,/^Marks:/+3yank -Gop:" -:" -:" Check patterns matching cursor position. -:func! Postest() - new - call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo', "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_', ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx']) - call setpos('.', [0, 1, 0, 0]) - s/\%>3c.//g - call setpos('.', [0, 2, 4, 0]) - s/\%#.*$//g - call setpos('.', [0, 3, 0, 0]) - s/\%<3c./_/g - %s/\%4l\%>5c./_/g - %s/\%6l\%>25v./_/g - %s/\%>6l\%3c./!/g - %s/\%>7l\%12c./?/g - %s/\%>7l\%<9l\%>5v\%<8v./#/g - $s/\%(|\u.*\)\@<=[^|\t]\+$//ge - 1,$yank - quit! -endfunc -Go-0-:set re=0 -:call Postest() -:put -o-1-:set re=1 -:call Postest() -:put -o-2-:set re=2 -:call Postest() -:put -:" -:" start and end of buffer -/\%^ -yeGop:" -50%/\%^.. -yeGopA END:" -50%/\%$ -"ayb20gg/..\%$ -"bybGo"apo"bp:" -:" -:" Check for detecting error -:set regexpengine=2 -:for pat in [' \ze*', ' \zs*'] -: try -: let l = matchlist('x x', pat) -: $put ='E888 NOT detected for ' . pat -: catch -: $put ='E888 detected for ' . pat -: endtry -:endfor -:" -:""""" Write the results """"""""""""" -:/\%#=1^Results/,$wq! test.out -ENDTEST - -Substitute here: -Ta 5 -Ac 7 - -Behind: -asdfasd\zs. -OK 1 - \>\zs. -OK 2 - \>\zs. -OK 0 - \s\+\ze\[/\|\s\zs\s\+ -OK 1 - \s\+\ze\[/\|\s\zs\s\+ -OK 2 - \s\+\ze\[/\|\s\zs\s\+ -OK 0 - abc\@= -OK 1 - abc\@= -OK 2 - abc\@= -OK 0 - abc\@=cd -OK 1 - abc\@=cd -OK 2 - abc\@=cd -OK 0 - abc\@= -OK 1 - abc\@= -OK 2 - abc\@= -OK 0 - abcd\@=e -OK 1 - abcd\@=e -OK 2 - abcd\@=e -OK 0 - abcd\@=e -OK 1 - abcd\@=e -OK 2 - abcd\@=e -OK 0 - \v(abc)@=.. -OK 1 - \v(abc)@=.. -OK 2 - \v(abc)@=.. -OK 0 - \(.*John\)\@=.*Bob -OK 1 - \(.*John\)\@=.*Bob -OK 2 - \(.*John\)\@=.*Bob -OK 0 - \(John.*\)\@=.*Bob -OK 1 - \(John.*\)\@=.*Bob -OK 2 - \(John.*\)\@=.*Bob -OK 0 - \<\S\+\())\)\@= -OK 1 - \<\S\+\())\)\@= -OK 2 - \<\S\+\())\)\@= -OK 0 - .*John\&.*Bob -OK 1 - .*John\&.*Bob -OK 2 - .*John\&.*Bob -OK 0 - .*John\&.*Bob -OK 1 - .*John\&.*Bob -OK 2 - .*John\&.*Bob -OK 0 - \v(test1)@=.*yep -OK 1 - \v(test1)@=.*yep -OK 2 - \v(test1)@=.*yep -OK 0 - foo\(bar\)\@! -OK 1 - foo\(bar\)\@! -OK 2 - foo\(bar\)\@! -OK 0 - foo\(bar\)\@! -OK 1 - foo\(bar\)\@! -OK 2 - foo\(bar\)\@! -OK 0 - if \(\(then\)\@!.\)*$ -OK 1 - if \(\(then\)\@!.\)*$ -OK 2 - if \(\(then\)\@!.\)*$ -OK 0 - if \(\(then\)\@!.\)*$ -OK 1 - if \(\(then\)\@!.\)*$ -OK 2 - if \(\(then\)\@!.\)*$ -OK 0 - \(foo\)\@!bar -OK 1 - \(foo\)\@!bar -OK 2 - \(foo\)\@!bar -OK 0 - \(foo\)\@!...bar -OK 1 - \(foo\)\@!...bar -OK 2 - \(foo\)\@!...bar -OK 0 - ^\%(.*bar\)\@!.*\zsfoo -OK 1 - ^\%(.*bar\)\@!.*\zsfoo -OK 2 - ^\%(.*bar\)\@!.*\zsfoo -OK 0 - ^\%(.*bar\)\@!.*\zsfoo -OK 1 - ^\%(.*bar\)\@!.*\zsfoo -OK 2 - ^\%(.*bar\)\@!.*\zsfoo -OK 0 - ^\%(.*bar\)\@!.*\zsfoo -OK 1 - ^\%(.*bar\)\@!.*\zsfoo -OK 2 - ^\%(.*bar\)\@!.*\zsfoo -OK 0 - [ ]\@!\p\%([ ]\@!\p\)*: -OK 1 - [ ]\@!\p\%([ ]\@!\p\)*: -OK 2 - [ ]\@!\p\%([ ]\@!\p\)*: -OK 0 - [ ]\@!\p\([ ]\@!\p\)*: -OK 1 - [ ]\@!\p\([ ]\@!\p\)*: -OK 2 - [ ]\@!\p\([ ]\@!\p\)*: -OK 0 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e -OK 1 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e -OK 2 - m\k\+_\@=\%(_\@!\k\)\@<=\k\+e -OK 0 - \%(\U\@<=S\k*\|S\l\)R -OK 1 - \%(\U\@<=S\k*\|S\l\)R -OK 2 - \%(\U\@<=S\k*\|S\l\)R -OK 0 - [[:alpha:]]\{-2,6} -OK 1 - [[:alpha:]]\{-2,6} -OK 2 - [[:alpha:]]\{-2,6} -OK 0 - -OK 1 - -OK 2 - -OK 0 - \v(()) -OK 1 - \v(()) -OK 2 - \v(()) -OK 0 - \v%(ab(xyz)c) -OK 1 - \v%(ab(xyz)c) -OK 2 - \v%(ab(xyz)c) -OK 0 - \v(test|)empty -OK 1 - \v(test|)empty -OK 2 - \v(test|)empty -OK 0 - \v(a|aa)(a|aa) -OK 1 - \v(a|aa)(a|aa) -OK 2 - \v(a|aa)(a|aa) -OK 0 - \%d32 -OK 1 - \%d32 -OK 2 - \%d32 -OK 0 - \%o40 -OK 1 - \%o40 -OK 2 - \%o40 -OK 0 - \%x20 -OK 1 - \%x20 -OK 2 - \%x20 -OK 0 - \%u0020 -OK 1 - \%u0020 -OK 2 - \%u0020 -OK 0 - \%U00000020 -OK 1 - \%U00000020 -OK 2 - \%U00000020 -OK 0 - \%d0 -OK 1 - \%d0 -OK 2 - \%d0 -OK 0 - foo\%[bar] -OK 1 - foo\%[bar] -OK 2 - foo\%[bar] -OK 0 - foo\%[bar] -OK 1 - foo\%[bar] -OK 2 - foo\%[bar] -OK 0 - foo\%[bar] -OK 1 - foo\%[bar] -OK 2 - foo\%[bar] -OK 0 - foo\%[bar] -OK 1 - foo\%[bar] -OK 2 - foo\%[bar] -OK 0 - foo\%[bar] -OK 1 - foo\%[bar] -OK 2 - foo\%[bar] -OK 0 - foo\%[bar] -OK 1 - foo\%[bar] -OK 2 - foo\%[bar] -OK 0 - foo\%[bar]x -OK 1 - foo\%[bar]x -OK 2 - foo\%[bar]x -OK 0 - foo\%[bar]x -OK 1 - foo\%[bar]x -OK 2 - foo\%[bar]x -OK 0 - \%[bar]x -OK 1 - \%[bar]x -OK 2 - \%[bar]x -OK 0 - \%[bar]x -OK 1 - \%[bar]x -OK 2 - \%[bar]x -OK 0 - \%[bar]x -OK 1 - \%[bar]x -OK 2 - \%[bar]x -OK 0 - b\%[[ao]r] -OK 1 - b\%[[ao]r] -OK 2 - b\%[[ao]r] -OK 0 - b\%[[]]r] -OK 1 - b\%[[]]r] -OK 2 - b\%[[]]r] -OK 0 - @\%[\w\-]* -OK 1 - @\%[\w\-]* -OK 2 - @\%[\w\-]* -OK 0 - goo\|go -OK 1 - goo\|go -OK 2 - goo\|go -OK 0 - \ -OK 1 - \(foo\)\@<=\> -OK 2 - \(foo\)\@<=\> -OK 0 - \(foo\)\@<=\> -OK 1 - \(foo\)\@<=\> -OK 2 - \(foo\)\@<=\> -OK 0 - \(foo\)\@<=.* -OK 1 - \(foo\)\@<=.* -OK 2 - \(foo\)\@<=.* -OK 0 - \(r\@<=\|\w\@a -OK 1 - \(a*\)\@>a -OK 2 - \(a*\)\@>a -OK 0 - \(a*\)\@>b -OK 1 - \(a*\)\@>b -OK 2 - \(a*\)\@>b -OK 0 - ^\(.\{-}b\)\@>. -OK 1 - ^\(.\{-}b\)\@>. -OK 2 - ^\(.\{-}b\)\@>. -OK 0 - \(.\{-}\)\(\)\@>$ -OK 1 - \(.\{-}\)\(\)\@>$ -OK 2 - \(.\{-}\)\(\)\@>$ -OK 0 - \(a*\)\@>a\|a\+ -OK 2 - \(a*\)\@>a\|a\+ -OK 0 - \_[^8-9]\+ -OK 1 - \_[^8-9]\+ -OK 2 - \_[^8-9]\+ -OK 0 - \_[^a]\+ -OK 1 - \_[^a]\+ -OK 2 - \_[^a]\+ -OK 0 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12} -OK 1 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12} -OK 2 - [0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12} -OK 0 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@= -OK 1 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@= -OK 2 - ^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@= -OK 0 - \%>70vGesamt -OK 1 - \%>70vGesamt -OK 2 - \%>70vGesamt -multi-line tests -OK 0 - ^.\(.\).\_..\1. -OK 1 - ^.\(.\).\_..\1. -OK 2 - ^.\(.\).\_..\1. -OK 0 - \v.*\/(.*)\n.*\/\1$ -OK 1 - \v.*\/(.*)\n.*\/\1$ -OK 2 - \v.*\/(.*)\n.*\/\1$ -OK 0 - \S.*\nx -OK 1 - \S.*\nx -OK 2 - \S.*\nx -OK 0 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\> -OK 1 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\> -OK 2 - \<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\> -OK 0 - a\n^b$\n^c -OK 1 - a\n^b$\n^c -OK 2 - a\n^b$\n^c -OK 0 - \(^.\+\n\)\1 -OK 1 - \(^.\+\n\)\1 -OK 2 - \(^.\+\n\)\1 - -Ta 5 -Ac 7 - -xxstart3 - -thexE thE thExethe -AndAxAnd AndAxAnd -oooxOfOr fOrOxooo -oooxOfOr fOrOxooo - -asdfhereasdf -asdfagainasdf - --0- -ffo -bob -__ooooo -koooo__ -moooooo - f__ -ab!babababababfoo -ba!ab##abab?bafoo -**!*****_ - ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx --1- -ffo -bob -__ooooo -koooo__ -moooooo - f__ -ab!babababababfoo -ba!ab##abab?bafoo -**!*****_ - ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx --2- -ffo -bob -__ooooo -koooo__ -moooooo - f__ -ab!babababababfoo -ba!ab##abab?bafoo -**!*****_ - ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx -Test -Test END -EN -E -E888 detected for \ze* -E888 detected for \zs* diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index b5e99b0ed3..2ee0ee1c0c 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -3,7 +3,7 @@ set encoding=latin1 scriptencoding latin1 func s:equivalence_test() - let str = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z" + let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z" let groups = split(str) for group1 in groups for c in split(group1, '\zs') @@ -98,3 +98,669 @@ func Test_out_of_memory() " This will be slow... call assert_fails('call search("\\v((n||<)+);")', 'E363:') endfunc + +" Tests for regexp patterns without multi-byte support. +func Test_regexp_single_line_pat() + " tl is a List of Lists with: + " regexp engine + " regexp pattern + " text to test the pattern on + " expected match (optional) + " expected submatch 1 (optional) + " expected submatch 2 (optional) + " etc. + " When there is no match use only the first two items. + let tl = [] + + call add(tl, [2, 'ab', 'aab', 'ab']) + call add(tl, [2, 'b', 'abcdef', 'b']) + call add(tl, [2, 'bc*', 'abccccdef', 'bcccc']) + call add(tl, [2, 'bc\{-}', 'abccccdef', 'b']) + call add(tl, [2, 'bc\{-}\(d\)', 'abccccdef', 'bccccd', 'd']) + call add(tl, [2, 'bc*', 'abbdef', 'b']) + call add(tl, [2, 'c*', 'ccc', 'ccc']) + call add(tl, [2, 'bc*', 'abdef', 'b']) + call add(tl, [2, 'c*', 'abdef', '']) + call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) + call add(tl, [2, 'bc\+', 'abdef']) " no match + + " operator \| + call add(tl, [2, 'a\|ab', 'cabd', 'a']) " alternation is ordered + + call add(tl, [2, 'c\?', 'ccb', 'c']) + call add(tl, [2, 'bc\?', 'abd', 'b']) + call add(tl, [2, 'bc\?', 'abccd', 'bc']) + + call add(tl, [2, '\va{1}', 'ab', 'a']) + + call add(tl, [2, '\va{2}', 'aa', 'aa']) + call add(tl, [2, '\va{2}', 'caad', 'aa']) + call add(tl, [2, '\va{2}', 'aba']) + call add(tl, [2, '\va{2}', 'ab']) + call add(tl, [2, '\va{2}', 'abaa', 'aa']) + call add(tl, [2, '\va{2}', 'aaa', 'aa']) + + call add(tl, [2, '\vb{1}', 'abca', 'b']) + call add(tl, [2, '\vba{2}', 'abaa', 'baa']) + call add(tl, [2, '\vba{3}', 'aabaac']) + + call add(tl, [2, '\v(ab){1}', 'ab', 'ab', 'ab']) + call add(tl, [2, '\v(ab){1}', 'dabc', 'ab', 'ab']) + call add(tl, [2, '\v(ab){1}', 'acb']) + + call add(tl, [2, '\v(ab){0,2}', 'acb', "", ""]) + call add(tl, [2, '\v(ab){0,2}', 'ab', 'ab', 'ab']) + call add(tl, [2, '\v(ab){1,2}', 'ab', 'ab', 'ab']) + call add(tl, [2, '\v(ab){1,2}', 'ababc', 'abab', 'ab']) + call add(tl, [2, '\v(ab){2,4}', 'ababcab', 'abab', 'ab']) + call add(tl, [2, '\v(ab){2,4}', 'abcababa', 'abab', 'ab']) + + call add(tl, [2, '\v(ab){2}', 'abab', 'abab', 'ab']) + call add(tl, [2, '\v(ab){2}', 'cdababe', 'abab', 'ab']) + call add(tl, [2, '\v(ab){2}', 'abac']) + call add(tl, [2, '\v(ab){2}', 'abacabab', 'abab', 'ab']) + call add(tl, [2, '\v((ab){2}){2}', 'abababab', 'abababab', 'abab', 'ab']) + call add(tl, [2, '\v((ab){2}){2}', 'abacabababab', 'abababab', 'abab', 'ab']) + + call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a']) + call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa']) + call add(tl, [2, '\v(a{2}){1}', 'aaac', 'aa', 'aa']) + call add(tl, [2, '\v(a{2}){1}', 'daaac', 'aa', 'aa']) + call add(tl, [2, '\v(a{1}){2}', 'daaac', 'aa', 'a']) + call add(tl, [2, '\v(a{1}){2}', 'aaa', 'aa', 'a']) + call add(tl, [2, '\v(a{2})+', 'adaac', 'aa', 'aa']) + call add(tl, [2, '\v(a{2})+', 'aa', 'aa', 'aa']) + call add(tl, [2, '\v(a{2}){1}', 'aa', 'aa', 'aa']) + call add(tl, [2, '\v(a{1}){2}', 'aa', 'aa', 'a']) + call add(tl, [2, '\v(a{1}){1}', 'a', 'a', 'a']) + call add(tl, [2, '\v(a{2}){2}', 'aaaa', 'aaaa', 'aa']) + call add(tl, [2, '\v(a{2}){2}', 'aaabaaaa', 'aaaa', 'aa']) + + call add(tl, [2, '\v(a+){2}', 'dadaac', 'aa', 'a']) + call add(tl, [2, '\v(a{3}){2}', 'aaaaaaa', 'aaaaaa', 'aaa']) + + call add(tl, [2, '\v(a{1,2}){2}', 'daaac', 'aaa', 'a']) + call add(tl, [2, '\v(a{1,3}){2}', 'daaaac', 'aaaa', 'a']) + call add(tl, [2, '\v(a{1,3}){2}', 'daaaaac', 'aaaaa', 'aa']) + call add(tl, [2, '\v(a{1,3}){3}', 'daac']) + call add(tl, [2, '\v(a{1,2}){2}', 'dac']) + call add(tl, [2, '\v(a+)+', 'daac', 'aa', 'aa']) + call add(tl, [2, '\v(a+)+', 'aaa', 'aaa', 'aaa']) + call add(tl, [2, '\v(a+){1,2}', 'aaa', 'aaa', 'aaa']) + call add(tl, [2, '\v(a+)(a+)', 'aaa', 'aaa', 'aa', 'a']) + call add(tl, [2, '\v(a{3})+', 'daaaac', 'aaa', 'aaa']) + call add(tl, [2, '\v(a|b|c)+', 'aacb', 'aacb', 'b']) + call add(tl, [2, '\v(a|b|c){2}', 'abcb', 'ab', 'b']) + call add(tl, [2, '\v(abc){2}', 'abcabd', ]) + call add(tl, [2, '\v(abc){2}', 'abdabcabc','abcabc', 'abc']) + + call add(tl, [2, 'a*', 'cc', '']) + call add(tl, [2, '\v(a*)+', 'cc', '']) + call add(tl, [2, '\v((ab)+)+', 'ab', 'ab', 'ab', 'ab']) + call add(tl, [2, '\v(((ab)+)+)+', 'ab', 'ab', 'ab', 'ab', 'ab']) + call add(tl, [2, '\v(((ab)+)+)+', 'dababc', 'abab', 'abab', 'abab', 'ab']) + call add(tl, [2, '\v(a{0,2})+', 'cc', '']) + call add(tl, [2, '\v(a*)+', '', '']) + call add(tl, [2, '\v((a*)+)+', '', '']) + call add(tl, [2, '\v((ab)*)+', '', '']) + call add(tl, [2, '\va{1,3}', 'aab', 'aa']) + call add(tl, [2, '\va{2,3}', 'abaa', 'aa']) + + call add(tl, [2, '\v((ab)+|c*)+', 'abcccaba', 'abcccab', '', 'ab']) + call add(tl, [2, '\v(a{2})|(b{3})', 'bbabbbb', 'bbb', '', 'bbb']) + call add(tl, [2, '\va{2}|b{2}', 'abab']) + call add(tl, [2, '\v(a)+|(c)+', 'bbacbaacbbb', 'a', 'a']) + call add(tl, [2, '\vab{2,3}c', 'aabbccccccccccccc', 'abbc']) + call add(tl, [2, '\vab{2,3}c', 'aabbbccccccccccccc', 'abbbc']) + call add(tl, [2, '\vab{2,3}cd{2,3}e', 'aabbbcddee', 'abbbcdde']) + call add(tl, [2, '\va(bc){2}d', 'aabcbfbc' ]) + call add(tl, [2, '\va*a{2}', 'a', ]) + call add(tl, [2, '\va*a{2}', 'aa', 'aa' ]) + call add(tl, [2, '\va*a{2}', 'aaa', 'aaa' ]) + call add(tl, [2, '\va*a{2}', 'bbbabcc', ]) + call add(tl, [2, '\va*b*|a*c*', 'a', 'a']) + call add(tl, [2, '\va{1}b{1}|a{1}b{1}', '']) + + " submatches + call add(tl, [2, '\v(a)', 'ab', 'a', 'a']) + call add(tl, [2, '\v(a)(b)', 'ab', 'ab', 'a', 'b']) + call add(tl, [2, '\v(ab)(b)(c)', 'abbc', 'abbc', 'ab', 'b', 'c']) + call add(tl, [2, '\v((a)(b))', 'ab', 'ab', 'ab', 'a', 'b']) + call add(tl, [2, '\v(a)|(b)', 'ab', 'a', 'a']) + + call add(tl, [2, '\v(a*)+', 'aaaa', 'aaaa', '']) + call add(tl, [2, 'x', 'abcdef']) + + " + " Simple tests + " + + " Search single groups + call add(tl, [2, 'ab', 'aab', 'ab']) + call add(tl, [2, 'ab', 'baced']) + call add(tl, [2, 'ab', ' ab ', 'ab']) + + " Search multi-modifiers + call add(tl, [2, 'x*', 'xcd', 'x']) + call add(tl, [2, 'x*', 'xxxxxxxxxxxxxxxxsofijiojgf', 'xxxxxxxxxxxxxxxx']) + " empty match is good + call add(tl, [2, 'x*', 'abcdoij', '']) + " no match here + call add(tl, [2, 'x\+', 'abcdoin']) + call add(tl, [2, 'x\+', 'abcdeoijdfxxiuhfij', 'xx']) + call add(tl, [2, 'x\+', 'xxxxx', 'xxxxx']) + call add(tl, [2, 'x\+', 'abc x siufhiush xxxxxxxxx', 'x']) + call add(tl, [2, 'x\=', 'x sdfoij', 'x']) + call add(tl, [2, 'x\=', 'abc sfoij', '']) " empty match is good + call add(tl, [2, 'x\=', 'xxxxxxxxx c', 'x']) + call add(tl, [2, 'x\?', 'x sdfoij', 'x']) + " empty match is good + call add(tl, [2, 'x\?', 'abc sfoij', '']) + call add(tl, [2, 'x\?', 'xxxxxxxxxx c', 'x']) + + call add(tl, [2, 'a\{0,0}', 'abcdfdoij', '']) + " same thing as 'a?' + call add(tl, [2, 'a\{0,1}', 'asiubid axxxaaa', 'a']) + " same thing as 'a\{0,1}' + call add(tl, [2, 'a\{1,0}', 'asiubid axxxaaa', 'a']) + call add(tl, [2, 'a\{3,6}', 'aa siofuh']) + call add(tl, [2, 'a\{3,6}', 'aaaaa asfoij afaa', 'aaaaa']) + call add(tl, [2, 'a\{3,6}', 'aaaaaaaa', 'aaaaaa']) + call add(tl, [2, 'a\{0}', 'asoiuj', '']) + call add(tl, [2, 'a\{2}', 'aaaa', 'aa']) + call add(tl, [2, 'a\{2}', 'iuash fiusahfliusah fiushfilushfi uhsaifuh askfj nasfvius afg aaaa sfiuhuhiushf', 'aa']) + call add(tl, [2, 'a\{2}', 'abcdefghijklmnopqrestuvwxyz1234567890']) + " same thing as 'a*' + call add(tl, [2, 'a\{0,}', 'oij sdigfusnf', '']) + call add(tl, [2, 'a\{0,}', 'aaaaa aa', 'aaaaa']) + call add(tl, [2, 'a\{2,}', 'sdfiougjdsafg']) + call add(tl, [2, 'a\{2,}', 'aaaaasfoij ', 'aaaaa']) + call add(tl, [2, 'a\{5,}', 'xxaaaaxxx ']) + call add(tl, [2, 'a\{5,}', 'xxaaaaaxxx ', 'aaaaa']) + call add(tl, [2, 'a\{,0}', 'oidfguih iuhi hiu aaaa', '']) + call add(tl, [2, 'a\{,5}', 'abcd', 'a']) + call add(tl, [2, 'a\{,5}', 'aaaaaaaaaa', 'aaaaa']) + " leading star as normal char when \{} follows + call add(tl, [2, '^*\{4,}$', '***']) + call add(tl, [2, '^*\{4,}$', '****', '****']) + call add(tl, [2, '^*\{4,}$', '*****', '*****']) + " same thing as 'a*' + call add(tl, [2, 'a\{}', 'bbbcddiuhfcd', '']) + call add(tl, [2, 'a\{}', 'aaaaioudfh coisf jda', 'aaaa']) + + call add(tl, [2, 'a\{-0,0}', 'abcdfdoij', '']) + " anti-greedy version of 'a?' + call add(tl, [2, 'a\{-0,1}', 'asiubid axxxaaa', '']) + call add(tl, [2, 'a\{-3,6}', 'aa siofuh']) + call add(tl, [2, 'a\{-3,6}', 'aaaaa asfoij afaa', 'aaa']) + call add(tl, [2, 'a\{-3,6}', 'aaaaaaaa', 'aaa']) + call add(tl, [2, 'a\{-0}', 'asoiuj', '']) + call add(tl, [2, 'a\{-2}', 'aaaa', 'aa']) + call add(tl, [2, 'a\{-2}', 'abcdefghijklmnopqrestuvwxyz1234567890']) + call add(tl, [2, 'a\{-0,}', 'oij sdigfusnf', '']) + call add(tl, [2, 'a\{-0,}', 'aaaaa aa', '']) + call add(tl, [2, 'a\{-2,}', 'sdfiougjdsafg']) + call add(tl, [2, 'a\{-2,}', 'aaaaasfoij ', 'aa']) + call add(tl, [2, 'a\{-,0}', 'oidfguih iuhi hiu aaaa', '']) + call add(tl, [2, 'a\{-,5}', 'abcd', '']) + call add(tl, [2, 'a\{-,5}', 'aaaaaaaaaa', '']) + " anti-greedy version of 'a*' + call add(tl, [2, 'a\{-}', 'bbbcddiuhfcd', '']) + call add(tl, [2, 'a\{-}', 'aaaaioudfh coisf jda', '']) + + " Test groups of characters and submatches + call add(tl, [2, '\(abc\)*', 'abcabcabc', 'abcabcabc', 'abc']) + call add(tl, [2, '\(ab\)\+', 'abababaaaaa', 'ababab', 'ab']) + call add(tl, [2, '\(abaaaaa\)*cd', 'cd', 'cd', '']) + call add(tl, [2, '\(test1\)\? \(test2\)\?', 'test1 test3', 'test1 ', 'test1', '']) + call add(tl, [2, '\(test1\)\= \(test2\) \(test4443\)\=', ' test2 test4443 yupiiiiiiiiiii', ' test2 test4443', '', 'test2', 'test4443']) + call add(tl, [2, '\(\(sub1\) hello \(sub 2\)\)', 'asterix sub1 hello sub 2 obelix', 'sub1 hello sub 2', 'sub1 hello sub 2', 'sub1', 'sub 2']) + call add(tl, [2, '\(\(\(yyxxzz\)\)\)', 'abcdddsfiusfyyzzxxyyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz', 'yyxxzz']) + call add(tl, [2, '\v((ab)+|c+)+', 'abcccaba', 'abcccab', 'ab', 'ab']) + call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab']) + call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', '']) + call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', '']) + call add(tl, [2, '\p*', 'aá ', 'aá ']) + + " Test greedy-ness and lazy-ness + call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa']) + call add(tl, [2, 'a\{-2,7}x','aaaaaaaaax', 'aaaaaaax']) + call add(tl, [2, 'a\{2,7}','aaaaaaaaaaaaaaaaaaaa', 'aaaaaaa']) + call add(tl, [2, 'a\{2,7}x','aaaaaaaaax', 'aaaaaaax']) + call add(tl, [2, '\vx(.{-,8})yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz','ayxa','xayzxayz']) + call add(tl, [2, '\vx(.*)yz(.*)','xayxayzxayzxayz','xayxayzxayzxayz', 'ayxayzxayzxa','']) + call add(tl, [2, '\v(a{1,2}){-2,3}','aaaaaaa','aaaa','aa']) + call add(tl, [2, '\v(a{-1,3})+', 'aa', 'aa', 'a']) + call add(tl, [2, '^\s\{-}\zs\( x\|x$\)', ' x', ' x', ' x']) + call add(tl, [2, '^\s\{-}\zs\(x\| x$\)', ' x', ' x', ' x']) + call add(tl, [2, '^\s\{-}\ze\(x\| x$\)', ' x', '', ' x']) + call add(tl, [2, '^\(\s\{-}\)\(x\| x$\)', ' x', ' x', '', ' x']) + + " Test Character classes + call add(tl, [2, '\d\+e\d\d','test 10e23 fd','10e23']) + + " Test collections and character range [] + call add(tl, [2, '\v[a]', 'abcd', 'a']) + call add(tl, [2, 'a[bcd]', 'abcd', 'ab']) + call add(tl, [2, 'a[b-d]', 'acbd', 'ac']) + call add(tl, [2, '[a-d][e-f][x-x]d', 'cexdxx', 'cexd']) + call add(tl, [2, '\v[[:alpha:]]+', 'abcdefghijklmnopqrstuvwxyz6','abcdefghijklmnopqrstuvwxyz']) + call add(tl, [2, '[[:alpha:]\+]', '6x8','x']) + call add(tl, [2, '[^abc]\+','abcabcabc']) + call add(tl, [2, '[^abc]','defghiasijvoinasoiunbvb','d']) + call add(tl, [2, '[^abc]\+','ddddddda','ddddddd']) + call add(tl, [2, '[^a-d]\+','aaaAAAZIHFNCddd','AAAZIHFNC']) + call add(tl, [2, '[a-f]*','iiiiiiii','']) + call add(tl, [2, '[a-f]*','abcdefgh','abcdef']) + call add(tl, [2, '[^a-f]\+','abcdefgh','gh']) + call add(tl, [2, '[a-c]\{-3,6}','abcabc','abc']) + call add(tl, [2, '[^[:alpha:]]\+','abcccadfoij7787ysf287yrnccdu','7787']) + call add(tl, [2, '[-a]', '-', '-']) + call add(tl, [2, '[a-]', '-', '-']) + call add(tl, [2, '[a-f]*\c','ABCDEFGH','ABCDEF']) + call add(tl, [2, '[abc][xyz]\c','-af-AF-BY--','BY']) + " filename regexp + call add(tl, [2, '[-./[:alnum:]_~]\+', 'log13.file', 'log13.file']) + " special chars + call add(tl, [2, '[\]\^\-\\]\+', '\^\\\-\---^', '\^\\\-\---^']) + " collation elem + call add(tl, [2, '[[.a.]]\+', 'aa', 'aa']) + " middle of regexp + call add(tl, [2, 'abc[0-9]*ddd', 'siuhabc ii']) + call add(tl, [2, 'abc[0-9]*ddd', 'adf abc44482ddd oijs', 'abc44482ddd']) + call add(tl, [2, '\_[0-9]\+', 'asfi9888u', '9888']) + call add(tl, [2, '[0-9\n]\+', 'asfi9888u', '9888']) + call add(tl, [2, '\_[0-9]\+', "asfi\n9888u", "\n9888"]) + call add(tl, [2, '\_f', " \na ", "\n"]) + call add(tl, [2, '\_f\+', " \na ", "\na"]) + call add(tl, [2, '[0-9A-Za-z-_.]\+', " @0_a.A-{ ", "0_a.A-"]) + + " Test start/end of line, start/end of file + call add(tl, [2, '^a.', "a_\nb ", "a_"]) + call add(tl, [2, '^a.', "b a \na_"]) + call add(tl, [2, '.a$', " a\n "]) + call add(tl, [2, '.a$', " a b\n_a", "_a"]) + call add(tl, [2, '\%^a.', "a a\na", "a "]) + call add(tl, [2, '\%^a', " a \na "]) + call add(tl, [2, '.a\%$', " a\n "]) + call add(tl, [2, '.a\%$', " a\n_a", "_a"]) + + " Test recognition of character classes + call add(tl, [2, '[0-7]\+', 'x0123456789x', '01234567']) + call add(tl, [2, '[^0-7]\+', '0a;X+% 897', 'a;X+% 89']) + call add(tl, [2, '[0-9]\+', 'x0123456789x', '0123456789']) + call add(tl, [2, '[^0-9]\+', '0a;X+% 9', 'a;X+% ']) + call add(tl, [2, '[0-9a-fA-F]\+', 'x0189abcdefg', '0189abcdef']) + call add(tl, [2, '[^0-9A-Fa-f]\+', '0189g;X+% ab', 'g;X+% ']) + call add(tl, [2, '[a-z_A-Z0-9]\+', ';+aso_SfOij ', 'aso_SfOij']) + call add(tl, [2, '[^a-z_A-Z0-9]\+', 'aSo_;+% sfOij', ';+% ']) + call add(tl, [2, '[a-z_A-Z]\+', '0abyz_ABYZ;', 'abyz_ABYZ']) + call add(tl, [2, '[^a-z_A-Z]\+', 'abAB_09;+% yzYZ', '09;+% ']) + call add(tl, [2, '[a-z]\+', '0abcxyz1', 'abcxyz']) + call add(tl, [2, '[a-z]\+', 'AabxyzZ', 'abxyz']) + call add(tl, [2, '[^a-z]\+', 'a;X09+% x', ';X09+% ']) + call add(tl, [2, '[^a-z]\+', 'abX0;%yz', 'X0;%']) + call add(tl, [2, '[a-zA-Z]\+', '0abABxzXZ9', 'abABxzXZ']) + call add(tl, [2, '[^a-zA-Z]\+', 'ab09_;+ XZ', '09_;+ ']) + call add(tl, [2, '[A-Z]\+', 'aABXYZz', 'ABXYZ']) + call add(tl, [2, '[^A-Z]\+', 'ABx0;%YZ', 'x0;%']) + call add(tl, [2, '[a-z]\+\c', '0abxyzABXYZ;', 'abxyzABXYZ']) + call add(tl, [2, '[A-Z]\+\c', '0abABxzXZ9', 'abABxzXZ']) + call add(tl, [2, '\c[^a-z]\+', 'ab09_;+ XZ', '09_;+ ']) + call add(tl, [2, '\c[^A-Z]\+', 'ab09_;+ XZ', '09_;+ ']) + call add(tl, [2, '\C[^A-Z]\+', 'ABCOIJDEOIFNSD jsfoij sa', ' jsfoij sa']) + + " Tests for \z features + " match ends at \ze + call add(tl, [2, 'xx \ze test', 'xx ']) + call add(tl, [2, 'abc\zeend', 'oij abcend', 'abc']) + call add(tl, [2, 'aa\zebb\|aaxx', ' aabb ', 'aa']) + call add(tl, [2, 'aa\zebb\|aaxx', ' aaxx ', 'aaxx']) + call add(tl, [2, 'aabb\|aa\zebb', ' aabb ', 'aabb']) + call add(tl, [2, 'aa\zebb\|aaebb', ' aabb ', 'aa']) + " match starts at \zs + call add(tl, [2, 'abc\zsdd', 'ddabcddxyzt', 'dd']) + call add(tl, [2, 'aa \zsax', ' ax']) + call add(tl, [2, 'abc \zsmatch\ze abc', 'abc abc abc match abc abc', 'match']) + call add(tl, [2, '\v(a \zsif .*){2}', 'a if then a if last', 'if last', 'a if last']) + call add(tl, [2, '\>\zs.', 'aword. ', '.']) + call add(tl, [2, '\s\+\ze\[/\|\s\zs\s\+', 'is [a t', ' ']) + + " Tests for \@= and \& features + call add(tl, [2, 'abc\@=', 'abc', 'ab']) + call add(tl, [2, 'abc\@=cd', 'abcd', 'abcd']) + call add(tl, [2, 'abc\@=', 'ababc', 'ab']) + " will never match, no matter the input text + call add(tl, [2, 'abcd\@=e', 'abcd']) + " will never match + call add(tl, [2, 'abcd\@=e', 'any text in here ... ']) + call add(tl, [2, '\v(abc)@=..', 'xabcd', 'ab', 'abc']) + call add(tl, [2, '\(.*John\)\@=.*Bob', 'here is John, and here is B']) + call add(tl, [2, '\(John.*\)\@=.*Bob', 'John is Bobs friend', 'John is Bob', 'John is Bobs friend']) + call add(tl, [2, '\<\S\+\())\)\@=', '$((i=i+1))', 'i=i+1', '))']) + call add(tl, [2, '.*John\&.*Bob', 'here is John, and here is B']) + call add(tl, [2, '.*John\&.*Bob', 'John is Bobs friend', 'John is Bob']) + call add(tl, [2, '\v(test1)@=.*yep', 'this is a test1, yep it is', 'test1, yep', 'test1']) + call add(tl, [2, 'foo\(bar\)\@!', 'foobar']) + call add(tl, [2, 'foo\(bar\)\@!', 'foo bar', 'foo']) + call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if then else']) + call add(tl, [2, 'if \(\(then\)\@!.\)*$', ' if else ', 'if else ', ' ']) + call add(tl, [2, '\(foo\)\@!bar', 'foobar', 'bar']) + call add(tl, [2, '\(foo\)\@!...bar', 'foobar']) + call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' bar foo ']) + call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo bar ']) + call add(tl, [2, '^\%(.*bar\)\@!.*\zsfoo', ' foo xxx ', 'foo']) + call add(tl, [2, '[ ]\@!\p\%([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:']) + call add(tl, [2, '[ ]\@!\p\([ ]\@!\p\)*:', 'implicit mappings:', 'mappings:', 's']) + call add(tl, [2, 'm\k\+_\@=\%(_\@!\k\)\@<=\k\+e', 'mx__xe', 'mx__xe']) + call add(tl, [2, '\%(\U\@<=S\k*\|S\l\)R', 'SuR', 'SuR']) + + " Combining different tests and features + call add(tl, [2, '[[:alpha:]]\{-2,6}', '787abcdiuhsasiuhb4', 'ab']) + call add(tl, [2, '', 'abcd', '']) + call add(tl, [2, '\v(())', 'any possible text', '']) + call add(tl, [2, '\v%(ab(xyz)c)', ' abxyzc ', 'abxyzc', 'xyz']) + call add(tl, [2, '\v(test|)empty', 'tesempty', 'empty', '']) + call add(tl, [2, '\v(a|aa)(a|aa)', 'aaa', 'aa', 'a', 'a']) + + " \%u and friends + call add(tl, [2, '\%d32', 'yes no', ' ']) + call add(tl, [2, '\%o40', 'yes no', ' ']) + call add(tl, [2, '\%x20', 'yes no', ' ']) + call add(tl, [2, '\%u0020', 'yes no', ' ']) + call add(tl, [2, '\%U00000020', 'yes no', ' ']) + call add(tl, [2, '\%d0', "yes\x0ano", "\x0a"]) + + "" \%[abc] + call add(tl, [2, 'foo\%[bar]', 'fobar']) + call add(tl, [2, 'foo\%[bar]', 'foobar', 'foobar']) + call add(tl, [2, 'foo\%[bar]', 'fooxx', 'foo']) + call add(tl, [2, 'foo\%[bar]', 'foobxx', 'foob']) + call add(tl, [2, 'foo\%[bar]', 'foobaxx', 'fooba']) + call add(tl, [2, 'foo\%[bar]', 'foobarxx', 'foobar']) + call add(tl, [2, 'foo\%[bar]x', 'foobxx', 'foobx']) + call add(tl, [2, 'foo\%[bar]x', 'foobarxx', 'foobarx']) + call add(tl, [2, '\%[bar]x', 'barxx', 'barx']) + call add(tl, [2, '\%[bar]x', 'bxx', 'bx']) + call add(tl, [2, '\%[bar]x', 'xxx', 'x']) + call add(tl, [2, 'b\%[[ao]r]', 'bar bor', 'bar']) + call add(tl, [2, 'b\%[[]]r]', 'b]r bor', 'b]r']) + call add(tl, [2, '@\%[\w\-]*', '[@pandoc]', '@pandoc']) + + " Alternatives, must use first longest match + call add(tl, [2, 'goo\|go', 'google', 'goo']) + call add(tl, [2, '\', 'foobar']) + call add(tl, [2, '\(foo\)\@<=\>', 'barfoo', '', 'foo']) + call add(tl, [2, '\(foo\)\@<=.*', 'foobar', 'bar', 'foo']) + + " complicated look-behind match + call add(tl, [2, '\(r\@<=\|\w\@ + call add(tl, [2, '\(a*\)\@>a', 'aaaa']) + call add(tl, [2, '\(a*\)\@>b', 'aaab', 'aaab', 'aaa']) + call add(tl, [2, '^\(.\{-}b\)\@>.', ' abcbd', ' abc', ' ab']) + call add(tl, [2, '\(.\{-}\)\(\)\@>$', 'abc', 'abc', 'abc', '']) + " TODO: BT engine does not restore submatch after failure + call add(tl, [1, '\(a*\)\@>a\|a\+', 'aaaa', 'aaaa']) + + " "\_" prepended negated collection matches EOL + call add(tl, [2, '\_[^8-9]\+', "asfi\n9888", "asfi\n"]) + call add(tl, [2, '\_[^a]\+', "asfi\n9888", "sfi\n9888"]) + + " Requiring lots of states. + call add(tl, [2, '[0-9a-zA-Z]\{8}-\([0-9a-zA-Z]\{4}-\)\{3}[0-9a-zA-Z]\{12}', " 12345678-1234-1234-1234-123456789012 ", "12345678-1234-1234-1234-123456789012", "1234-"]) + + " Skip adding state twice + call add(tl, [2, '^\%(\%(^\s*#\s*if\>\|#\s*if\)\)\(\%>1c.*$\)\@=', "#if FOO", "#if", ' FOO']) + + " Test \%V atom + call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt']) + + " Run the tests + for t in tl + let re = t[0] + let pat = t[1] + let text = t[2] + let matchidx = 3 + for engine in [0, 1, 2] + if engine == 2 && re == 0 || engine == 1 && re == 1 + continue + endif + let ®expengine = engine + try + let l = matchlist(text, pat) + catch + call assert_report('Error ' . engine . ': pat: \"' . pat + \ . '\", text: \"' . text . '\", caused an exception: \"' + \ . v:exception . '\"') + endtry + " check the match itself + if len(l) == 0 && len(t) > matchidx + call assert_report('Error ' . engine . ': pat: \"' . pat + \ . '\", text: \"' . text . '\", did not match, expected: \"' + \ . t[matchidx] . '\"') + elseif len(l) > 0 && len(t) == matchidx + call assert_report('Error ' . engine . ': pat: \"' . pat + \ . '\", text: \"' . text . '\", match: \"' . l[0] + \ . '\", expected no match') + elseif len(t) > matchidx && l[0] != t[matchidx] + call assert_report('Error ' . engine . ': pat: \"' . pat + \ . '\", text: \"' . text . '\", match: \"' . l[0] + \ . '\", expected: \"' . t[matchidx] . '\"') + else + " Test passed + endif + + " check all the nine submatches + if len(l) > 0 + for i in range(1, 9) + if len(t) <= matchidx + i + let e = '' + else + let e = t[matchidx + i] + endif + if l[i] != e + call assert_report('Error ' . engine . ': pat: \"' . pat + \ . '\", text: \"' . text . '\", submatch ' . i . ': \"' + \ . l[i] . '\", expected: \"' . e . '\"') + endif + endfor + unlet i + endif + endfor + endfor + + unlet t tl e l +endfunc + +" Tests for multi-line regexp patterns without multi-byte support. +func Test_regexp_multiline_pat() + let tl = [] + + " back references + call add(tl, [2, '^.\(.\).\_..\1.', ['aaa', 'aaa', 'b'], ['XX', 'b']]) + call add(tl, [2, '\v.*\/(.*)\n.*\/\1$', ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', './Dir1/Dir2/file1.txt', './OtherDir1/OtherDir2/file1.txt'], ['./Dir1/Dir2/zyxwvuts.txt', './Dir1/Dir2/abcdefgh.bat', '', 'XX']]) + + " line breaks + call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']]) + + " Check that \_[0-9] matching EOL does not break a following \> + call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']]) + + " Check a pattern with a line break and ^ and $ + call add(tl, [2, 'a\n^b$\n^c', ['a', 'b', 'c'], ['XX']]) + + call add(tl, [2, '\(^.\+\n\)\1', [' dog', ' dog', 'asdf'], ['XXasdf']]) + + " Run the multi-line tests + for t in tl + let re = t[0] + let pat = t[1] + let before = t[2] + let after = t[3] + for engine in [0, 1, 2] + if engine == 2 && re == 0 || engine == 1 && re ==1 + continue + endif + let ®expengine = engine + new + call setline(1, before) + exe '%s/' . pat . '/XX/' + let result = getline(1, '$') + q! + if result != after + call assert_report('Error: pat: \"' . pat . '\", text: \"' + \ . string(before) . '\", expected: \"' . string(after) + \ . '\", got: \"' . string(result) . '\"') + else + " Test passed + endif + endfor + endfor + unlet t tl +endfunc + +" Check that using a pattern on two lines doesn't get messed up by using +" matchstr() with \ze in between. +func Test_matchstr_with_ze() + new + call append(0, ['Substitute here:', 'Ta 5', + \ 'Ac 7']) + call cursor(1, 1) + set re=0 + + .+1,.+2s/""/\='"' . matchstr(getline("."), '\d\+\ze<') . '"' + call assert_equal(['Substitute here:', 'Ta 5', + \ 'Ac 7', ''], getline(1, '$')) + + bwipe! +endfunc + +" Check a pattern with a look beind crossing a line boundary +func Test_lookbehind_across_line() + new + call append(0, ['Behind:', 'asdfasd" + exe "normal jV:s/\\%Va/A/g\" + exe "normal jfx\fxj:s/\\%Vo/O/g\" + call assert_equal(['Visual:', 'thexE thE thExethe', 'AndAxAnd AndAxAnd', + \ 'oooxOfOr fOrOxooo', 'oooxOfOr fOrOxooo', ''], getline(1, '$')) + bwipe! +endfunc + +" Check matching marks +func Test_matching_marks() + new + call append(0, ['', '', '', 'Marks:', 'asdfSasdfsadfEasdf', 'asdfSas', + \ 'dfsadfEasdf', '', '', '', '', '']) + call cursor(4, 1) + exe "normal jfSmsfEme:.-4,.+6s/.\\%>'s.*\\%<'e../here/\" + exe "normal jfSmsj0fEme:.-4,.+6s/.\\%>'s\\_.*\\%<'e../again/\" + call assert_equal(['', '', '', 'Marks:', 'asdfhereasdf', 'asdfagainasdf', + \ '', '', '', '', '', ''], getline(1, '$')) + bwipe! +endfunc + +" Check patterns matching cursor position. +func s:curpos_test() + new + call setline(1, ['ffooooo', 'boboooo', 'zoooooo', 'koooooo', 'moooooo', + \ "\t\t\tfoo", 'abababababababfoo', 'bababababababafoo', '********_', + \ ' xxxxxxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx']) + call setpos('.', [0, 1, 0, 0]) + s/\%>3c.//g + call setpos('.', [0, 2, 4, 0]) + s/\%#.*$//g + call setpos('.', [0, 3, 0, 0]) + s/\%<3c./_/g + %s/\%4l\%>5c./_/g + %s/\%6l\%>25v./_/g + %s/\%>6l\%3c./!/g + %s/\%>7l\%12c./?/g + %s/\%>7l\%<9l\%>5v\%<8v./#/g + $s/\%(|\u.*\)\@<=[^|\t]\+$//ge + call assert_equal(['ffo', 'bob', '__ooooo', 'koooo__', 'moooooo', + \ ' f__', 'ab!babababababfoo', + \ 'ba!ab##abab?bafoo', '**!*****_', + \ ' ! xxx?xxxxxxxx xxxx xxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxx xxxxx xxxxxxx xx xxxx xxxxxxxx xxxx xxxxxxxxxxx xxx xxxxxxx xxxxxxxxx xx xxxxxx xx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxx xxxxxxxx xxxxxxxxx xxxx xxx xxxx xxx xxx xxxxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxxxxxxx xx xxxxx xxx xxxxxxxx xxxxxx xxx xxx xxxxxxxxx xxxxxxx x xxxxxxxxx xx xxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxx xxxxxxx xxx xxx xxxxxxxx xxxxxxx xxxx xxx xxxxxx xxxxx xxxxx xx xxxxxx xxxxxxx xxx xxxxxxxxxxxx xxxx xxxxxxxxx xxxxxx xxxxxx xxxxx xxx xxxxxxx xxxxxxxxxxxxxxxx xxxxxxxxx xxxxxxxxxx xxxx xx xxxxxxxx xxx xxxxxxxxxxx xxxxx'], + \ getline(1, '$')) + bwipe! +endfunc + +func Test_matching_curpos() + set re=0 + call s:curpos_test() + set re=1 + call s:curpos_test() + set re=2 + call s:curpos_test() + set re& +endfunc + +" Test for matching the start and end of a buffer +func Test_start_end_of_buffer_match() + new + call setline(1, repeat(['vim edit'], 20)) + /\%^ + call assert_equal([0, 1, 1, 0], getpos('.')) + exe "normal 50%/\\%^..\" + call assert_equal([0, 1, 1, 0], getpos('.')) + exe "normal 50%/\\%$\" + call assert_equal([0, 20, 8, 0], getpos('.')) + exe "normal 6gg/..\\%$\" + call assert_equal([0, 20, 7, 0], getpos('.')) + bwipe! +endfunc + +" Check for detecting error +func Test_regexp_error() + set regexpengine=2 + call assert_fails("call matchlist('x x', ' \\ze*')", 'E888:') + call assert_fails("call matchlist('x x', ' \\zs*')", 'E888:') + set re& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From fcd9105018828048a8460c109650f61545b489a0 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 15 Dec 2019 17:34:54 -0500 Subject: vim-patch:8.2.0012: some undo functionality is not tested Problem: Some undo functionality is not tested. Solution: Add a few more test cases. (Dominique Pelle, closes vim/vim#5364) https://github.com/vim/vim/commit/559b9c68fe550f3af63d42e0838622aab1ceb1b3 --- src/nvim/testdir/test_undo.vim | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index e8aaecedc3..4fd4ec4e62 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -241,6 +241,26 @@ func Test_undojoin() close! endfunc +" undojoin not allowed after undo +func Test_undojoin_after_undo() + new + call feedkeys("ixx\u", 'xt') + call assert_fails(':undojoin', 'E790:') + bwipe! +endfunc + +" undojoin is a noop when no change yet, or when 'undolevels' is negative +func Test_undojoin_noop() + new + call feedkeys(":undojoin\", 'xt') + call assert_equal([''], getline(1, '$')) + setlocal undolevels=-1 + call feedkeys("ixx\u", 'xt') + call feedkeys(":undojoin\", 'xt') + call assert_equal(['xx'], getline(1, '$')) + bwipe! +endfunc + func Test_undo_write() call delete('Xtest') split Xtest @@ -327,6 +347,22 @@ func Test_undofile_earlier() call delete('Xundofile') endfunc +func Test_wundo_errors() + new + call setline(1, 'hello') + call assert_fails('wundo! Xdoesnotexist/Xundofile', 'E828:') + bwipe! +endfunc + +func Test_rundo_errors() + call assert_fails('rundo XfileDoesNotExist', 'E822:') + + call writefile(['abc'], 'Xundofile') + call assert_fails('rundo Xundofile', 'E823:') + + call delete('Xundofile') +endfunc + " Test for undo working properly when executing commands from a register. " Also test this in an empty buffer. func Test_cmd_in_reg_undo() @@ -343,6 +379,24 @@ func Test_cmd_in_reg_undo() let @a = '' endfunc +" undo or redo are noop if there is nothing to undo or redo +func Test_undo_redo_noop() + new + call assert_fails('undo 2', 'E830:') + + message clear + undo + let messages = split(execute('message'), "\n") + call assert_equal('Already at oldest change', messages[-1]) + + message clear + redo + let messages = split(execute('message'), "\n") + call assert_equal('Already at newest change', messages[-1]) + + bwipe! +endfunc + func Test_redo_empty_line() new exe "norm\x16r\x160" -- cgit From 9e6ebed6f4c695cfa00710e003d606d5d720b542 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 16 Dec 2019 19:48:57 -0500 Subject: vim-patch:8.2.0013: not using a typedef for condstack Problem: Not using a typedef for condstack. Solution: Add a typedef. https://github.com/vim/vim/commit/ddef129160ff0676e5da482071fb2fdc2988ac34 --- src/nvim/eval.c | 2 +- src/nvim/ex_cmds_defs.h | 91 +++++++++++++++++++++++++++++++++++-------------- src/nvim/ex_docmd.c | 18 +++++----- src/nvim/ex_eval.c | 35 ++++++++++--------- src/nvim/ex_eval.h | 44 ------------------------ 5 files changed, 93 insertions(+), 97 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4c76b1b2e8..902d68dcf9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -23652,7 +23652,7 @@ void ex_return(exarg_T *eap) int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { int idx; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (reanimate) /* Undo the return. */ diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 3a9fd01dd9..4a40cc54b4 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -12,31 +12,29 @@ # include "ex_cmds_enum.generated.h" #endif -/* - * When adding an Ex command: - * 1. Add an entry to the table in src/nvim/ex_cmds.lua. Keep it sorted on the - * shortest version of the command name that works. If it doesn't start with - * a lower case letter, add it at the end. - * - * Each table entry is a table with the following keys: - * - * Key | Description - * ------- | ------------------------------------------------------------- - * command | Name of the command. Required. - * enum | Name of the enum entry. If not set defaults to CMD_{command}. - * flags | A set of the flags from below list joined by bitwise or. - * func | Name of the function containing the implementation. - * - * Referenced function should be either non-static one or defined in - * ex_docmd.c and be coercible to ex_func_T type from below. - * - * All keys not described in the above table are reserved for future use. - * - * 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c. - * 3. Add an entry in the index for Ex commands at ":help ex-cmd-index". - * 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and - * long name of the command. - */ +// When adding an Ex command: +// 1. Add an entry to the table in src/nvim/ex_cmds.lua. Keep it sorted on the +// shortest version of the command name that works. If it doesn't start with +// a lower case letter, add it at the end. +// +// Each table entry is a table with the following keys: +// +// Key | Description +// ------- | ------------------------------------------------------------- +// command | Name of the command. Required. +// enum | Name of the enum entry. If not set defaults to CMD_{command}. +// flags | A set of the flags from below list joined by bitwise or. +// func | Name of the function containing the implementation. +// +// Referenced function should be either non-static one or defined in +// ex_docmd.c and be coercible to ex_func_T type from below. +// +// All keys not described in the above table are reserved for future use. +// +// 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c. +// 3. Add an entry in the index for Ex commands at ":help ex-cmd-index". +// 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and +// long name of the command. #define RANGE 0x001 /* allow a linespecs */ #define BANG 0x002 /* allow a ! after the command name */ @@ -98,6 +96,47 @@ typedef struct cmdname { int cmd_addr_type; ///< Flag for address type } CommandDefinition; +// A list used for saving values of "emsg_silent". Used by ex_try() to save the +// value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT +// flag below is set. +typedef struct eslist_elem eslist_T; +struct eslist_elem { + int saved_emsg_silent; // saved value of "emsg_silent" + eslist_T *next; // next element on the list +}; + +// For conditional commands a stack is kept of nested conditionals. +// When cs_idx < 0, there is no conditional command. +enum { + CSTACK_LEN = 50, +}; + +typedef struct { + int cs_flags[CSTACK_LEN]; // CSF_ flags + char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally" + union { + void *csp_rv[CSTACK_LEN]; // return typeval for pending return + void *csp_ex[CSTACK_LEN]; // exception for pending throw + } cs_pend; + void *cs_forinfo[CSTACK_LEN]; // info used by ":for" + int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line + int cs_idx; // current entry, or -1 if none + int cs_looplevel; // nr of nested ":while"s and ":for"s + int cs_trylevel; // nr of nested ":try"s + eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent" + int cs_lflags; // loop flags: CSL_ flags +} cstack_T; +# define cs_rettv cs_pend.csp_rv +# define cs_exception cs_pend.csp_ex + +// Flags for the cs_lflags item in cstack_T. +enum { + CSL_HAD_LOOP = 1, // just found ":while" or ":for" + CSL_HAD_ENDLOOP = 2, // just found ":endwhile" or ":endfor" + CSL_HAD_CONT = 4, // just found ":continue" + CSL_HAD_FINA = 8, // just found ":finally" +}; + /// Arguments used for Ex commands. struct exarg { char_u *arg; ///< argument of the command @@ -128,7 +167,7 @@ struct exarg { char_u *errmsg; ///< returned error message LineGetter getline; ///< Function used to get the next line void *cookie; ///< argument for getline() - struct condstack *cstack; ///< condition stack for ":if" etc. + cstack_T *cstack; ///< condition stack for ":if" etc. }; #define FORCE_BIN 1 // ":edit ++bin file" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 30c1373445..d16ad9db2c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -325,13 +325,13 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, int count = 0; /* line number count */ int did_inc = FALSE; /* incremented RedrawingDisabled */ int retval = OK; - struct condstack cstack; /* conditional stack */ - garray_T lines_ga; /* keep lines for ":while"/":for" */ - int current_line = 0; /* active line in lines_ga */ - char_u *fname = NULL; /* function or script name */ - linenr_T *breakpoint = NULL; /* ptr to breakpoint field in cookie */ - int *dbg_tick = NULL; /* ptr to dbg_tick field in cookie */ - struct dbg_stuff debug_saved; /* saved things for debug mode */ + cstack_T cstack; // conditional stack + garray_T lines_ga; // keep lines for ":while"/":for" + int current_line = 0; // active line in lines_ga + char_u *fname = NULL; // function or script name + linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie + int *dbg_tick = NULL; // ptr to dbg_tick field in cookie + struct dbg_stuff debug_saved; // saved things for debug mode int initial_trylevel; struct msglist **saved_msg_list = NULL; struct msglist *private_msg_list; @@ -361,7 +361,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, EMSG(_("E169: Command too recursive")); // When converting to an exception, we do not include the command name // since this is not an error of the specific command. - do_errthrow((struct condstack *)NULL, (char_u *)NULL); + do_errthrow((cstack_T *)NULL, (char_u *)NULL); msg_list = saved_msg_list; return FAIL; } @@ -1545,7 +1545,7 @@ static bool parse_one_cmd( */ static char_u * do_one_cmd(char_u **cmdlinep, int flags, - struct condstack *cstack, + cstack_T *cstack, LineGetter fgetline, void *cookie /* argument for fgetline() */ ) diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 28bc222827..f70a568e4a 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -307,7 +307,7 @@ void free_global_msglist(void) * error exception. If cstack is NULL, postpone the throw until do_cmdline() * has returned (see do_one_cmd()). */ -void do_errthrow(struct condstack *cstack, char_u *cmdname) +void do_errthrow(cstack_T *cstack, char_u *cmdname) { /* * Ensure that all commands in nested function calls and sourced files @@ -339,7 +339,7 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname) * exception if appropriate. Return TRUE if the current exception is discarded, * FALSE otherwise. */ -int do_intthrow(struct condstack *cstack) +int do_intthrow(cstack_T *cstack) { // If no interrupt occurred or no try conditional is active and no exception // is being thrown, do nothing (for compatibility of non-EH scripts). @@ -795,7 +795,7 @@ void ex_if(exarg_T *eap) { int skip; int result; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_idx == CSTACK_LEN - 1) eap->errmsg = (char_u *)N_("E579: :if nesting too deep"); @@ -852,7 +852,7 @@ void ex_else(exarg_T *eap) { int skip; int result; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; skip = CHECK_SKIP; @@ -926,7 +926,7 @@ void ex_while(exarg_T *eap) bool error; int skip; int result; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_idx == CSTACK_LEN - 1) eap->errmsg = (char_u *)N_("E585: :while/:for nesting too deep"); @@ -1005,7 +1005,7 @@ void ex_while(exarg_T *eap) void ex_continue(exarg_T *eap) { int idx; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) eap->errmsg = (char_u *)N_("E586: :continue without :while or :for"); @@ -1039,7 +1039,7 @@ void ex_continue(exarg_T *eap) void ex_break(exarg_T *eap) { int idx; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) eap->errmsg = (char_u *)N_("E587: :break without :while or :for"); @@ -1061,7 +1061,7 @@ void ex_break(exarg_T *eap) */ void ex_endwhile(exarg_T *eap) { - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; int idx; char_u *err; int csf; @@ -1164,7 +1164,7 @@ void ex_throw(exarg_T *eap) * for ":throw" (user exception) and error and interrupt exceptions. Also * used for rethrowing an uncaught exception. */ -void do_throw(struct condstack *cstack) +void do_throw(cstack_T *cstack) { int idx; int inactivate_try = FALSE; @@ -1225,7 +1225,7 @@ void do_throw(struct condstack *cstack) void ex_try(exarg_T *eap) { int skip; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_idx == CSTACK_LEN - 1) eap->errmsg = (char_u *)N_("E601: :try nesting too deep"); @@ -1260,7 +1260,7 @@ void ex_try(exarg_T *eap) * to save the value. */ if (emsg_silent) { - eslist_T *elem = xmalloc(sizeof(struct eslist_elem)); + eslist_T *elem = xmalloc(sizeof(*elem)); elem->saved_emsg_silent = emsg_silent; elem->next = cstack->cs_emsg_silent_list; cstack->cs_emsg_silent_list = elem; @@ -1286,7 +1286,7 @@ void ex_catch(exarg_T *eap) char_u *save_cpo; regmatch_T regmatch; int prev_got_int; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; char_u *pat; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { @@ -1432,7 +1432,7 @@ void ex_finally(exarg_T *eap) int idx; int skip = FALSE; int pending = CSTP_NONE; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) eap->errmsg = (char_u *)N_("E606: :finally without :try"); @@ -1555,7 +1555,7 @@ void ex_endtry(exarg_T *eap) int rethrow = FALSE; int pending = CSTP_NONE; void *rettv = NULL; - struct condstack *cstack = eap->cstack; + cstack_T *const cstack = eap->cstack; if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { eap->errmsg = (char_u *)N_("E602: :endtry without :try"); @@ -1882,7 +1882,7 @@ void leave_cleanup(cleanup_T *csp) * entered, is restored (used by ex_endtry()). This is normally done only * when such a try conditional is left. */ -int cleanup_conditionals(struct condstack *cstack, int searched_cond, int inclusive) +int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) { int idx; int stop = FALSE; @@ -1990,7 +1990,7 @@ int cleanup_conditionals(struct condstack *cstack, int searched_cond, int inclus /* * Return an appropriate error message for a missing endwhile/endfor/endif. */ -static char_u *get_end_emsg(struct condstack *cstack) +static char_u *get_end_emsg(cstack_T *cstack) { if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE) return e_endwhile; @@ -2007,7 +2007,8 @@ static char_u *get_end_emsg(struct condstack *cstack) * type. * Also free "for info" structures where needed. */ -void rewind_conditionals(struct condstack *cstack, int idx, int cond_type, int *cond_level) +void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, + int *cond_level) { while (cstack->cs_idx > idx) { if (cstack->cs_flags[cstack->cs_idx] & cond_type) diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 2237b6aca3..d8388c9156 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -4,42 +4,6 @@ #include "nvim/pos.h" // for linenr_T #include "nvim/ex_cmds_defs.h" // for exarg_T -/* - * A list used for saving values of "emsg_silent". Used by ex_try() to save the - * value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT - * flag below is set. - */ - -typedef struct eslist_elem eslist_T; -struct eslist_elem { - int saved_emsg_silent; /* saved value of "emsg_silent" */ - eslist_T *next; /* next element on the list */ -}; - -/* - * For conditional commands a stack is kept of nested conditionals. - * When cs_idx < 0, there is no conditional command. - */ -#define CSTACK_LEN 50 - -struct condstack { - int cs_flags[CSTACK_LEN]; // CSF_ flags - char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally" - union { - void *csp_rv[CSTACK_LEN]; // return typeval for pending return - void *csp_ex[CSTACK_LEN]; // exception for pending throw - } cs_pend; - void *cs_forinfo[CSTACK_LEN]; // info used by ":for" - int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line - int cs_idx; // current entry, or -1 if none - int cs_looplevel; // nr of nested ":while"s and ":for"s - int cs_trylevel; // nr of nested ":try"s - eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent" - int cs_lflags; // loop flags: CSL_ flags -}; -# define cs_rettv cs_pend.csp_rv -# define cs_exception cs_pend.csp_ex - /* There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" * was used. */ # define CSF_TRUE 0x0001 /* condition was TRUE */ @@ -69,14 +33,6 @@ struct condstack { # define CSTP_RETURN 24 /* ":return" is pending */ # define CSTP_FINISH 32 /* ":finish" is pending */ -/* - * Flags for the cs_lflags item in struct condstack. - */ -# define CSL_HAD_LOOP 1 /* just found ":while" or ":for" */ -# define CSL_HAD_ENDLOOP 2 /* just found ":endwhile" or ":endfor" */ -# define CSL_HAD_CONT 4 /* just found ":continue" */ -# define CSL_HAD_FINA 8 /* just found ":finally" */ - /* * A list of error messages that can be converted to an exception. "throw_msg" * is only set in the first element of the list. Usually, it points to the -- cgit From 3de1bc4bf9bd530fbeff74174d4e0ba82f92e9e4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 17 Dec 2019 01:14:22 -0500 Subject: fileio: use uint64_t for temp_count #11555 Band-aid workaround to file collision when using `tempname` for temporary batchfiles. --- src/nvim/fileio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index f518e59acc..865da25009 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -5360,7 +5360,7 @@ static bool vim_settempdir(char *tempdir) char_u *vim_tempname(void) { // Temp filename counter. - static uint32_t temp_count; + static uint64_t temp_count; char_u *tempdir = vim_gettempdir(); if (!tempdir) { @@ -5371,7 +5371,7 @@ char_u *vim_tempname(void) // and nobody else creates a file in it. char_u template[TEMP_FILE_PATH_MAXLEN]; snprintf((char *)template, TEMP_FILE_PATH_MAXLEN, - "%s%" PRIu32, tempdir, temp_count++); + "%s%" PRIu64, tempdir, temp_count++); return vim_strsave(template); } -- cgit From 74a547d340cbdf9ec60c498a9d064283cabcfb6f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 17 Dec 2019 21:38:11 -0500 Subject: vim-patch:8.2.0015: not all modeline variants are tested Problem: Not all modeline variants are tested. Solution: Add modeline tests. (Dominique Pelle, closes vim/vim#5369) https://github.com/vim/vim/commit/e74331db4b1b6a64fbbc188ff9bc472a478b9a4e --- src/nvim/testdir/test_modeline.vim | 117 +++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim index 1e196e07f0..9bdada616c 100644 --- a/src/nvim/testdir/test_modeline.vim +++ b/src/nvim/testdir/test_modeline.vim @@ -5,12 +5,30 @@ func Test_modeline_invalid() call writefile(['vi:0', 'nothing'], 'Xmodeline') let modeline = &modeline set modeline - call assert_fails('set Xmodeline', 'E518:') + call assert_fails('split Xmodeline', 'E518:') + + " Missing end colon (ignored). + call writefile(['// vim: set ts=2'], 'Xmodeline') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + " Missing colon at beginning (ignored). + call writefile(['// vim set ts=2:'], 'Xmodeline') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + " Missing space after vim (ignored). + call writefile(['// vim:ts=2:'], 'Xmodeline') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! let &modeline = modeline bwipe! call delete('Xmodeline') - endfunc +endfunc func Test_modeline_filetype() call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype') @@ -60,8 +78,99 @@ func Test_modeline_keymap() set keymap= iminsert=0 imsearch=-1 endfunc +func Test_modeline_version() + let modeline = &modeline + set modeline + + " Test with vim:{vers}: (version {vers} or later). + call writefile(['// vim' .. v:version .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(2, &ts) + bwipe! + + call writefile(['// vim' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(2, &ts) + bwipe! + + call writefile(['// vim' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bw! + + " Test with vim>{vers}: (version after {vers}). + call writefile(['// vim>' .. v:version .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + call writefile(['// vim>' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(2, &ts) + bwipe! + + call writefile(['// vim>' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + " Test with vim<{vers}: (version before {vers}). + call writefile(['// vim<' .. v:version .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + call writefile(['// vim<' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + call writefile(['// vim<' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(2, &ts) + bwipe! + + " Test with vim={vers}: (version {vers} only). + call writefile(['// vim=' .. v:version .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(2, &ts) + bwipe! + + call writefile(['// vim=' .. (v:version - 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + call writefile(['// vim=' .. (v:version + 100) .. ': ts=2:'], 'Xmodeline_version') + edit Xmodeline_version + call assert_equal(8, &ts) + bwipe! + + let &modeline = modeline + call delete('Xmodeline_version') +endfunc + +func Test_modeline_colon() + let modeline = &modeline + set modeline + + call writefile(['// vim: set showbreak=\: ts=2: sw=2'], 'Xmodeline_colon') + edit Xmodeline_colon + + " backlash colon should become colon. + call assert_equal(':', &showbreak) + + " 'ts' should be set. + " 'sw' should be ignored because it is after the end colon. + call assert_equal(2, &ts) + call assert_equal(8, &sw) + + let &modeline = modeline + call delete('Xmodeline_colon') +endfunc + func s:modeline_fails(what, text, error) - if !exists('+' . a:what) + if !exists('+' .. a:what) return endif let fname = "Xmodeline_fails_" . a:what @@ -119,7 +228,7 @@ func Test_modeline_fails_always() call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:') call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:') call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:') - call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:') + call s:modeline_fails('modelineexpr', 'modelineexpr', 'E520:') call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:') call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:') call s:modeline_fails('perldll', 'perldll=Something()', 'E520:') -- cgit From 04ebfeb8818b7f5b3d0b174c18257f1afa0bc8fd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 18 Dec 2019 05:22:58 -0500 Subject: vim-patch:8.2.0018: :join does not add white space where it should Problem: :join does not add white space where it should. (Zdenek Dohnal) Solution: Handle joining multiple lines propely. https://github.com/vim/vim/commit/91b65e49440e77222a65b6f868507453239f409b --- src/nvim/ops.c | 2 +- src/nvim/testdir/test_join.vim | 385 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 386 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 294c65ca03..0ca16e2c25 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3772,7 +3772,7 @@ int do_join(size_t count, curr = skipwhite(curr); if (*curr != NUL && *curr != ')' - && currsize != 0 + && sumsize != 0 && endcurr1 != TAB && (!has_format_option(FO_MBYTE_JOIN) || (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100)) diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim index ecb55c9af6..ac6ef8f29f 100644 --- a/src/nvim/testdir/test_join.vim +++ b/src/nvim/testdir/test_join.vim @@ -54,3 +54,388 @@ func Test_join_marks() call assert_equal([0, 4, 67, 0], getpos("']")) enew! endfunc + +" Test for joining lines and marks in them +" in compatible and nocompatible modes +" and with 'joinspaces' set or not +" and with 'cpoptions' flag 'j' set or not +func Test_join_spaces_marks() + new + " Text used for the test + insert +asdfasdf. +asdf +asdfasdf. +asdf +asdfasdf. +asdf +asdfasdf. +asdf +asdfasdf. +asdf +asdfasdf. +asdf +asdfasdf. +asdf +asdfasdf +asdf +asdfasdf +asdf +asdfasdf +asdf +asdfasdf +asdf +asdfasdf +asdf +asdfasdf +asdf +asdfasdf +asdf +zx cvn. +as dfg? +hjkl iop! +ert +zx cvn. +as dfg? +hjkl iop! +ert +. + let text = getline(1, '$') + normal gg + + set nojoinspaces + set cpoptions-=j + normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ + normal j05lmx + normal 2j06lmy + normal 2k4Jy3l$p + normal `xyl$p + normal `yy2l$p + + " set cpoptions+=j + normal j05lmx + normal 2j06lmy + normal 2k4Jy3l$p + normal `xyl$p + normal `yy2l$p + + " Expected output + let expected =<< trim [DATA] + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + zx cvn. as dfg? hjkl iop! ert ernop + zx cvn. as dfg? hjkl iop! ert ernop + [DATA] + + call assert_equal(expected, getline(1, '$')) + throw 'skipped: Nvim does not support "set compatible" or "set cpoptions+=j"' + + enew! + call append(0, text) + normal gg + + set cpoptions-=j + set joinspaces + normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ + normal j05lmx + normal 2j06lmy + normal 2k4Jy3l$p + normal `xyl$p + normal `yy2l$p + + set cpoptions+=j + normal j05lmx + normal 2j06lmy + normal 2k4Jy3l$p + normal `xyl$p + normal `yy2l$p + + " Expected output + let expected =<< trim [DATA] + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + zx cvn. as dfg? hjkl iop! ert enop + zx cvn. as dfg? hjkl iop! ert ernop + + [DATA] + + call assert_equal(expected, getline(1, '$')) + + enew! + call append(0, text) + normal gg + + set cpoptions-=j + set nojoinspaces + set compatible + + normal JjJjJjJjJjJjJjJjJjJjJjJjJjJ + normal j4Jy3l$pjdG + + " Expected output + let expected =<< trim [DATA] + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf. asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + asdfasdf asdf + zx cvn. as dfg? hjkl iop! ert a + [DATA] + + call assert_equal(expected, getline(1, '$')) + + set nocompatible + set cpoptions&vim + set joinspaces&vim + close! +endfunc + +" Test for joining lines with comments +func Test_join_lines_with_comments() + new + + " Text used by the test + insert +{ + +/* +* Make sure the previous comment leader is not removed. +*/ + +/* +* Make sure the previous comment leader is not removed. +*/ + +// Should the next comment leader be left alone? +// Yes. + +// Should the next comment leader be left alone? +// Yes. + +/* Here the comment leader should be left intact. */ +// And so should this one. + +/* Here the comment leader should be left intact. */ +// And so should this one. + +if (condition) // Remove the next comment leader! +// OK, I will. +action(); + +if (condition) // Remove the next comment leader! +// OK, I will. +action(); +} +. + + call cursor(2, 1) + set comments=s1:/*,mb:*,ex:*/,:// + set nojoinspaces fo=j + set backspace=eol,start + + .,+3join + exe "normal j4J\" + .,+2join + exe "normal j3J\" + .,+2join + exe "normal j3J\" + .,+2join + exe "normal jj3J\" + + " Expected output + let expected =<< trim [CODE] + { + /* Make sure the previous comment leader is not removed. */ + /* Make sure the previous comment leader is not removed. */ + // Should the next comment leader be left alone? Yes. + // Should the next comment leader be left alone? Yes. + /* Here the comment leader should be left intact. */ // And so should this one. + /* Here the comment leader should be left intact. */ // And so should this one. + if (condition) // Remove the next comment leader! OK, I will. + action(); + if (condition) // Remove the next comment leader! OK, I will. + action(); + } + [CODE] + + call assert_equal(expected, getline(1, '$')) + + set comments&vim + set joinspaces&vim + set fo&vim + set backspace&vim + close! +endfunc + +" Test for joining lines with different comment leaders +func Test_join_comments_2() + new + + insert +{ + +/* + * Make sure the previous comment leader is not removed. + */ + +/* + * Make sure the previous comment leader is not removed. + */ + +/* List: + * - item1 + * foo bar baz + * foo bar baz + * - item2 + * foo bar baz + * foo bar baz + */ + +/* List: + * - item1 + * foo bar baz + * foo bar baz + * - item2 + * foo bar baz + * foo bar baz + */ + +// Should the next comment leader be left alone? +// Yes. + +// Should the next comment leader be left alone? +// Yes. + +/* Here the comment leader should be left intact. */ +// And so should this one. + +/* Here the comment leader should be left intact. */ +// And so should this one. + +if (condition) // Remove the next comment leader! + // OK, I will. + action(); + +if (condition) // Remove the next comment leader! + // OK, I will. + action(); + +int i = 7 /* foo *// 3 + // comment + ; + +int i = 7 /* foo *// 3 + // comment + ; + +># Note that the last character of the ending comment leader (left angle + # bracket) is a comment leader itself. Make sure that this comment leader is + # not removed from the next line #< +< On this line a new comment is opened which spans 2 lines. This comment should +< retain its comment leader. + +># Note that the last character of the ending comment leader (left angle + # bracket) is a comment leader itself. Make sure that this comment leader is + # not removed from the next line #< +< On this line a new comment is opened which spans 2 lines. This comment should +< retain its comment leader. + +} +. + + call cursor(2, 1) + set comments=sO:*\ -,mO:*\ \ ,exO:*/ + set comments+=s1:/*,mb:*,ex:*/,:// + set comments+=s1:>#,mb:#,ex:#<,:< + set cpoptions-=j joinspaces fo=j + set backspace=eol,start + + .,+3join + exe "normal j4J\" + .,+8join + exe "normal j9J\" + .,+2join + exe "normal j3J\" + .,+2join + exe "normal j3J\" + .,+2join + exe "normal jj3J\j" + .,+2join + exe "normal jj3J\j" + .,+5join + exe "normal j6J\" + exe "normal oSome code!\// Make sure backspacing does not remove this comment leader.\0i\\" + + " Expected output + let expected =<< trim [CODE] + { + /* Make sure the previous comment leader is not removed. */ + /* Make sure the previous comment leader is not removed. */ + /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */ + /* List: item1 foo bar baz foo bar baz item2 foo bar baz foo bar baz */ + // Should the next comment leader be left alone? Yes. + // Should the next comment leader be left alone? Yes. + /* Here the comment leader should be left intact. */ // And so should this one. + /* Here the comment leader should be left intact. */ // And so should this one. + if (condition) // Remove the next comment leader! OK, I will. + action(); + if (condition) // Remove the next comment leader! OK, I will. + action(); + int i = 7 /* foo *// 3 // comment + ; + int i = 7 /* foo *// 3 // comment + ; + ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader. + ># Note that the last character of the ending comment leader (left angle bracket) is a comment leader itself. Make sure that this comment leader is not removed from the next line #< < On this line a new comment is opened which spans 2 lines. This comment should retain its comment leader. + + Some code!// Make sure backspacing does not remove this comment leader. + } + [CODE] + + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +func Test_join_lines() + new + call setline(1, ['a', 'b', '', 'c', 'd']) + %join + call assert_equal('a b c d', getline(1)) + call setline(1, ['a', 'b', '', 'c', 'd']) + normal 5J + call assert_equal('a b c d', getline(1)) + bwipe! +endfunc -- cgit From 35813c62ed94ba86cb3448165160a6e0f7ef47f5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 18 Dec 2019 07:57:40 -0500 Subject: test/old: skip Test_screenpos for now It fails in CI. --- src/nvim/testdir/test_cursor_func.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 037918fa31..e8e561dfd8 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -66,6 +66,7 @@ func Test_curswant_with_cursorline() endfunc func Test_screenpos() + throw 'skipped: TODO: ' rightbelow new rightbelow 20vsplit call setline(1, ["\tsome text", "long wrapping line here", "next line"]) -- cgit From 65aca4d857ab9ff278f410b17ef31d91e48a37b7 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Thu, 19 Dec 2019 21:27:21 +0100 Subject: TUI: can make the cursor transparent #11519 when setting 'guicursor' highlight blend=100. --- src/nvim/tui/tui.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 60e1353000..e168cf079a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -100,7 +100,7 @@ typedef struct { bool immediate_wrap_after_last_column; bool bce; bool mouse_enabled; - bool busy, is_invisible; + bool busy, is_invisible, want_invisible; bool cork, overflow; bool cursor_color_changed; bool is_starting; @@ -198,6 +198,7 @@ static void terminfo_start(UI *ui) data->default_attr = false; data->can_clear_attr = false; data->is_invisible = true; + data->want_invisible = false; data->busy = false; data->cork = false; data->overflow = false; @@ -1032,7 +1033,11 @@ static void tui_set_mode(UI *ui, ModeShape mode) if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) { HlAttrs aep = kv_A(data->attrs, c.id); - if (aep.rgb_ae_attr & HL_INVERSE) { + + data->want_invisible = aep.hl_blend == 100; + if (data->want_invisible) { + unibi_out(ui, unibi_cursor_invisible); + } else if (aep.rgb_ae_attr & HL_INVERSE) { // We interpret "inverse" as "default" (no termcode for "inverse"...). // Hopefully the user's default cursor color is inverse. unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); @@ -1980,10 +1985,12 @@ static void flush_buf(UI *ui) assert(data->is_invisible); // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. - bufp->base = data->norm; - bufp->len = UV_BUF_LEN(data->normlen); - bufp++; - data->is_invisible = data->busy; + if (!data->want_invisible) { + bufp->base = data->norm; + bufp->len = UV_BUF_LEN(data->normlen); + bufp++; + } + data->is_invisible = false; } uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), -- cgit From c147806e23d0ade781b8a298924150fe8d5887ad Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Dec 2019 00:02:36 -0500 Subject: vim-patch:8.2.0019: cannot number of lines of another buffer Problem: Cannot number of lines of another buffer. Solution: Add "linecount" to getbufinfo(). (Yasuhiro Matsumoto, closes vim/vim#5370) https://github.com/vim/vim/commit/a9e9679de3ef082ee29868ab404283dfc53258f2 --- src/nvim/eval.c | 1 + src/nvim/testdir/test_bufwintabinfo.vim | 10 ++++++++++ 2 files changed, 11 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 902d68dcf9..04899f2c99 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9922,6 +9922,7 @@ static dict_T *get_buffer_info(buf_T *buf) buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); tv_dict_add_nr(dict, S_LEN("lnum"), buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); + tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 0e8c7d1dc1..176d49d28e 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -139,3 +139,13 @@ function Test_get_win_options() set foldlevel=0 endif endfunc + +func Test_getbufinfo_lines() + new Xfoo + call setline(1, ['a', 'bc', 'd']) + let bn = bufnr('%') + hide + call assert_equal(3, getbufinfo(bn)[0]["linecount"]) + edit Xfoo + bw! +endfunc -- cgit From a115f2314387706242ce5b9e5b2d7090a902d9cb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 20 Dec 2019 00:31:56 -0500 Subject: vim-patch:8.2.0024: filetype Rego not recognized Problem: Filetype Rego not recognized. Solution: Add *.rego. (Matt Dunford, closes vim/vim#5376) https://github.com/vim/vim/commit/a4ce82fe2e990eb9eaabf6ad400e2a74cce50ce6 --- src/nvim/testdir/test_filetype.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index e085f58e56..cc0037b4cf 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -364,6 +364,7 @@ let s:filename_checks = { \ 'rcs': ['file,v'], \ 'readline': ['.inputrc', 'inputrc'], \ 'remind': ['.reminders', 'file.remind', 'file.rem'], + \ 'rego': ['file.rego'], \ 'resolv': ['resolv.conf'], \ 'reva': ['file.frt'], \ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'], -- cgit From 138480939a6d696d77b866474bcdda054d8fbfa1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 21 Dec 2019 07:55:15 -0500 Subject: vim-patch:8.2.0025: repeated word in comment (#11586) Problem: Repeated word in comment. Solution: Remove one. (Rene Nyffenegger, closes vim/vim#5384) https://github.com/vim/vim/commit/fe72d08400d9064b3f959f1f62f279527e64835a --- src/nvim/eval/typval.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 0b04170cac..02d241f6f5 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -246,7 +246,7 @@ typedef int scid_T; /// Format argument for scid_T #define PRIdSCID "d" -// SCript ConteXt (SCTX): identifies a script script line. +// SCript ConteXt (SCTX): identifies a script line. // When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current // line number. When executing a user function "sc_lnum" is the line where the // function was defined, "sourcing_lnum" is the line number inside the -- cgit From 64248e64a0d49d6781e2ab22d00f4155981a5c98 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 22 Dec 2019 04:41:11 +0100 Subject: tests: sync Test_undojoin_redo from Vim #11589 This was not added in e0e482589 with Vim patch 8.0.0205. --- src/nvim/testdir/test_undo.vim | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 4fd4ec4e62..adcdcb1cd9 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -238,7 +238,17 @@ func Test_undojoin() call assert_equal(['aaaa', 'bbbb', 'cccc'], getline(2, '$')) call feedkeys("u", 'xt') call assert_equal(['aaaa'], getline(2, '$')) - close! + bwipe! +endfunc + +func Test_undojoin_redo() + new + call setline(1, ['first line', 'second line']) + call feedkeys("ixx\", 'xt') + call feedkeys(":undojoin | redo\", 'xt') + call assert_equal('xxfirst line', getline(1)) + call assert_equal('second line', getline(2)) + bwipe! endfunc " undojoin not allowed after undo -- cgit From 79bd8d2ab6cae1c0be3233a9a7551d0b7bcc5944 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 28 Sep 2019 18:41:49 +0200 Subject: tree-sitter: update vendored tree-sitter runtime tree-sitter/tree-sitter commit edb569310005c66838b7d69fa60850acac6abeee Included files are: lib/include/tree-sitter/*.h lib/src/*.[ch] lib/src/unicode/* LICENSE --- src/tree_sitter/api.h | 241 +++++- src/tree_sitter/bits.h | 29 + src/tree_sitter/language.c | 102 ++- src/tree_sitter/language.h | 3 + src/tree_sitter/lexer.c | 334 +++++---- src/tree_sitter/lexer.h | 2 +- src/tree_sitter/lib.c | 5 +- src/tree_sitter/node.c | 10 +- src/tree_sitter/parser.c | 41 +- src/tree_sitter/parser.h | 5 +- src/tree_sitter/point.h | 1 + src/tree_sitter/query.c | 1450 ++++++++++++++++++++++++++++++++++++ src/tree_sitter/subtree.c | 13 +- src/tree_sitter/tree.c | 7 +- src/tree_sitter/tree_cursor.c | 79 +- src/tree_sitter/tree_cursor.h | 1 + src/tree_sitter/unicode.h | 50 ++ src/tree_sitter/unicode/ICU_SHA | 1 + src/tree_sitter/unicode/LICENSE | 414 ++++++++++ src/tree_sitter/unicode/README.md | 29 + src/tree_sitter/unicode/ptypes.h | 1 + src/tree_sitter/unicode/umachine.h | 448 +++++++++++ src/tree_sitter/unicode/urename.h | 1 + src/tree_sitter/unicode/utf.h | 1 + src/tree_sitter/unicode/utf16.h | 733 ++++++++++++++++++ src/tree_sitter/unicode/utf8.h | 881 ++++++++++++++++++++++ 26 files changed, 4653 insertions(+), 229 deletions(-) create mode 100644 src/tree_sitter/bits.h create mode 100644 src/tree_sitter/query.c create mode 100644 src/tree_sitter/unicode.h create mode 100644 src/tree_sitter/unicode/ICU_SHA create mode 100644 src/tree_sitter/unicode/LICENSE create mode 100644 src/tree_sitter/unicode/README.md create mode 100644 src/tree_sitter/unicode/ptypes.h create mode 100644 src/tree_sitter/unicode/umachine.h create mode 100644 src/tree_sitter/unicode/urename.h create mode 100644 src/tree_sitter/unicode/utf.h create mode 100644 src/tree_sitter/unicode/utf16.h create mode 100644 src/tree_sitter/unicode/utf8.h (limited to 'src') diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h index d39d0521ee..40187e3db0 100644 --- a/src/tree_sitter/api.h +++ b/src/tree_sitter/api.h @@ -14,7 +14,19 @@ extern "C" { /* Section - ABI Versioning */ /****************************/ +/** + * The latest ABI version that is supported by the current version of the + * library. When Languages are generated by the Tree-sitter CLI, they are + * assigned an ABI version number that corresponds to the current CLI version. + * The Tree-sitter library is generally backwards-compatible with languages + * generated using older CLI versions, but is not forwards-compatible. + */ #define TREE_SITTER_LANGUAGE_VERSION 11 + +/** + * The earliest ABI version that is supported by the current version of the + * library. + */ #define TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION 9 /*******************/ @@ -26,6 +38,8 @@ typedef uint16_t TSFieldId; typedef struct TSLanguage TSLanguage; typedef struct TSParser TSParser; typedef struct TSTree TSTree; +typedef struct TSQuery TSQuery; +typedef struct TSQueryCursor TSQueryCursor; typedef enum { TSInputEncodingUTF8, @@ -87,6 +101,37 @@ typedef struct { uint32_t context[2]; } TSTreeCursor; +typedef struct { + TSNode node; + uint32_t index; +} TSQueryCapture; + +typedef struct { + uint32_t id; + uint16_t pattern_index; + uint16_t capture_count; + const TSQueryCapture *captures; +} TSQueryMatch; + +typedef enum { + TSQueryPredicateStepTypeDone, + TSQueryPredicateStepTypeCapture, + TSQueryPredicateStepTypeString, +} TSQueryPredicateStepType; + +typedef struct { + TSQueryPredicateStepType type; + uint32_t value_id; +} TSQueryPredicateStep; + +typedef enum { + TSQueryErrorNone = 0, + TSQueryErrorSyntax, + TSQueryErrorNodeType, + TSQueryErrorField, + TSQueryErrorCapture, +} TSQueryError; + /********************/ /* Section - Parser */ /********************/ @@ -119,7 +164,7 @@ bool ts_parser_set_language(TSParser *self, const TSLanguage *language); const TSLanguage *ts_parser_language(const TSParser *self); /** - * Set the spans of text that the parser should include when parsing. + * Set the ranges of text that the parser should include when parsing. * * By default, the parser will always include entire documents. This function * allows you to parse only a *portion* of a document but still return a syntax @@ -226,14 +271,16 @@ TSTree *ts_parser_parse_string_encoding( * by default, it will resume where it left off on the next call to * `ts_parser_parse` or other parsing functions. If you don't want to resume, * and instead intend to use this parser to parse some other document, you must - * call this `ts_parser_reset` first. + * call `ts_parser_reset` first. */ void ts_parser_reset(TSParser *self); /** * Set the maximum duration in microseconds that parsing should be allowed to - * take before halting. If parsing takes longer than this, it will halt early, - * returning NULL. See `ts_parser_parse` for more information. + * take before halting. + * + * If parsing takes longer than this, it will halt early, returning NULL. + * See `ts_parser_parse` for more information. */ void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout); @@ -243,10 +290,11 @@ void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout); uint64_t ts_parser_timeout_micros(const TSParser *self); /** - * Set the parser's current cancellation flag pointer. If a non-null pointer is - * assigned, then the parser will periodically read from this pointer during - * parsing. If it reads a non-zero value, it will halt early, returning NULL. - * See `ts_parser_parse` for more information. + * Set the parser's current cancellation flag pointer. + * + * If a non-null pointer is assigned, then the parser will periodically read + * from this pointer during parsing. If it reads a non-zero value, it will + * halt early, returning NULL. See `ts_parser_parse` for more information. */ void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag); @@ -322,22 +370,22 @@ const TSLanguage *ts_tree_language(const TSTree *); void ts_tree_edit(TSTree *self, const TSInputEdit *edit); /** - * Compare a new syntax tree to a previous syntax tree representing the same + * Compare an old edited syntax tree to a new syntax tree representing the same * document, returning an array of ranges whose syntactic structure has changed. * * For this to work correctly, the old syntax tree must have been edited such * that its ranges match up to the new tree. Generally, you'll want to call - * this function right after calling one of the `ts_parser_parse` functions, - * passing in the new tree that was returned from `ts_parser_parse` and the old - * tree that was passed as a parameter. + * this function right after calling one of the `ts_parser_parse` functions. + * You need to pass the old tree that was passed to parse, as well as the new + * tree that was returned from that function. * * The returned array is allocated using `malloc` and the caller is responsible * for freeing it using `free`. The length of the array will be written to the * given `length` pointer. */ TSRange *ts_tree_get_changed_ranges( - const TSTree *self, const TSTree *old_tree, + const TSTree *new_tree, uint32_t *length ); @@ -409,8 +457,8 @@ bool ts_node_is_named(TSNode); bool ts_node_is_missing(TSNode); /** - * Check if the node is *missing*. Missing nodes are inserted by the parser in - * order to recover from certain kinds of syntax errors. + * Check if the node is *extra*. Extra nodes represent things like comments, + * which are not required the grammar, but can appear anywhere. */ bool ts_node_is_extra(TSNode); @@ -542,7 +590,7 @@ TSTreeCursor ts_tree_cursor_new(TSNode); void ts_tree_cursor_delete(TSTreeCursor *); /** - * Re-initialize a tree cursor to start at a different ndoe. + * Re-initialize a tree cursor to start at a different node. */ void ts_tree_cursor_reset(TSTreeCursor *, TSNode); @@ -584,7 +632,7 @@ bool ts_tree_cursor_goto_parent(TSTreeCursor *); bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); /** - * Move the cursor to the first schild of its current node. + * Move the cursor to the first child of its current node. * * This returns `true` if the cursor successfully moved, and returns `false` * if there were no children. @@ -592,7 +640,7 @@ bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *); bool ts_tree_cursor_goto_first_child(TSTreeCursor *); /** - * Move the cursor to the first schild of its current node that extends beyond + * Move the cursor to the first child of its current node that extends beyond * the given byte offset. * * This returns the index of the child node if one was found, and returns -1 @@ -602,6 +650,156 @@ int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t); TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *); +/*******************/ +/* Section - Query */ +/*******************/ + +/** + * Create a new query from a string containing one or more S-expression + * patterns. The query is associated with a particular language, and can + * only be run on syntax nodes parsed with that language. + * + * If all of the given patterns are valid, this returns a `TSQuery`. + * If a pattern is invalid, this returns `NULL`, and provides two pieces + * of information about the problem: + * 1. The byte offset of the error is written to the `error_offset` parameter. + * 2. The type of error is written to the `error_type` parameter. + */ +TSQuery *ts_query_new( + const TSLanguage *language, + const char *source, + uint32_t source_len, + uint32_t *error_offset, + TSQueryError *error_type +); + +/** + * Delete a query, freeing all of the memory that it used. + */ +void ts_query_delete(TSQuery *); + +/** + * Get the number of patterns, captures, or string literals in the query. + */ +uint32_t ts_query_pattern_count(const TSQuery *); +uint32_t ts_query_capture_count(const TSQuery *); +uint32_t ts_query_string_count(const TSQuery *); + +/** + * Get the byte offset where the given pattern starts in the query's source. + * + * This can be useful when combining queries by concatenating their source + * code strings. + */ +uint32_t ts_query_start_byte_for_pattern(const TSQuery *, uint32_t); + +/** + * Get all of the predicates for the given pattern in the query. + * + * The predicates are represented as a single array of steps. There are three + * types of steps in this array, which correspond to the three legal values for + * the `type` field: + * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names + * of captures. Their `value_id` can be used with the + * `ts_query_capture_name_for_id` function to obtain the name of the capture. + * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal + * strings. Their `value_id` can be used with the + * `ts_query_string_value_for_id` function to obtain their string value. + * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels* + * that represent the end of an individual predicate. If a pattern has two + * predicates, then there will be two steps with this `type` in the array. + */ +const TSQueryPredicateStep *ts_query_predicates_for_pattern( + const TSQuery *self, + uint32_t pattern_index, + uint32_t *length +); + +/** + * Get the name and length of one of the query's captures, or one of the + * query's string literals. Each capture and string is associated with a + * numeric id based on the order that it appeared in the query's source. + */ +const char *ts_query_capture_name_for_id( + const TSQuery *, + uint32_t id, + uint32_t *length +); +const char *ts_query_string_value_for_id( + const TSQuery *, + uint32_t id, + uint32_t *length +); + +/** + * Disable a certain capture within a query. This prevents the capture + * from being returned in matches, and also avoids any resource usage + * associated with recording the capture. + */ +void ts_query_disable_capture(TSQuery *, const char *, uint32_t); + +/** + * Create a new cursor for executing a given query. + * + * The cursor stores the state that is needed to iteratively search + * for matches. To use the query cursor, first call `ts_query_cursor_exec` + * to start running a given query on a given syntax node. Then, there are + * two options for consuming the results of the query: + * 1. Repeatedly call `ts_query_cursor_next_match` to iterate over all of the + * the *matches* in the order that they were found. Each match contains the + * index of the pattern that matched, and an array of captures. Because + * multiple patterns can match the same set of nodes, one match may contain + * captures that appear *before* some of the captures from a previous match. + * 2. Repeatedly call `ts_query_cursor_next_capture` to iterate over all of the + * individual *captures* in the order that they appear. This is useful if + * don't care about which pattern matched, and just want a single ordered + * sequence of captures. + * + * If you don't care about consuming all of the results, you can stop calling + * `ts_query_cursor_next_match` or `ts_query_cursor_next_capture` at any point. + * You can then start executing another query on another node by calling + * `ts_query_cursor_exec` again. + */ +TSQueryCursor *ts_query_cursor_new(void); + +/** + * Delete a query cursor, freeing all of the memory that it used. + */ +void ts_query_cursor_delete(TSQueryCursor *); + +/** + * Start running a given query on a given node. + */ +void ts_query_cursor_exec(TSQueryCursor *, const TSQuery *, TSNode); + +/** + * Set the range of bytes or (row, column) positions in which the query + * will be executed. + */ +void ts_query_cursor_set_byte_range(TSQueryCursor *, uint32_t, uint32_t); +void ts_query_cursor_set_point_range(TSQueryCursor *, TSPoint, TSPoint); + +/** + * Advance to the next match of the currently running query. + * + * If there is a match, write it to `*match` and return `true`. + * Otherwise, return `false`. + */ +bool ts_query_cursor_next_match(TSQueryCursor *, TSQueryMatch *match); +void ts_query_cursor_remove_match(TSQueryCursor *, uint32_t id); + +/** + * Advance to the next capture of the currently running query. + * + * If there is a capture, write its match to `*match` and its index within + * the matche's capture list to `*capture_index`. Otherwise, return `false`. + */ +bool ts_query_cursor_next_capture( + TSQueryCursor *, + TSQueryMatch *match, + uint32_t *capture_index +); + /**********************/ /* Section - Language */ /**********************/ @@ -619,7 +817,12 @@ const char *ts_language_symbol_name(const TSLanguage *, TSSymbol); /** * Get the numerical id for the given node type string. */ -TSSymbol ts_language_symbol_for_name(const TSLanguage *, const char *); +TSSymbol ts_language_symbol_for_name( + const TSLanguage *self, + const char *string, + uint32_t length, + bool is_named +); /** * Get the number of distinct field names in the language. diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h new file mode 100644 index 0000000000..3bec455dd1 --- /dev/null +++ b/src/tree_sitter/bits.h @@ -0,0 +1,29 @@ +#ifndef TREE_SITTER_BITS_H_ +#define TREE_SITTER_BITS_H_ + +#include + +static inline uint32_t bitmask_for_index(uint16_t id) { + return (1u << (31 - id)); +} + +#ifdef _WIN32 + +#include + +static inline uint32_t count_leading_zeros(uint32_t x) { + if (x == 0) return 32; + uint32_t result; + _BitScanReverse(&result, x); + return 31 - result; +} + +#else + +static inline uint32_t count_leading_zeros(uint32_t x) { + if (x == 0) return 32; + return __builtin_clz(x); +} + +#endif +#endif // TREE_SITTER_BITS_H_ diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c index 1bfb1a8d03..e240ef2a53 100644 --- a/src/tree_sitter/language.c +++ b/src/tree_sitter/language.c @@ -3,8 +3,28 @@ #include "./error_costs.h" #include -void ts_language_table_entry(const TSLanguage *self, TSStateId state, - TSSymbol symbol, TableEntry *result) { +uint32_t ts_language_symbol_count(const TSLanguage *self) { + return self->symbol_count + self->alias_count; +} + +uint32_t ts_language_version(const TSLanguage *self) { + return self->version; +} + +uint32_t ts_language_field_count(const TSLanguage *self) { + if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) { + return self->field_count; + } else { + return 0; + } +} + +void ts_language_table_entry( + const TSLanguage *self, + TSStateId state, + TSSymbol symbol, + TableEntry *result +) { if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) { result->action_count = 0; result->is_reusable = false; @@ -19,48 +39,72 @@ void ts_language_table_entry(const TSLanguage *self, TSStateId state, } } -uint32_t ts_language_symbol_count(const TSLanguage *language) { - return language->symbol_count + language->alias_count; -} - -uint32_t ts_language_version(const TSLanguage *language) { - return language->version; -} - -TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *language, TSSymbol symbol) { +TSSymbolMetadata ts_language_symbol_metadata( + const TSLanguage *self, + TSSymbol symbol +) { if (symbol == ts_builtin_sym_error) { return (TSSymbolMetadata){.visible = true, .named = true}; } else if (symbol == ts_builtin_sym_error_repeat) { return (TSSymbolMetadata){.visible = false, .named = false}; } else { - return language->symbol_metadata[symbol]; + return self->symbol_metadata[symbol]; + } +} + +TSSymbol ts_language_public_symbol( + const TSLanguage *self, + TSSymbol symbol +) { + if (symbol == ts_builtin_sym_error) return symbol; + if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { + return self->public_symbol_map[symbol]; + } else { + return symbol; } } -const char *ts_language_symbol_name(const TSLanguage *language, TSSymbol symbol) { +const char *ts_language_symbol_name( + const TSLanguage *self, + TSSymbol symbol +) { if (symbol == ts_builtin_sym_error) { return "ERROR"; } else if (symbol == ts_builtin_sym_error_repeat) { return "_ERROR"; } else { - return language->symbol_names[symbol]; + return self->symbol_names[symbol]; } } -TSSymbol ts_language_symbol_for_name(const TSLanguage *self, const char *name) { - if (!strcmp(name, "ERROR")) return ts_builtin_sym_error; - +TSSymbol ts_language_symbol_for_name( + const TSLanguage *self, + const char *string, + uint32_t length, + bool is_named +) { + if (!strncmp(string, "ERROR", length)) return ts_builtin_sym_error; uint32_t count = ts_language_symbol_count(self); for (TSSymbol i = 0; i < count; i++) { - if (!strcmp(self->symbol_names[i], name)) { - return i; + TSSymbolMetadata metadata = ts_language_symbol_metadata(self, i); + if (!metadata.visible || metadata.named != is_named) continue; + const char *symbol_name = self->symbol_names[i]; + if (!strncmp(symbol_name, string, length) && !symbol_name[length]) { + if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { + return self->public_symbol_map[i]; + } else { + return i; + } } } return 0; } -TSSymbolType ts_language_symbol_type(const TSLanguage *language, TSSymbol symbol) { - TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol); +TSSymbolType ts_language_symbol_type( + const TSLanguage *self, + TSSymbol symbol +) { + TSSymbolMetadata metadata = ts_language_symbol_metadata(self, symbol); if (metadata.named) { return TSSymbolTypeRegular; } else if (metadata.visible) { @@ -70,15 +114,10 @@ TSSymbolType ts_language_symbol_type(const TSLanguage *language, TSSymbol symbol } } -uint32_t ts_language_field_count(const TSLanguage *self) { - if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) { - return self->field_count; - } else { - return 0; - } -} - -const char *ts_language_field_name_for_id(const TSLanguage *self, TSFieldId id) { +const char *ts_language_field_name_for_id( + const TSLanguage *self, + TSFieldId id +) { uint32_t count = ts_language_field_count(self); if (count) { return self->field_names[id]; @@ -96,7 +135,8 @@ TSFieldId ts_language_field_id_for_name( for (TSSymbol i = 1; i < count + 1; i++) { switch (strncmp(name, self->field_names[i], name_length)) { case 0: - return i; + if (self->field_names[i][name_length] == 0) return i; + break; case -1: return 0; default: diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h index 0741486a1b..d7e17c3d70 100644 --- a/src/tree_sitter/language.h +++ b/src/tree_sitter/language.h @@ -10,6 +10,7 @@ extern "C" { #define ts_builtin_sym_error_repeat (ts_builtin_sym_error - 1) #define TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS 10 +#define TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING 11 #define TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES 11 typedef struct { @@ -22,6 +23,8 @@ void ts_language_table_entry(const TSLanguage *, TSStateId, TSSymbol, TableEntry TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol); +TSSymbol ts_language_public_symbol(const TSLanguage *, TSSymbol); + static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) { return 0 < symbol && symbol < self->external_token_count + 1; } diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c index fdc127466f..e2ca851973 100644 --- a/src/tree_sitter/lexer.c +++ b/src/tree_sitter/lexer.c @@ -2,26 +2,58 @@ #include "./lexer.h" #include "./subtree.h" #include "./length.h" -#include "./utf16.h" -#include "utf8proc.h" - -#define LOG(...) \ - if (self->logger.log) { \ - snprintf(self->debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \ - self->logger.log(self->logger.payload, TSLogTypeLex, self->debug_buffer); \ +#include "./unicode.h" + +#define LOG(message, character) \ + if (self->logger.log) { \ + snprintf( \ + self->debug_buffer, \ + TREE_SITTER_SERIALIZATION_BUFFER_SIZE, \ + 32 <= character && character < 127 ? \ + message " character:'%c'" : \ + message " character:%d", \ + character \ + ); \ + self->logger.log( \ + self->logger.payload, \ + TSLogTypeLex, \ + self->debug_buffer \ + ); \ } -#define LOG_CHARACTER(message, character) \ - LOG( \ - 32 <= character && character < 127 ? \ - message " character:'%c'" : \ - message " character:%d", character \ - ) +static const int32_t BYTE_ORDER_MARK = 0xFEFF; -static const char empty_chunk[3] = { 0, 0 }; +static const TSRange DEFAULT_RANGE = { + .start_point = { + .row = 0, + .column = 0, + }, + .end_point = { + .row = UINT32_MAX, + .column = UINT32_MAX, + }, + .start_byte = 0, + .end_byte = UINT32_MAX +}; -static const int32_t BYTE_ORDER_MARK = 0xFEFF; +// Check if the lexer has reached EOF. This state is stored +// by setting the lexer's `current_included_range_index` such that +// it has consumed all of its available ranges. +static bool ts_lexer__eof(const TSLexer *_self) { + Lexer *self = (Lexer *)_self; + return self->current_included_range_index == self->included_range_count; +} + +// Clear the currently stored chunk of source code, because the lexer's +// position has changed. +static void ts_lexer__clear_chunk(Lexer *self) { + self->chunk = NULL; + self->chunk_size = 0; + self->chunk_start = 0; +} +// Call the lexer's input callback to obtain a new chunk of source code +// for the current position. static void ts_lexer__get_chunk(Lexer *self) { self->chunk_start = self->current_position.bytes; self->chunk = self->input.read( @@ -30,15 +62,15 @@ static void ts_lexer__get_chunk(Lexer *self) { self->current_position.extent, &self->chunk_size ); - if (!self->chunk_size) self->chunk = empty_chunk; + if (!self->chunk_size) { + self->current_included_range_index = self->included_range_count; + self->chunk = NULL; + } } -typedef utf8proc_ssize_t (*DecodeFunction)( - const utf8proc_uint8_t *, - utf8proc_ssize_t, - utf8proc_int32_t * -); - +// Decode the next unicode character in the current chunk of source code. +// This assumes that the lexer has already retrieved a chunk of source +// code that spans the current position. static void ts_lexer__get_lookahead(Lexer *self) { uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start; const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk; @@ -50,29 +82,37 @@ static void ts_lexer__get_lookahead(Lexer *self) { return; } - DecodeFunction decode = - self->input.encoding == TSInputEncodingUTF8 ? utf8proc_iterate : utf16_iterate; + UnicodeDecodeFunction decode = self->input.encoding == TSInputEncodingUTF8 + ? ts_decode_utf8 + : ts_decode_utf16; self->lookahead_size = decode(chunk, size, &self->data.lookahead); // If this chunk ended in the middle of a multi-byte character, // try again with a fresh chunk. - if (self->data.lookahead == -1 && size < 4) { + if (self->data.lookahead == TS_DECODE_ERROR && size < 4) { ts_lexer__get_chunk(self); chunk = (const uint8_t *)self->chunk; size = self->chunk_size; self->lookahead_size = decode(chunk, size, &self->data.lookahead); } - if (self->data.lookahead == -1) { + if (self->data.lookahead == TS_DECODE_ERROR) { self->lookahead_size = 1; } } -static void ts_lexer__advance(TSLexer *payload, bool skip) { - Lexer *self = (Lexer *)payload; - if (self->chunk == empty_chunk) - return; +// Advance to the next character in the source code, retrieving a new +// chunk of source code if needed. +static void ts_lexer__advance(TSLexer *_self, bool skip) { + Lexer *self = (Lexer *)_self; + if (!self->chunk) return; + + if (skip) { + LOG("skip", self->data.lookahead); + } else { + LOG("consume", self->data.lookahead); + } if (self->lookahead_size) { self->current_position.bytes += self->lookahead_size; @@ -84,53 +124,65 @@ static void ts_lexer__advance(TSLexer *payload, bool skip) { } } - TSRange *current_range = &self->included_ranges[self->current_included_range_index]; - if (self->current_position.bytes == current_range->end_byte) { - self->current_included_range_index++; - if (self->current_included_range_index == self->included_range_count) { - self->data.lookahead = '\0'; - self->lookahead_size = 1; - return; - } else { - current_range++; - self->current_position = (Length) { - current_range->start_byte, - current_range->start_point, - }; + const TSRange *current_range = NULL; + if (self->current_included_range_index < self->included_range_count) { + current_range = &self->included_ranges[self->current_included_range_index]; + if (self->current_position.bytes == current_range->end_byte) { + self->current_included_range_index++; + if (self->current_included_range_index < self->included_range_count) { + current_range++; + self->current_position = (Length) { + current_range->start_byte, + current_range->start_point, + }; + } else { + current_range = NULL; + } } } - if (skip) { - LOG_CHARACTER("skip", self->data.lookahead); - self->token_start_position = self->current_position; - } else { - LOG_CHARACTER("consume", self->data.lookahead); - } + if (skip) self->token_start_position = self->current_position; - if (self->current_position.bytes >= self->chunk_start + self->chunk_size) { - ts_lexer__get_chunk(self); + if (current_range) { + if (self->current_position.bytes >= self->chunk_start + self->chunk_size) { + ts_lexer__get_chunk(self); + } + ts_lexer__get_lookahead(self); + } else { + ts_lexer__clear_chunk(self); + self->data.lookahead = '\0'; + self->lookahead_size = 1; } - - ts_lexer__get_lookahead(self); } -static void ts_lexer__mark_end(TSLexer *payload) { - Lexer *self = (Lexer *)payload; - TSRange *current_included_range = &self->included_ranges[self->current_included_range_index]; - if (self->current_included_range_index > 0 && - self->current_position.bytes == current_included_range->start_byte) { - TSRange *previous_included_range = current_included_range - 1; - self->token_end_position = (Length) { - previous_included_range->end_byte, - previous_included_range->end_point, - }; - } else { - self->token_end_position = self->current_position; +// Mark that a token match has completed. This can be called multiple +// times if a longer match is found later. +static void ts_lexer__mark_end(TSLexer *_self) { + Lexer *self = (Lexer *)_self; + if (!ts_lexer__eof(&self->data)) { + // If the lexer is right at the beginning of included range, + // then the token should be considered to end at the *end* of the + // previous included range, rather than here. + TSRange *current_included_range = &self->included_ranges[ + self->current_included_range_index + ]; + if ( + self->current_included_range_index > 0 && + self->current_position.bytes == current_included_range->start_byte + ) { + TSRange *previous_included_range = current_included_range - 1; + self->token_end_position = (Length) { + previous_included_range->end_byte, + previous_included_range->end_point, + }; + return; + } } + self->token_end_position = self->current_position; } -static uint32_t ts_lexer__get_column(TSLexer *payload) { - Lexer *self = (Lexer *)payload; +static uint32_t ts_lexer__get_column(TSLexer *_self) { + Lexer *self = (Lexer *)_self; uint32_t goal_byte = self->current_position.bytes; self->current_position.bytes -= self->current_position.extent.column; @@ -142,67 +194,69 @@ static uint32_t ts_lexer__get_column(TSLexer *payload) { uint32_t result = 0; while (self->current_position.bytes < goal_byte) { - ts_lexer__advance(payload, false); + ts_lexer__advance(&self->data, false); result++; } return result; } -static bool ts_lexer__is_at_included_range_start(TSLexer *payload) { - const Lexer *self = (const Lexer *)payload; - TSRange *current_range = &self->included_ranges[self->current_included_range_index]; - return self->current_position.bytes == current_range->start_byte; +// Is the lexer at a boundary between two disjoint included ranges of +// source code? This is exposed as an API because some languages' external +// scanners need to perform custom actions at these bounaries. +static bool ts_lexer__is_at_included_range_start(const TSLexer *_self) { + const Lexer *self = (const Lexer *)_self; + if (self->current_included_range_index < self->included_range_count) { + TSRange *current_range = &self->included_ranges[self->current_included_range_index]; + return self->current_position.bytes == current_range->start_byte; + } else { + return false; + } } -// The lexer's methods are stored as a struct field so that generated -// parsers can call them without needing to be linked against this library. - void ts_lexer_init(Lexer *self) { *self = (Lexer) { .data = { + // The lexer's methods are stored as struct fields so that generated + // parsers can call them without needing to be linked against this + // library. .advance = ts_lexer__advance, .mark_end = ts_lexer__mark_end, .get_column = ts_lexer__get_column, .is_at_included_range_start = ts_lexer__is_at_included_range_start, + .eof = ts_lexer__eof, .lookahead = 0, .result_symbol = 0, }, .chunk = NULL, + .chunk_size = 0, .chunk_start = 0, - .current_position = {UINT32_MAX, {0, 0}}, + .current_position = {0, {0, 0}}, .logger = { .payload = NULL, .log = NULL }, + .included_ranges = NULL, + .included_range_count = 0, .current_included_range_index = 0, }; - - self->included_ranges = NULL; ts_lexer_set_included_ranges(self, NULL, 0); - ts_lexer_reset(self, length_zero()); } void ts_lexer_delete(Lexer *self) { ts_free(self->included_ranges); } -void ts_lexer_set_input(Lexer *self, TSInput input) { - self->input = input; - self->data.lookahead = 0; - self->lookahead_size = 0; - self->chunk = 0; - self->chunk_start = 0; - self->chunk_size = 0; -} - static void ts_lexer_goto(Lexer *self, Length position) { + self->current_position = position; bool found_included_range = false; + + // Move to the first valid position at or after the given position. for (unsigned i = 0; i < self->included_range_count; i++) { TSRange *included_range = &self->included_ranges[i]; if (included_range->end_byte > position.bytes) { if (included_range->start_byte > position.bytes) { - position = (Length) { + self->current_position = (Length) { .bytes = included_range->start_byte, .extent = included_range->start_point, }; @@ -214,46 +268,61 @@ static void ts_lexer_goto(Lexer *self, Length position) { } } - if (!found_included_range) { + if (found_included_range) { + // If the current position is outside of the current chunk of text, + // then clear out the current chunk of text. + if (self->chunk && ( + position.bytes < self->chunk_start || + position.bytes >= self->chunk_start + self->chunk_size + )) { + ts_lexer__clear_chunk(self); + } + + self->lookahead_size = 0; + self->data.lookahead = '\0'; + } + + // If the given position is beyond any of included ranges, move to the EOF + // state - past the end of the included ranges. + else { + self->current_included_range_index = self->included_range_count; TSRange *last_included_range = &self->included_ranges[self->included_range_count - 1]; - position = (Length) { + self->current_position = (Length) { .bytes = last_included_range->end_byte, .extent = last_included_range->end_point, }; - self->chunk = empty_chunk; - self->chunk_start = position.bytes; - self->chunk_size = 2; - } - - self->token_start_position = position; - self->token_end_position = LENGTH_UNDEFINED; - self->current_position = position; - - if (self->chunk && (position.bytes < self->chunk_start || - position.bytes >= self->chunk_start + self->chunk_size)) { - self->chunk = 0; - self->chunk_start = 0; - self->chunk_size = 0; + ts_lexer__clear_chunk(self); + self->lookahead_size = 1; + self->data.lookahead = '\0'; } +} - self->lookahead_size = 0; - self->data.lookahead = 0; +void ts_lexer_set_input(Lexer *self, TSInput input) { + self->input = input; + ts_lexer__clear_chunk(self); + ts_lexer_goto(self, self->current_position); } +// Move the lexer to the given position. This doesn't do any work +// if the parser is already at the given position. void ts_lexer_reset(Lexer *self, Length position) { - if (position.bytes != self->current_position.bytes) ts_lexer_goto(self, position); + if (position.bytes != self->current_position.bytes) { + ts_lexer_goto(self, position); + } } void ts_lexer_start(Lexer *self) { self->token_start_position = self->current_position; self->token_end_position = LENGTH_UNDEFINED; self->data.result_symbol = 0; - if (!self->chunk) ts_lexer__get_chunk(self); - if (!self->lookahead_size) ts_lexer__get_lookahead(self); - if ( - self->current_position.bytes == 0 && - self->data.lookahead == BYTE_ORDER_MARK - ) ts_lexer__advance((TSLexer *)self, true); + if (!ts_lexer__eof(&self->data)) { + if (!self->chunk_size) ts_lexer__get_chunk(self); + if (!self->lookahead_size) ts_lexer__get_lookahead(self); + if ( + self->current_position.bytes == 0 && + self->data.lookahead == BYTE_ORDER_MARK + ) ts_lexer__advance(&self->data, true); + } } void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) { @@ -267,7 +336,7 @@ void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) { // the character decoding algorithm may have looked at the following byte. // Therefore, the next byte *after* the current (invalid) character // affects the interpretation of the current character. - if (self->data.lookahead == -1) { + if (self->data.lookahead == TS_DECODE_ERROR) { current_lookahead_end_byte++; } @@ -277,8 +346,8 @@ void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) { } void ts_lexer_advance_to_end(Lexer *self) { - while (self->data.lookahead != 0) { - ts_lexer__advance((TSLexer *)self, false); + while (self->chunk) { + ts_lexer__advance(&self->data, false); } } @@ -286,30 +355,19 @@ void ts_lexer_mark_end(Lexer *self) { ts_lexer__mark_end(&self->data); } -static const TSRange DEFAULT_RANGES[] = { - { - .start_point = { - .row = 0, - .column = 0, - }, - .end_point = { - .row = UINT32_MAX, - .column = UINT32_MAX, - }, - .start_byte = 0, - .end_byte = UINT32_MAX - } -}; - -void ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count) { - if (!ranges) { - ranges = DEFAULT_RANGES; +void ts_lexer_set_included_ranges( + Lexer *self, + const TSRange *ranges, + uint32_t count +) { + if (count == 0 || !ranges) { + ranges = &DEFAULT_RANGE; count = 1; } - size_t sz = count * sizeof(TSRange); - self->included_ranges = ts_realloc(self->included_ranges, sz); - memcpy(self->included_ranges, ranges, sz); + size_t size = count * sizeof(TSRange); + self->included_ranges = ts_realloc(self->included_ranges, size); + memcpy(self->included_ranges, ranges, size); self->included_range_count = count; ts_lexer_goto(self, self->current_position); } diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h index f523d88f65..8cd9c26706 100644 --- a/src/tree_sitter/lexer.h +++ b/src/tree_sitter/lexer.h @@ -16,7 +16,7 @@ typedef struct { Length token_start_position; Length token_end_position; - TSRange * included_ranges; + TSRange *included_ranges; size_t included_range_count; size_t current_included_range_index; diff --git a/src/tree_sitter/lib.c b/src/tree_sitter/lib.c index fc5fbc9210..289d32f4c5 100644 --- a/src/tree_sitter/lib.c +++ b/src/tree_sitter/lib.c @@ -2,19 +2,16 @@ // // The following directories must be added to the include path: // - include -// - utf8proc #define _POSIX_C_SOURCE 200112L -#define UTF8PROC_STATIC #include "./get_changed_ranges.c" #include "./language.c" #include "./lexer.c" #include "./node.c" #include "./parser.c" +#include "./query.c" #include "./stack.c" #include "./subtree.c" #include "./tree_cursor.c" #include "./tree.c" -#include "./utf16.c" -#include "utf8proc.c" diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c index 6b2be36ee5..b03e2fc979 100644 --- a/src/tree_sitter/node.c +++ b/src/tree_sitter/node.c @@ -415,13 +415,15 @@ TSPoint ts_node_end_point(TSNode self) { } TSSymbol ts_node_symbol(TSNode self) { - return ts_node__alias(&self) - ? ts_node__alias(&self) - : ts_subtree_symbol(ts_node__subtree(self)); + TSSymbol symbol = ts_node__alias(&self); + if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self)); + return ts_language_public_symbol(self.tree->language, symbol); } const char *ts_node_type(TSNode self) { - return ts_language_symbol_name(self.tree->language, ts_node_symbol(self)); + TSSymbol symbol = ts_node__alias(&self); + if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self)); + return ts_language_symbol_name(self.tree->language, symbol); } char *ts_node_string(TSNode self) { diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c index 88b20845fd..f381afccab 100644 --- a/src/tree_sitter/parser.c +++ b/src/tree_sitter/parser.c @@ -351,6 +351,7 @@ static Subtree ts_parser__lex( Length start_position = ts_stack_position(self->stack, version); Subtree external_token = ts_stack_last_external_token(self->stack, version); TSLexMode lex_mode = self->language->lex_modes[parse_state]; + if (lex_mode.lex_state == (uint16_t)-1) return NULL_SUBTREE; const bool *valid_external_tokens = ts_language_enabled_external_tokens( self->language, lex_mode.external_lex_state @@ -438,7 +439,7 @@ static Subtree ts_parser__lex( } if (self->lexer.current_position.bytes == error_end_position.bytes) { - if (self->lexer.data.lookahead == 0) { + if (self->lexer.data.eof(&self->lexer.data)) { self->lexer.data.result_symbol = ts_builtin_sym_error; break; } @@ -748,7 +749,8 @@ static StackVersion ts_parser__reduce( uint32_t count, int dynamic_precedence, uint16_t production_id, - bool fragile + bool is_fragile, + bool is_extra ) { uint32_t initial_version_count = ts_stack_version_count(self->stack); uint32_t removed_version_count = 0; @@ -813,7 +815,8 @@ static StackVersion ts_parser__reduce( TSStateId state = ts_stack_state(self->stack, slice_version); TSStateId next_state = ts_language_next_state(self->language, state, symbol); - if (fragile || pop.size > 1 || initial_version_count > 1) { + if (is_extra) parent.ptr->extra = true; + if (is_fragile || pop.size > 1 || initial_version_count > 1) { parent.ptr->fragile_left = true; parent.ptr->fragile_right = true; parent.ptr->parse_state = TS_TREE_STATE_NONE; @@ -962,7 +965,7 @@ static bool ts_parser__do_all_potential_reductions( reduction_version = ts_parser__reduce( self, version, action.symbol, action.count, action.dynamic_precedence, action.production_id, - true + true, false ); } @@ -1366,8 +1369,17 @@ static bool ts_parser__advance( // Otherwise, re-run the lexer. if (!lookahead.ptr) { lookahead = ts_parser__lex(self, version, state); - ts_parser__set_cached_token(self, position, last_external_token, lookahead); - ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry); + if (lookahead.ptr) { + ts_parser__set_cached_token(self, position, last_external_token, lookahead); + ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry); + } + + // When parsing a non-terminal extra, a null lookahead indicates the + // end of the rule. The reduction is stored in the EOF table entry. + // After the reduction, the lexer needs to be run again. + else { + ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry); + } } for (;;) { @@ -1422,11 +1434,12 @@ static bool ts_parser__advance( case TSParseActionTypeReduce: { bool is_fragile = table_entry.action_count > 1; + bool is_extra = lookahead.ptr == NULL; LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.symbol), action.params.child_count); StackVersion reduction_version = ts_parser__reduce( self, version, action.params.symbol, action.params.child_count, action.params.dynamic_precedence, action.params.production_id, - is_fragile + is_fragile, is_extra ); if (reduction_version != STACK_VERSION_NONE) { last_reduction_version = reduction_version; @@ -1459,6 +1472,15 @@ static bool ts_parser__advance( ts_stack_renumber_version(self->stack, last_reduction_version, version); LOG_STACK(); state = ts_stack_state(self->stack, version); + + // At the end of a non-terminal extra rule, the lexer will return a + // null subtree, because the parser needs to perform a fixed reduction + // regardless of the lookahead node. After performing that reduction, + // (and completing the non-terminal extra rule) run the lexer again based + // on the current parse state. + if (!lookahead.ptr) { + lookahead = ts_parser__lex(self, version, state); + } ts_language_table_entry( self->language, state, @@ -1655,6 +1677,7 @@ TSParser *ts_parser_new(void) { void ts_parser_delete(TSParser *self) { if (!self) return; + ts_parser_set_language(self, NULL); ts_stack_delete(self->stack); if (self->reduce_actions.contents) { array_delete(&self->reduce_actions); @@ -1670,7 +1693,6 @@ void ts_parser_delete(TSParser *self) { ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE); ts_subtree_pool_delete(&self->tree_pool); reusable_node_delete(&self->reusable_node); - ts_parser_set_language(self, NULL); ts_free(self); } @@ -1695,6 +1717,7 @@ bool ts_parser_set_language(TSParser *self, const TSLanguage *language) { } self->language = language; + ts_parser_reset(self); return true; } @@ -1747,7 +1770,7 @@ const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) } void ts_parser_reset(TSParser *self) { - if (self->language->external_scanner.deserialize) { + if (self->language && self->language->external_scanner.deserialize) { self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0); } diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h index 974a7ca52f..9df91f8c3c 100644 --- a/src/tree_sitter/parser.h +++ b/src/tree_sitter/parser.h @@ -45,7 +45,8 @@ struct TSLexer { void (*advance)(TSLexer *, bool); void (*mark_end)(TSLexer *); uint32_t (*get_column)(TSLexer *); - bool (*is_at_included_range_start)(TSLexer *); + bool (*is_at_included_range_start)(const TSLexer *); + bool (*eof)(const TSLexer *); }; typedef enum { @@ -117,6 +118,7 @@ struct TSLanguage { uint32_t large_state_count; const uint16_t *small_parse_table; const uint32_t *small_parse_table_map; + const TSSymbol *public_symbol_map; }; /* @@ -126,6 +128,7 @@ struct TSLanguage { #define START_LEXER() \ bool result = false; \ bool skip = false; \ + bool eof = false; \ int32_t lookahead; \ goto start; \ next_state: \ diff --git a/src/tree_sitter/point.h b/src/tree_sitter/point.h index 4d0aed18ef..a50d20214b 100644 --- a/src/tree_sitter/point.h +++ b/src/tree_sitter/point.h @@ -3,6 +3,7 @@ #include "tree_sitter/api.h" +#define POINT_ZERO ((TSPoint) {0, 0}) #define POINT_MAX ((TSPoint) {UINT32_MAX, UINT32_MAX}) static inline TSPoint point__new(unsigned row, unsigned column) { diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c new file mode 100644 index 0000000000..a5ce86032c --- /dev/null +++ b/src/tree_sitter/query.c @@ -0,0 +1,1450 @@ +#include "tree_sitter/api.h" +#include "./alloc.h" +#include "./array.h" +#include "./bits.h" +#include "./language.h" +#include "./point.h" +#include "./tree_cursor.h" +#include "./unicode.h" +#include + +/* + * Stream - A sequence of unicode characters derived from a UTF8 string. + * This struct is used in parsing queries from S-expressions. + */ +typedef struct { + const char *input; + const char *end; + int32_t next; + uint8_t next_size; +} Stream; + +/* + * QueryStep - A step in the process of matching a query. Each node within + * a query S-expression maps to one of these steps. An entire pattern is + * represented as a sequence of these steps. Fields: + * + * - `symbol` - The grammar symbol to match. A zero value represents the + * wildcard symbol, '*'. + * - `field` - The field name to match. A zero value means that a field name + * was not specified. + * - `capture_id` - An integer representing the name of the capture associated + * with this node in the pattern. A `NONE` value means this node is not + * captured in this pattern. + * - `depth` - The depth where this node occurs in the pattern. The root node + * of the pattern has depth zero. + */ +typedef struct { + TSSymbol symbol; + TSFieldId field; + uint16_t capture_id; + uint16_t depth: 15; + bool contains_captures: 1; +} QueryStep; + +/* + * Slice - A slice of an external array. Within a query, capture names, + * literal string values, and predicate step informations are stored in three + * contiguous arrays. Individual captures, string values, and predicates are + * represented as slices of these three arrays. + */ +typedef struct { + uint32_t offset; + uint32_t length; +} Slice; + +/* + * SymbolTable - a two-way mapping of strings to ids. + */ +typedef struct { + Array(char) characters; + Array(Slice) slices; +} SymbolTable; + +/* + * PatternEntry - The set of steps needed to match a particular pattern, + * represented as a slice of a shared array. These entries are stored in a + * 'pattern map' - a sorted array that makes it possible to efficiently lookup + * patterns based on the symbol for their first step. + */ +typedef struct { + uint16_t step_index; + uint16_t pattern_index; +} PatternEntry; + +/* + * QueryState - The state of an in-progress match of a particular pattern + * in a query. While executing, a `TSQueryCursor` must keep track of a number + * of possible in-progress matches. Each of those possible matches is + * represented as one of these states. + */ +typedef struct { + uint16_t start_depth; + uint16_t pattern_index; + uint16_t step_index; + uint16_t capture_count; + uint16_t capture_list_id; + uint16_t consumed_capture_count; + uint32_t id; +} QueryState; + +/* + * CaptureListPool - A collection of *lists* of captures. Each QueryState + * needs to maintain its own list of captures. They are all represented as + * slices of one shared array. The CaptureListPool keeps track of which + * parts of the shared array are currently in use by a QueryState. + */ +typedef struct { + Array(TSQueryCapture) list; + uint32_t usage_map; +} CaptureListPool; + +/* + * TSQuery - A tree query, compiled from a string of S-expressions. The query + * itself is immutable. The mutable state used in the process of executing the + * query is stored in a `TSQueryCursor`. + */ +struct TSQuery { + SymbolTable captures; + SymbolTable predicate_values; + Array(QueryStep) steps; + Array(PatternEntry) pattern_map; + Array(TSQueryPredicateStep) predicate_steps; + Array(Slice) predicates_by_pattern; + Array(uint32_t) start_bytes_by_pattern; + const TSLanguage *language; + uint16_t max_capture_count; + uint16_t wildcard_root_pattern_count; + TSSymbol *symbol_map; +}; + +/* + * TSQueryCursor - A stateful struct used to execute a query on a tree. + */ +struct TSQueryCursor { + const TSQuery *query; + TSTreeCursor cursor; + Array(QueryState) states; + Array(QueryState) finished_states; + CaptureListPool capture_list_pool; + uint32_t depth; + uint32_t start_byte; + uint32_t end_byte; + uint32_t next_state_id; + TSPoint start_point; + TSPoint end_point; + bool ascending; +}; + +static const TSQueryError PARENT_DONE = -1; +static const uint8_t PATTERN_DONE_MARKER = UINT8_MAX; +static const uint16_t NONE = UINT16_MAX; +static const TSSymbol WILDCARD_SYMBOL = 0; +static const uint16_t MAX_STATE_COUNT = 32; + +// #define LOG(...) fprintf(stderr, __VA_ARGS__) +#define LOG(...) + +/********** + * Stream + **********/ + +// Advance to the next unicode code point in the stream. +static bool stream_advance(Stream *self) { + self->input += self->next_size; + if (self->input < self->end) { + uint32_t size = ts_decode_utf8( + (const uint8_t *)self->input, + self->end - self->input, + &self->next + ); + if (size > 0) { + self->next_size = size; + return true; + } + } else { + self->next_size = 0; + self->next = '\0'; + } + return false; +} + +// Reset the stream to the given input position, represented as a pointer +// into the input string. +static void stream_reset(Stream *self, const char *input) { + self->input = input; + self->next_size = 0; + stream_advance(self); +} + +static Stream stream_new(const char *string, uint32_t length) { + Stream self = { + .next = 0, + .input = string, + .end = string + length, + }; + stream_advance(&self); + return self; +} + +static void stream_skip_whitespace(Stream *stream) { + for (;;) { + if (iswspace(stream->next)) { + stream_advance(stream); + } else if (stream->next == ';') { + // skip over comments + stream_advance(stream); + while (stream->next && stream->next != '\n') { + if (!stream_advance(stream)) break; + } + } else { + break; + } + } +} + +static bool stream_is_ident_start(Stream *stream) { + return iswalnum(stream->next) || stream->next == '_' || stream->next == '-'; +} + +static void stream_scan_identifier(Stream *stream) { + do { + stream_advance(stream); + } while ( + iswalnum(stream->next) || + stream->next == '_' || + stream->next == '-' || + stream->next == '.' || + stream->next == '?' || + stream->next == '!' + ); +} + +/****************** + * CaptureListPool + ******************/ + +static CaptureListPool capture_list_pool_new() { + return (CaptureListPool) { + .list = array_new(), + .usage_map = UINT32_MAX, + }; +} + +static void capture_list_pool_reset(CaptureListPool *self, uint16_t list_size) { + self->usage_map = UINT32_MAX; + uint32_t total_size = MAX_STATE_COUNT * list_size; + array_reserve(&self->list, total_size); + self->list.size = total_size; +} + +static void capture_list_pool_delete(CaptureListPool *self) { + array_delete(&self->list); +} + +static TSQueryCapture *capture_list_pool_get(CaptureListPool *self, uint16_t id) { + return &self->list.contents[id * (self->list.size / MAX_STATE_COUNT)]; +} + +static bool capture_list_pool_is_empty(const CaptureListPool *self) { + return self->usage_map == 0; +} + +static uint16_t capture_list_pool_acquire(CaptureListPool *self) { + // In the usage_map bitmask, ones represent free lists, and zeros represent + // lists that are in use. A free list id can quickly be found by counting + // the leading zeros in the usage map. An id of zero corresponds to the + // highest-order bit in the bitmask. + uint16_t id = count_leading_zeros(self->usage_map); + if (id == 32) return NONE; + self->usage_map &= ~bitmask_for_index(id); + return id; +} + +static void capture_list_pool_release(CaptureListPool *self, uint16_t id) { + self->usage_map |= bitmask_for_index(id); +} + +/************** + * SymbolTable + **************/ + +static SymbolTable symbol_table_new() { + return (SymbolTable) { + .characters = array_new(), + .slices = array_new(), + }; +} + +static void symbol_table_delete(SymbolTable *self) { + array_delete(&self->characters); + array_delete(&self->slices); +} + +static int symbol_table_id_for_name( + const SymbolTable *self, + const char *name, + uint32_t length +) { + for (unsigned i = 0; i < self->slices.size; i++) { + Slice slice = self->slices.contents[i]; + if ( + slice.length == length && + !strncmp(&self->characters.contents[slice.offset], name, length) + ) return i; + } + return -1; +} + +static const char *symbol_table_name_for_id( + const SymbolTable *self, + uint16_t id, + uint32_t *length +) { + Slice slice = self->slices.contents[id]; + *length = slice.length; + return &self->characters.contents[slice.offset]; +} + +static uint16_t symbol_table_insert_name( + SymbolTable *self, + const char *name, + uint32_t length +) { + int id = symbol_table_id_for_name(self, name, length); + if (id >= 0) return (uint16_t)id; + Slice slice = { + .offset = self->characters.size, + .length = length, + }; + array_grow_by(&self->characters, length + 1); + memcpy(&self->characters.contents[slice.offset], name, length); + self->characters.contents[self->characters.size - 1] = 0; + array_push(&self->slices, slice); + return self->slices.size - 1; +} + +/********* + * Query + *********/ + +// The `pattern_map` contains a mapping from TSSymbol values to indices in the +// `steps` array. For a given syntax node, the `pattern_map` makes it possible +// to quickly find the starting steps of all of the patterns whose root matches +// that node. Each entry has two fields: a `pattern_index`, which identifies one +// of the patterns in the query, and a `step_index`, which indicates the start +// offset of that pattern's steps pattern within the `steps` array. +// +// The entries are sorted by the patterns' root symbols, and lookups use a +// binary search. This ensures that the cost of this initial lookup step +// scales logarithmically with the number of patterns in the query. +// +// This returns `true` if the symbol is present and `false` otherwise. +// If the symbol is not present `*result` is set to the index where the +// symbol should be inserted. +static inline bool ts_query__pattern_map_search( + const TSQuery *self, + TSSymbol needle, + uint32_t *result +) { + uint32_t base_index = self->wildcard_root_pattern_count; + uint32_t size = self->pattern_map.size - base_index; + if (size == 0) { + *result = base_index; + return false; + } + while (size > 1) { + uint32_t half_size = size / 2; + uint32_t mid_index = base_index + half_size; + TSSymbol mid_symbol = self->steps.contents[ + self->pattern_map.contents[mid_index].step_index + ].symbol; + if (needle > mid_symbol) base_index = mid_index; + size -= half_size; + } + + TSSymbol symbol = self->steps.contents[ + self->pattern_map.contents[base_index].step_index + ].symbol; + + if (needle > symbol) { + base_index++; + if (base_index < self->pattern_map.size) { + symbol = self->steps.contents[ + self->pattern_map.contents[base_index].step_index + ].symbol; + } + } + + *result = base_index; + return needle == symbol; +} + +// Insert a new pattern's start index into the pattern map, maintaining +// the pattern map's ordering invariant. +static inline void ts_query__pattern_map_insert( + TSQuery *self, + TSSymbol symbol, + uint32_t start_step_index +) { + uint32_t index; + ts_query__pattern_map_search(self, symbol, &index); + array_insert(&self->pattern_map, index, ((PatternEntry) { + .step_index = start_step_index, + .pattern_index = self->pattern_map.size, + })); +} + +static void ts_query__finalize_steps(TSQuery *self) { + for (unsigned i = 0; i < self->steps.size; i++) { + QueryStep *step = &self->steps.contents[i]; + uint32_t depth = step->depth; + if (step->capture_id != NONE) { + step->contains_captures = true; + } else { + step->contains_captures = false; + for (unsigned j = i + 1; j < self->steps.size; j++) { + QueryStep *s = &self->steps.contents[j]; + if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break; + if (s->capture_id != NONE) step->contains_captures = true; + } + } + } +} + +// Parse a single predicate associated with a pattern, adding it to the +// query's internal `predicate_steps` array. Predicates are arbitrary +// S-expressions associated with a pattern which are meant to be handled at +// a higher level of abstraction, such as the Rust/JavaScript bindings. They +// can contain '@'-prefixed capture names, double-quoted strings, and bare +// symbols, which also represent strings. +static TSQueryError ts_query__parse_predicate( + TSQuery *self, + Stream *stream +) { + if (stream->next == ')') return PARENT_DONE; + if (stream->next != '(') return TSQueryErrorSyntax; + stream_advance(stream); + stream_skip_whitespace(stream); + + unsigned step_count = 0; + for (;;) { + if (stream->next == ')') { + stream_advance(stream); + stream_skip_whitespace(stream); + array_back(&self->predicates_by_pattern)->length++; + array_push(&self->predicate_steps, ((TSQueryPredicateStep) { + .type = TSQueryPredicateStepTypeDone, + .value_id = 0, + })); + break; + } + + // Parse an '@'-prefixed capture name + else if (stream->next == '@') { + stream_advance(stream); + + // Parse the capture name + if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; + const char *capture_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - capture_name; + + // Add the capture id to the first step of the pattern + int capture_id = symbol_table_id_for_name( + &self->captures, + capture_name, + length + ); + if (capture_id == -1) { + stream_reset(stream, capture_name); + return TSQueryErrorCapture; + } + + array_back(&self->predicates_by_pattern)->length++; + array_push(&self->predicate_steps, ((TSQueryPredicateStep) { + .type = TSQueryPredicateStepTypeCapture, + .value_id = capture_id, + })); + } + + // Parse a string literal + else if (stream->next == '"') { + stream_advance(stream); + + // Parse the string content + const char *string_content = stream->input; + while (stream->next != '"') { + if (stream->next == '\n' || !stream_advance(stream)) { + stream_reset(stream, string_content - 1); + return TSQueryErrorSyntax; + } + } + uint32_t length = stream->input - string_content; + + // Add a step for the node + uint16_t id = symbol_table_insert_name( + &self->predicate_values, + string_content, + length + ); + array_back(&self->predicates_by_pattern)->length++; + array_push(&self->predicate_steps, ((TSQueryPredicateStep) { + .type = TSQueryPredicateStepTypeString, + .value_id = id, + })); + + if (stream->next != '"') return TSQueryErrorSyntax; + stream_advance(stream); + } + + // Parse a bare symbol + else if (stream_is_ident_start(stream)) { + const char *symbol_start = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - symbol_start; + uint16_t id = symbol_table_insert_name( + &self->predicate_values, + symbol_start, + length + ); + array_back(&self->predicates_by_pattern)->length++; + array_push(&self->predicate_steps, ((TSQueryPredicateStep) { + .type = TSQueryPredicateStepTypeString, + .value_id = id, + })); + } + + else { + return TSQueryErrorSyntax; + } + + step_count++; + stream_skip_whitespace(stream); + } + + return 0; +} + +// Read one S-expression pattern from the stream, and incorporate it into +// the query's internal state machine representation. For nested patterns, +// this function calls itself recursively. +static TSQueryError ts_query__parse_pattern( + TSQuery *self, + Stream *stream, + uint32_t depth, + uint32_t *capture_count +) { + uint16_t starting_step_index = self->steps.size; + + if (stream->next == 0) return TSQueryErrorSyntax; + + // Finish the parent S-expression + if (stream->next == ')') { + return PARENT_DONE; + } + + // Parse a parenthesized node expression + else if (stream->next == '(') { + stream_advance(stream); + stream_skip_whitespace(stream); + + // Parse a nested list, which represents a pattern followed by + // zero-or-more predicates. + if (stream->next == '(' && depth == 0) { + TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count); + if (e) return e; + + // Parse the predicates. + stream_skip_whitespace(stream); + for (;;) { + TSQueryError e = ts_query__parse_predicate(self, stream); + if (e == PARENT_DONE) { + stream_advance(stream); + stream_skip_whitespace(stream); + return 0; + } else if (e) { + return e; + } + } + } + + TSSymbol symbol; + + // Parse the wildcard symbol + if (stream->next == '*') { + symbol = WILDCARD_SYMBOL; + stream_advance(stream); + } + + // Parse a normal node name + else if (stream_is_ident_start(stream)) { + const char *node_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - node_name; + symbol = ts_language_symbol_for_name( + self->language, + node_name, + length, + true + ); + if (!symbol) { + stream_reset(stream, node_name); + return TSQueryErrorNodeType; + } + } else { + return TSQueryErrorSyntax; + } + + // Add a step for the node. + array_push(&self->steps, ((QueryStep) { + .depth = depth, + .symbol = symbol, + .field = 0, + .capture_id = NONE, + .contains_captures = false, + })); + + // Parse the child patterns + stream_skip_whitespace(stream); + for (;;) { + TSQueryError e = ts_query__parse_pattern(self, stream, depth + 1, capture_count); + if (e == PARENT_DONE) { + stream_advance(stream); + break; + } else if (e) { + return e; + } + } + } + + // Parse a double-quoted anonymous leaf node expression + else if (stream->next == '"') { + stream_advance(stream); + + // Parse the string content + const char *string_content = stream->input; + while (stream->next != '"') { + if (!stream_advance(stream)) { + stream_reset(stream, string_content - 1); + return TSQueryErrorSyntax; + } + } + uint32_t length = stream->input - string_content; + + // Add a step for the node + TSSymbol symbol = ts_language_symbol_for_name( + self->language, + string_content, + length, + false + ); + if (!symbol) { + stream_reset(stream, string_content); + return TSQueryErrorNodeType; + } + array_push(&self->steps, ((QueryStep) { + .depth = depth, + .symbol = symbol, + .field = 0, + .capture_id = NONE, + .contains_captures = false, + })); + + if (stream->next != '"') return TSQueryErrorSyntax; + stream_advance(stream); + } + + // Parse a field-prefixed pattern + else if (stream_is_ident_start(stream)) { + // Parse the field name + const char *field_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - field_name; + stream_skip_whitespace(stream); + + if (stream->next != ':') { + stream_reset(stream, field_name); + return TSQueryErrorSyntax; + } + stream_advance(stream); + stream_skip_whitespace(stream); + + // Parse the pattern + uint32_t step_index = self->steps.size; + TSQueryError e = ts_query__parse_pattern(self, stream, depth, capture_count); + if (e == PARENT_DONE) return TSQueryErrorSyntax; + if (e) return e; + + // Add the field name to the first step of the pattern + TSFieldId field_id = ts_language_field_id_for_name( + self->language, + field_name, + length + ); + if (!field_id) { + stream->input = field_name; + return TSQueryErrorField; + } + self->steps.contents[step_index].field = field_id; + } + + // Parse a wildcard pattern + else if (stream->next == '*') { + stream_advance(stream); + stream_skip_whitespace(stream); + + // Add a step that matches any kind of node + array_push(&self->steps, ((QueryStep) { + .depth = depth, + .symbol = WILDCARD_SYMBOL, + .field = 0, + .contains_captures = false, + })); + } + + else { + return TSQueryErrorSyntax; + } + + stream_skip_whitespace(stream); + + // Parse an '@'-prefixed capture pattern + if (stream->next == '@') { + stream_advance(stream); + + // Parse the capture name + if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax; + const char *capture_name = stream->input; + stream_scan_identifier(stream); + uint32_t length = stream->input - capture_name; + + // Add the capture id to the first step of the pattern + uint16_t capture_id = symbol_table_insert_name( + &self->captures, + capture_name, + length + ); + self->steps.contents[starting_step_index].capture_id = capture_id; + (*capture_count)++; + + stream_skip_whitespace(stream); + } + + return 0; +} + +TSQuery *ts_query_new( + const TSLanguage *language, + const char *source, + uint32_t source_len, + uint32_t *error_offset, + TSQueryError *error_type +) { + TSSymbol *symbol_map; + if (ts_language_version(language) >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) { + symbol_map = NULL; + } else { + // Work around the fact that multiple symbols can currently be + // associated with the same name, due to "simple aliases". + // In the next language ABI version, this map will be contained + // in the language's `public_symbol_map` field. + uint32_t symbol_count = ts_language_symbol_count(language); + symbol_map = ts_malloc(sizeof(TSSymbol) * symbol_count); + for (unsigned i = 0; i < symbol_count; i++) { + const char *name = ts_language_symbol_name(language, i); + const TSSymbolType symbol_type = ts_language_symbol_type(language, i); + + symbol_map[i] = i; + + for (unsigned j = 0; j < i; j++) { + if (ts_language_symbol_type(language, j) == symbol_type) { + if (!strcmp(name, ts_language_symbol_name(language, j))) { + symbol_map[i] = j; + break; + } + } + } + } + } + + TSQuery *self = ts_malloc(sizeof(TSQuery)); + *self = (TSQuery) { + .steps = array_new(), + .pattern_map = array_new(), + .captures = symbol_table_new(), + .predicate_values = symbol_table_new(), + .predicate_steps = array_new(), + .predicates_by_pattern = array_new(), + .symbol_map = symbol_map, + .wildcard_root_pattern_count = 0, + .max_capture_count = 0, + .language = language, + }; + + // Parse all of the S-expressions in the given string. + Stream stream = stream_new(source, source_len); + stream_skip_whitespace(&stream); + uint32_t start_step_index; + while (stream.input < stream.end) { + start_step_index = self->steps.size; + uint32_t capture_count = 0; + array_push(&self->start_bytes_by_pattern, stream.input - source); + array_push(&self->predicates_by_pattern, ((Slice) { + .offset = self->predicate_steps.size, + .length = 0, + })); + *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count); + array_push(&self->steps, ((QueryStep) { .depth = PATTERN_DONE_MARKER })); + + // If any pattern could not be parsed, then report the error information + // and terminate. + if (*error_type) { + *error_offset = stream.input - source; + ts_query_delete(self); + return NULL; + } + + // Maintain a map that can look up patterns for a given root symbol. + ts_query__pattern_map_insert( + self, + self->steps.contents[start_step_index].symbol, + start_step_index + ); + if (self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL) { + self->wildcard_root_pattern_count++; + } + + // Keep track of the maximum number of captures in pattern, because + // that numer determines how much space is needed to store each capture + // list. + if (capture_count > self->max_capture_count) { + self->max_capture_count = capture_count; + } + } + + ts_query__finalize_steps(self); + return self; +} + +void ts_query_delete(TSQuery *self) { + if (self) { + array_delete(&self->steps); + array_delete(&self->pattern_map); + array_delete(&self->predicate_steps); + array_delete(&self->predicates_by_pattern); + array_delete(&self->start_bytes_by_pattern); + symbol_table_delete(&self->captures); + symbol_table_delete(&self->predicate_values); + ts_free(self->symbol_map); + ts_free(self); + } +} + +uint32_t ts_query_pattern_count(const TSQuery *self) { + return self->predicates_by_pattern.size; +} + +uint32_t ts_query_capture_count(const TSQuery *self) { + return self->captures.slices.size; +} + +uint32_t ts_query_string_count(const TSQuery *self) { + return self->predicate_values.slices.size; +} + +const char *ts_query_capture_name_for_id( + const TSQuery *self, + uint32_t index, + uint32_t *length +) { + return symbol_table_name_for_id(&self->captures, index, length); +} + +const char *ts_query_string_value_for_id( + const TSQuery *self, + uint32_t index, + uint32_t *length +) { + return symbol_table_name_for_id(&self->predicate_values, index, length); +} + +const TSQueryPredicateStep *ts_query_predicates_for_pattern( + const TSQuery *self, + uint32_t pattern_index, + uint32_t *step_count +) { + Slice slice = self->predicates_by_pattern.contents[pattern_index]; + *step_count = slice.length; + return &self->predicate_steps.contents[slice.offset]; +} + +uint32_t ts_query_start_byte_for_pattern( + const TSQuery *self, + uint32_t pattern_index +) { + return self->start_bytes_by_pattern.contents[pattern_index]; +} + +void ts_query_disable_capture( + TSQuery *self, + const char *name, + uint32_t length +) { + int id = symbol_table_id_for_name(&self->captures, name, length); + if (id != -1) { + for (unsigned i = 0; i < self->steps.size; i++) { + QueryStep *step = &self->steps.contents[i]; + if (step->capture_id == id) { + step->capture_id = NONE; + } + } + } + ts_query__finalize_steps(self); +} + +/*************** + * QueryCursor + ***************/ + +TSQueryCursor *ts_query_cursor_new() { + TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor)); + *self = (TSQueryCursor) { + .ascending = false, + .states = array_new(), + .finished_states = array_new(), + .capture_list_pool = capture_list_pool_new(), + .start_byte = 0, + .end_byte = UINT32_MAX, + .start_point = {0, 0}, + .end_point = POINT_MAX, + }; + array_reserve(&self->states, MAX_STATE_COUNT); + array_reserve(&self->finished_states, MAX_STATE_COUNT); + return self; +} + +void ts_query_cursor_delete(TSQueryCursor *self) { + array_delete(&self->states); + array_delete(&self->finished_states); + ts_tree_cursor_delete(&self->cursor); + capture_list_pool_delete(&self->capture_list_pool); + ts_free(self); +} + +void ts_query_cursor_exec( + TSQueryCursor *self, + const TSQuery *query, + TSNode node +) { + array_clear(&self->states); + array_clear(&self->finished_states); + ts_tree_cursor_reset(&self->cursor, node); + capture_list_pool_reset(&self->capture_list_pool, query->max_capture_count); + self->next_state_id = 0; + self->depth = 0; + self->ascending = false; + self->query = query; +} + +void ts_query_cursor_set_byte_range( + TSQueryCursor *self, + uint32_t start_byte, + uint32_t end_byte +) { + if (end_byte == 0) { + start_byte = 0; + end_byte = UINT32_MAX; + } + self->start_byte = start_byte; + self->end_byte = end_byte; +} + +void ts_query_cursor_set_point_range( + TSQueryCursor *self, + TSPoint start_point, + TSPoint end_point +) { + if (end_point.row == 0 && end_point.column == 0) { + start_point = POINT_ZERO; + end_point = POINT_MAX; + } + self->start_point = start_point; + self->end_point = end_point; +} + +// Search through all of the in-progress states, and find the captured +// node that occurs earliest in the document. +static bool ts_query_cursor__first_in_progress_capture( + TSQueryCursor *self, + uint32_t *state_index, + uint32_t *byte_offset, + uint32_t *pattern_index +) { + bool result = false; + for (unsigned i = 0; i < self->states.size; i++) { + const QueryState *state = &self->states.contents[i]; + if (state->capture_count > 0) { + const TSQueryCapture *captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + uint32_t capture_byte = ts_node_start_byte(captures[0].node); + if ( + !result || + capture_byte < *byte_offset || + ( + capture_byte == *byte_offset && + state->pattern_index < *pattern_index + ) + ) { + result = true; + *state_index = i; + *byte_offset = capture_byte; + *pattern_index = state->pattern_index; + } + } + } + return result; +} + +static bool ts_query__cursor_add_state( + TSQueryCursor *self, + const PatternEntry *slice +) { + uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool); + + // If there are no capture lists left in the pool, then terminate whichever + // state has captured the earliest node in the document, and steal its + // capture list. + if (list_id == NONE) { + uint32_t state_index, byte_offset, pattern_index; + if (ts_query_cursor__first_in_progress_capture( + self, + &state_index, + &byte_offset, + &pattern_index + )) { + LOG( + " abandon state. index:%u, pattern:%u, offset:%u.\n", + state_index, pattern_index, byte_offset + ); + list_id = self->states.contents[state_index].capture_list_id; + array_erase(&self->states, state_index); + } else { + LOG(" too many finished states.\n"); + return false; + } + } + + LOG(" start state. pattern:%u\n", slice->pattern_index); + array_push(&self->states, ((QueryState) { + .capture_list_id = list_id, + .step_index = slice->step_index, + .pattern_index = slice->pattern_index, + .start_depth = self->depth, + .capture_count = 0, + .consumed_capture_count = 0, + })); + return true; +} + +static QueryState *ts_query__cursor_copy_state( + TSQueryCursor *self, + const QueryState *state +) { + uint32_t new_list_id = capture_list_pool_acquire(&self->capture_list_pool); + if (new_list_id == NONE) return NULL; + array_push(&self->states, *state); + QueryState *new_state = array_back(&self->states); + new_state->capture_list_id = new_list_id; + TSQueryCapture *old_captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + TSQueryCapture *new_captures = capture_list_pool_get( + &self->capture_list_pool, + new_list_id + ); + memcpy(new_captures, old_captures, state->capture_count * sizeof(TSQueryCapture)); + return new_state; +} + +// Walk the tree, processing patterns until at least one pattern finishes, +// If one or more patterns finish, return `true` and store their states in the +// `finished_states` array. Multiple patterns can finish on the same node. If +// there are no more matches, return `false`. +static inline bool ts_query_cursor__advance(TSQueryCursor *self) { + do { + if (self->ascending) { + LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor))); + + // When leaving a node, remove any unfinished states whose next step + // needed to match something within that node. + uint32_t deleted_count = 0; + for (unsigned i = 0, n = self->states.size; i < n; i++) { + QueryState *state = &self->states.contents[i]; + QueryStep *step = &self->query->steps.contents[state->step_index]; + + if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) { + LOG( + " failed to match. pattern:%u, step:%u\n", + state->pattern_index, + state->step_index + ); + + capture_list_pool_release( + &self->capture_list_pool, + state->capture_list_id + ); + deleted_count++; + } else if (deleted_count > 0) { + self->states.contents[i - deleted_count] = *state; + } + } + + self->states.size -= deleted_count; + + if (ts_tree_cursor_goto_next_sibling(&self->cursor)) { + self->ascending = false; + } else if (ts_tree_cursor_goto_parent(&self->cursor)) { + self->depth--; + } else { + return self->finished_states.size > 0; + } + } else { + bool can_have_later_siblings; + bool can_have_later_siblings_with_this_field; + TSFieldId field_id = ts_tree_cursor_current_status( + &self->cursor, + &can_have_later_siblings, + &can_have_later_siblings_with_this_field + ); + TSNode node = ts_tree_cursor_current_node(&self->cursor); + TSSymbol symbol = ts_node_symbol(node); + if (symbol != ts_builtin_sym_error && self->query->symbol_map) { + symbol = self->query->symbol_map[symbol]; + } + + // If this node is before the selected range, then avoid descending + // into it. + if ( + ts_node_end_byte(node) <= self->start_byte || + point_lte(ts_node_end_point(node), self->start_point) + ) { + if (!ts_tree_cursor_goto_next_sibling(&self->cursor)) { + self->ascending = true; + } + continue; + } + + // If this node is after the selected range, then stop walking. + if ( + self->end_byte <= ts_node_start_byte(node) || + point_lte(self->end_point, ts_node_start_point(node)) + ) return false; + + LOG( + "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u, can_have_later_siblings:%d, can_have_later_siblings_with_this_field:%d\n", + ts_node_type(node), + ts_language_field_name_for_id(self->query->language, field_id), + ts_node_start_point(node).row, + self->states.size, + self->finished_states.size, + can_have_later_siblings, + can_have_later_siblings_with_this_field + ); + + // Add new states for any patterns whose root node is a wildcard. + for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) { + PatternEntry *slice = &self->query->pattern_map.contents[i]; + QueryStep *step = &self->query->steps.contents[slice->step_index]; + + // If this node matches the first step of the pattern, then add a new + // state at the start of this pattern. + if (step->field && field_id != step->field) continue; + if (!ts_query__cursor_add_state(self, slice)) break; + } + + // Add new states for any patterns whose root node matches this node. + unsigned i; + if (ts_query__pattern_map_search(self->query, symbol, &i)) { + PatternEntry *slice = &self->query->pattern_map.contents[i]; + QueryStep *step = &self->query->steps.contents[slice->step_index]; + do { + // If this node matches the first step of the pattern, then add a new + // state at the start of this pattern. + if (step->field && field_id != step->field) continue; + if (!ts_query__cursor_add_state(self, slice)) break; + + // Advance to the next pattern whose root node matches this node. + i++; + if (i == self->query->pattern_map.size) break; + slice = &self->query->pattern_map.contents[i]; + step = &self->query->steps.contents[slice->step_index]; + } while (step->symbol == symbol); + } + + // Update all of the in-progress states with current node. + for (unsigned i = 0, n = self->states.size; i < n; i++) { + QueryState *state = &self->states.contents[i]; + QueryStep *step = &self->query->steps.contents[state->step_index]; + + // Check that the node matches all of the criteria for the next + // step of the pattern.if ( + if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue; + + // Determine if this node matches this step of the pattern, and also + // if this node can have later siblings that match this step of the + // pattern. + bool node_does_match = !step->symbol || step->symbol == symbol; + bool later_sibling_can_match = can_have_later_siblings; + if (step->field) { + if (step->field == field_id) { + if (!can_have_later_siblings_with_this_field) { + later_sibling_can_match = false; + } + } else { + node_does_match = false; + } + } + + if (!node_does_match) { + if (!later_sibling_can_match) { + LOG( + " discard state. pattern:%u, step:%u\n", + state->pattern_index, + state->step_index + ); + capture_list_pool_release( + &self->capture_list_pool, + state->capture_list_id + ); + array_erase(&self->states, i); + i--; + n--; + } + continue; + } + + // Some patterns can match their root node in multiple ways, + // capturing different children. If this pattern step could match + // later children within the same parent, then this query state + // cannot simply be updated in place. It must be split into two + // states: one that matches this node, and one which skips over + // this node, to preserve the possibility of matching later + // siblings. + QueryState *next_state = state; + if ( + step->depth > 0 && + step->contains_captures && + later_sibling_can_match + ) { + QueryState *copy = ts_query__cursor_copy_state(self, state); + if (copy) { + LOG( + " split state. pattern:%u, step:%u\n", + copy->pattern_index, + copy->step_index + ); + next_state = copy; + } else { + LOG(" canot split state.\n"); + } + } + + LOG( + " advance state. pattern:%u, step:%u\n", + next_state->pattern_index, + next_state->step_index + ); + + // If the current node is captured in this pattern, add it to the + // capture list. + if (step->capture_id != NONE) { + LOG( + " capture node. pattern:%u, capture_id:%u\n", + next_state->pattern_index, + step->capture_id + ); + TSQueryCapture *capture_list = capture_list_pool_get( + &self->capture_list_pool, + next_state->capture_list_id + ); + capture_list[next_state->capture_count++] = (TSQueryCapture) { + node, + step->capture_id + }; + } + + // If the pattern is now done, then remove it from the list of + // in-progress states, and add it to the list of finished states. + next_state->step_index++; + QueryStep *next_step = step + 1; + if (next_step->depth == PATTERN_DONE_MARKER) { + LOG(" finish pattern %u\n", next_state->pattern_index); + + next_state->id = self->next_state_id++; + array_push(&self->finished_states, *next_state); + if (next_state == state) { + array_erase(&self->states, i); + i--; + n--; + } else { + self->states.size--; + } + } + } + + // Continue descending if possible. + if (ts_tree_cursor_goto_first_child(&self->cursor)) { + self->depth++; + } else { + self->ascending = true; + } + } + } while (self->finished_states.size == 0); + + return true; +} + +bool ts_query_cursor_next_match( + TSQueryCursor *self, + TSQueryMatch *match +) { + if (self->finished_states.size == 0) { + if (!ts_query_cursor__advance(self)) { + return false; + } + } + + QueryState *state = &self->finished_states.contents[0]; + match->id = state->id; + match->pattern_index = state->pattern_index; + match->capture_count = state->capture_count; + match->captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + capture_list_pool_release(&self->capture_list_pool, state->capture_list_id); + array_erase(&self->finished_states, 0); + return true; +} + +void ts_query_cursor_remove_match( + TSQueryCursor *self, + uint32_t match_id +) { + for (unsigned i = 0; i < self->finished_states.size; i++) { + const QueryState *state = &self->finished_states.contents[i]; + if (state->id == match_id) { + capture_list_pool_release( + &self->capture_list_pool, + state->capture_list_id + ); + array_erase(&self->finished_states, i); + return; + } + } +} + +bool ts_query_cursor_next_capture( + TSQueryCursor *self, + TSQueryMatch *match, + uint32_t *capture_index +) { + for (;;) { + // The goal here is to return captures in order, even though they may not + // be discovered in order, because patterns can overlap. If there are any + // finished patterns, then try to find one that contains a capture that + // is *definitely* before any capture in an *unfinished* pattern. + if (self->finished_states.size > 0) { + // First, identify the position of the earliest capture in an unfinished + // match. For a finished capture to be returned, it must be *before* + // this position. + uint32_t first_unfinished_capture_byte = UINT32_MAX; + uint32_t first_unfinished_pattern_index = UINT32_MAX; + uint32_t first_unfinished_state_index; + ts_query_cursor__first_in_progress_capture( + self, + &first_unfinished_state_index, + &first_unfinished_capture_byte, + &first_unfinished_pattern_index + ); + + // Find the earliest capture in a finished match. + int first_finished_state_index = -1; + uint32_t first_finished_capture_byte = first_unfinished_capture_byte; + uint32_t first_finished_pattern_index = first_unfinished_pattern_index; + for (unsigned i = 0; i < self->finished_states.size; i++) { + const QueryState *state = &self->finished_states.contents[i]; + if (state->capture_count > state->consumed_capture_count) { + const TSQueryCapture *captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + uint32_t capture_byte = ts_node_start_byte( + captures[state->consumed_capture_count].node + ); + if ( + capture_byte < first_finished_capture_byte || + ( + capture_byte == first_finished_capture_byte && + state->pattern_index < first_finished_pattern_index + ) + ) { + first_finished_state_index = i; + first_finished_capture_byte = capture_byte; + first_finished_pattern_index = state->pattern_index; + } + } else { + capture_list_pool_release( + &self->capture_list_pool, + state->capture_list_id + ); + array_erase(&self->finished_states, i); + i--; + } + } + + // If there is finished capture that is clearly before any unfinished + // capture, then return its match, and its capture index. Internally + // record the fact that the capture has been 'consumed'. + if (first_finished_state_index != -1) { + QueryState *state = &self->finished_states.contents[ + first_finished_state_index + ]; + match->id = state->id; + match->pattern_index = state->pattern_index; + match->capture_count = state->capture_count; + match->captures = capture_list_pool_get( + &self->capture_list_pool, + state->capture_list_id + ); + *capture_index = state->consumed_capture_count; + state->consumed_capture_count++; + return true; + } + + if (capture_list_pool_is_empty(&self->capture_list_pool)) { + LOG( + " abandon state. index:%u, pattern:%u, offset:%u.\n", + first_unfinished_state_index, + first_unfinished_pattern_index, + first_unfinished_capture_byte + ); + capture_list_pool_release( + &self->capture_list_pool, + self->states.contents[first_unfinished_state_index].capture_list_id + ); + array_erase(&self->states, first_unfinished_state_index); + } + } + + // If there are no finished matches that are ready to be returned, then + // continue finding more matches. + if (!ts_query_cursor__advance(self)) return false; + } +} + +#undef LOG diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c index e95733eb46..30144fa175 100644 --- a/src/tree_sitter/subtree.c +++ b/src/tree_sitter/subtree.c @@ -18,18 +18,9 @@ typedef struct { Length new_end; } Edit; -#ifdef TREE_SITTER_TEST - -#define TS_MAX_INLINE_TREE_LENGTH 2 -#define TS_MAX_TREE_POOL_SIZE 0 - -#else - #define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX #define TS_MAX_TREE_POOL_SIZE 32 -#endif - static const ExternalScannerState empty_state = {.length = 0, .short_data = {0}}; // ExternalScannerState @@ -775,10 +766,10 @@ Subtree ts_subtree_last_external_token(Subtree tree) { } static size_t ts_subtree__write_char_to_string(char *s, size_t n, int32_t c) { - if (c == 0) - return snprintf(s, n, "EOF"); if (c == -1) return snprintf(s, n, "INVALID"); + else if (c == '\0') + return snprintf(s, n, "'\\0'"); else if (c == '\n') return snprintf(s, n, "'\\n'"); else if (c == '\t') diff --git a/src/tree_sitter/tree.c b/src/tree_sitter/tree.c index 04cb1d242f..391fa7f592 100644 --- a/src/tree_sitter/tree.c +++ b/src/tree_sitter/tree.c @@ -84,12 +84,10 @@ void ts_tree_edit(TSTree *self, const TSInputEdit *edit) { } TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uint32_t *count) { - TSRange *result; TreeCursor cursor1 = {NULL, array_new()}; TreeCursor cursor2 = {NULL, array_new()}; - TSNode root = ts_tree_root_node(self); - ts_tree_cursor_init(&cursor1, root); - ts_tree_cursor_init(&cursor2, root); + ts_tree_cursor_init(&cursor1, ts_tree_root_node(self)); + ts_tree_cursor_init(&cursor2, ts_tree_root_node(other)); TSRangeArray included_range_differences = array_new(); ts_range_array_get_changed_ranges( @@ -98,6 +96,7 @@ TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uin &included_range_differences ); + TSRange *result; *count = ts_subtree_get_changed_ranges( &self->root, &other->root, &cursor1, &cursor2, self->language, &included_range_differences, &result diff --git a/src/tree_sitter/tree_cursor.c b/src/tree_sitter/tree_cursor.c index 7103fc411d..00b9679d73 100644 --- a/src/tree_sitter/tree_cursor.c +++ b/src/tree_sitter/tree_cursor.c @@ -244,6 +244,72 @@ TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) { ); } +TSFieldId ts_tree_cursor_current_status( + const TSTreeCursor *_self, + bool *can_have_later_siblings, + bool *can_have_later_siblings_with_this_field +) { + const TreeCursor *self = (const TreeCursor *)_self; + TSFieldId result = 0; + *can_have_later_siblings = false; + *can_have_later_siblings_with_this_field = false; + + // Walk up the tree, visiting the current node and its invisible ancestors, + // because fields can refer to nodes through invisible *wrapper* nodes, + for (unsigned i = self->stack.size - 1; i > 0; i--) { + TreeCursorEntry *entry = &self->stack.contents[i]; + TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; + + // Stop walking up when a visible ancestor is found. + if (i != self->stack.size - 1) { + if (ts_subtree_visible(*entry->subtree)) break; + const TSSymbol *alias_sequence = ts_language_alias_sequence( + self->tree->language, + parent_entry->subtree->ptr->production_id + ); + if (alias_sequence && alias_sequence[entry->structural_child_index]) { + break; + } + } + + if (ts_subtree_child_count(*parent_entry->subtree) > entry->child_index + 1) { + *can_have_later_siblings = true; + } + + if (ts_subtree_extra(*entry->subtree)) break; + + const TSFieldMapEntry *field_map, *field_map_end; + ts_language_field_map( + self->tree->language, + parent_entry->subtree->ptr->production_id, + &field_map, &field_map_end + ); + + // Look for a field name associated with the current node. + if (!result) { + for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { + if (!i->inherited && i->child_index == entry->structural_child_index) { + result = i->field_id; + *can_have_later_siblings_with_this_field = false; + break; + } + } + } + + // Determine if there other later siblings with the same field name. + if (result) { + for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { + if (i->field_id == result && i->child_index > entry->structural_child_index) { + *can_have_later_siblings_with_this_field = true; + break; + } + } + } + } + + return result; +} + TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) { const TreeCursor *self = (const TreeCursor *)_self; @@ -264,19 +330,18 @@ TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) { } } + if (ts_subtree_extra(*entry->subtree)) break; + const TSFieldMapEntry *field_map, *field_map_end; ts_language_field_map( self->tree->language, parent_entry->subtree->ptr->production_id, &field_map, &field_map_end ); - - while (field_map < field_map_end) { - if ( - !field_map->inherited && - field_map->child_index == entry->structural_child_index - ) return field_map->field_id; - field_map++; + for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { + if (!i->inherited && i->child_index == entry->structural_child_index) { + return i->field_id; + } } } return 0; diff --git a/src/tree_sitter/tree_cursor.h b/src/tree_sitter/tree_cursor.h index 55bdad86da..5a39dd278c 100644 --- a/src/tree_sitter/tree_cursor.h +++ b/src/tree_sitter/tree_cursor.h @@ -16,5 +16,6 @@ typedef struct { } TreeCursor; void ts_tree_cursor_init(TreeCursor *, TSNode); +TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *); #endif // TREE_SITTER_TREE_CURSOR_H_ diff --git a/src/tree_sitter/unicode.h b/src/tree_sitter/unicode.h new file mode 100644 index 0000000000..0fba56a612 --- /dev/null +++ b/src/tree_sitter/unicode.h @@ -0,0 +1,50 @@ +#ifndef TREE_SITTER_UNICODE_H_ +#define TREE_SITTER_UNICODE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define U_EXPORT +#define U_EXPORT2 +#include "unicode/utf8.h" +#include "unicode/utf16.h" + +static const int32_t TS_DECODE_ERROR = U_SENTINEL; + +// These functions read one unicode code point from the given string, +// returning the number of bytes consumed. +typedef uint32_t (*UnicodeDecodeFunction)( + const uint8_t *string, + uint32_t length, + int32_t *code_point +); + +static inline uint32_t ts_decode_utf8( + const uint8_t *string, + uint32_t length, + int32_t *code_point +) { + uint32_t i = 0; + U8_NEXT(string, i, length, *code_point); + return i; +} + +static inline uint32_t ts_decode_utf16( + const uint8_t *string, + uint32_t length, + int32_t *code_point +) { + uint32_t i = 0; + U16_NEXT(((uint16_t *)string), i, length, *code_point); + return i * 2; +} + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_UNICODE_H_ diff --git a/src/tree_sitter/unicode/ICU_SHA b/src/tree_sitter/unicode/ICU_SHA new file mode 100644 index 0000000000..3622283ba3 --- /dev/null +++ b/src/tree_sitter/unicode/ICU_SHA @@ -0,0 +1 @@ +552b01f61127d30d6589aa4bf99468224979b661 diff --git a/src/tree_sitter/unicode/LICENSE b/src/tree_sitter/unicode/LICENSE new file mode 100644 index 0000000000..2e01e36876 --- /dev/null +++ b/src/tree_sitter/unicode/LICENSE @@ -0,0 +1,414 @@ +COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + +Copyright © 1991-2019 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +--------------------- + +Third-Party Software Licenses + +This section contains third-party software notices and/or additional +terms for licensed third-party software components included within ICU +libraries. + +1. ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) + + # The Google Chrome software developed by Google is licensed under + # the BSD license. Other software included in this distribution is + # provided under other licenses, as set forth below. + # + # The BSD License + # http://opensource.org/licenses/bsd-license.php + # Copyright (C) 2006-2008, Google Inc. + # + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # Redistributions in binary form must reproduce the above + # copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided with + # the distribution. + # Neither the name of Google Inc. nor the names of its + # contributors may be used to endorse or promote products derived from + # this software without specific prior written permission. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + # + # The word list in cjdict.txt are generated by combining three word lists + # listed below with further processing for compound word breaking. The + # frequency is generated with an iterative training against Google web + # corpora. + # + # * Libtabe (Chinese) + # - https://sourceforge.net/project/?group_id=1519 + # - Its license terms and conditions are shown below. + # + # * IPADIC (Japanese) + # - http://chasen.aist-nara.ac.jp/chasen/distribution.html + # - Its license terms and conditions are shown below. + # + # ---------COPYING.libtabe ---- BEGIN-------------------- + # + # /* + # * Copyright (c) 1999 TaBE Project. + # * Copyright (c) 1999 Pai-Hsiang Hsiao. + # * All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the TaBE Project nor the names of its + # * contributors may be used to endorse or promote products derived + # * from this software without specific prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # /* + # * Copyright (c) 1999 Computer Systems and Communication Lab, + # * Institute of Information Science, Academia + # * Sinica. All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the Computer Systems and Communication Lab + # * nor the names of its contributors may be used to endorse or + # * promote products derived from this software without specific + # * prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, + # University of Illinois + # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 + # + # ---------------COPYING.libtabe-----END-------------------------------- + # + # + # ---------------COPYING.ipadic-----BEGIN------------------------------- + # + # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science + # and Technology. All Rights Reserved. + # + # Use, reproduction, and distribution of this software is permitted. + # Any copy of this software, whether in its original form or modified, + # must include both the above copyright notice and the following + # paragraphs. + # + # Nara Institute of Science and Technology (NAIST), + # the copyright holders, disclaims all warranties with regard to this + # software, including all implied warranties of merchantability and + # fitness, in no event shall NAIST be liable for + # any special, indirect or consequential damages or any damages + # whatsoever resulting from loss of use, data or profits, whether in an + # action of contract, negligence or other tortuous action, arising out + # of or in connection with the use or performance of this software. + # + # A large portion of the dictionary entries + # originate from ICOT Free Software. The following conditions for ICOT + # Free Software applies to the current dictionary as well. + # + # Each User may also freely distribute the Program, whether in its + # original form or modified, to any third party or parties, PROVIDED + # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear + # on, or be attached to, the Program, which is distributed substantially + # in the same form as set out herein and that such intended + # distribution, if actually made, will neither violate or otherwise + # contravene any of the laws and regulations of the countries having + # jurisdiction over the User or the intended distribution itself. + # + # NO WARRANTY + # + # The program was produced on an experimental basis in the course of the + # research and development conducted during the project and is provided + # to users as so produced on an experimental basis. Accordingly, the + # program is provided without any warranty whatsoever, whether express, + # implied, statutory or otherwise. The term "warranty" used herein + # includes, but is not limited to, any warranty of the quality, + # performance, merchantability and fitness for a particular purpose of + # the program and the nonexistence of any infringement or violation of + # any right of any third party. + # + # Each user of the program will agree and understand, and be deemed to + # have agreed and understood, that there is no warranty whatsoever for + # the program and, accordingly, the entire risk arising from or + # otherwise connected with the program is assumed by the user. + # + # Therefore, neither ICOT, the copyright holder, or any other + # organization that participated in or was otherwise related to the + # development of the program and their respective officials, directors, + # officers and other employees shall be held liable for any and all + # damages, including, without limitation, general, special, incidental + # and consequential damages, arising out of or otherwise in connection + # with the use or inability to use the program or any product, material + # or result produced or otherwise obtained by using the program, + # regardless of whether they have been advised of, or otherwise had + # knowledge of, the possibility of such damages at any time during the + # project or thereafter. Each user will be deemed to have agreed to the + # foregoing by his or her commencement of use of the program. The term + # "use" as used herein includes, but is not limited to, the use, + # modification, copying and distribution of the program and the + # production of secondary products from the program. + # + # In the case where the program, whether in its original form or + # modified, was distributed or delivered to or received by a user from + # any person, organization or entity other than ICOT, unless it makes or + # grants independently of ICOT any specific warranty to the user in + # writing, such person, organization or entity, will also be exempted + # from and not be held liable to the user for any such damages as noted + # above as far as the program is concerned. + # + # ---------------COPYING.ipadic-----END---------------------------------- + +3. Lao Word Break Dictionary Data (laodict.txt) + + # Copyright (c) 2013 International Business Machines Corporation + # and others. All Rights Reserved. + # + # Project: http://code.google.com/p/lao-dictionary/ + # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt + # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt + # (copied below) + # + # This file is derived from the above dictionary, with slight + # modifications. + # ---------------------------------------------------------------------- + # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, + # are permitted provided that the following conditions are met: + # + # + # Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. Redistributions in + # binary form must reproduce the above copyright notice, this list of + # conditions and the following disclaimer in the documentation and/or + # other materials provided with the distribution. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # OF THE POSSIBILITY OF SUCH DAMAGE. + # -------------------------------------------------------------------------- + +4. Burmese Word Break Dictionary Data (burmesedict.txt) + + # Copyright (c) 2014 International Business Machines Corporation + # and others. All Rights Reserved. + # + # This list is part of a project hosted at: + # github.com/kanyawtech/myanmar-karen-word-lists + # + # -------------------------------------------------------------------------- + # Copyright (c) 2013, LeRoy Benjamin Sharon + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: Redistributions of source code must retain the above + # copyright notice, this list of conditions and the following + # disclaimer. Redistributions in binary form must reproduce the + # above copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided + # with the distribution. + # + # Neither the name Myanmar Karen Word Lists, nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + # -------------------------------------------------------------------------- + +5. Time Zone Database + + ICU uses the public domain data and code derived from Time Zone +Database for its time zone support. The ownership of the TZ database +is explained in BCP 175: Procedure for Maintaining the Time Zone +Database section 7. + + # 7. Database Ownership + # + # The TZ database itself is not an IETF Contribution or an IETF + # document. Rather it is a pre-existing and regularly updated work + # that is in the public domain, and is intended to remain in the + # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do + # not apply to the TZ Database or contributions that individuals make + # to it. Should any claims be made and substantiated against the TZ + # Database, the organization that is providing the IANA + # Considerations defined in this RFC, under the memorandum of + # understanding with the IETF, currently ICANN, may act in accordance + # with all competent court orders. No ownership claims will be made + # by ICANN or the IETF Trust on the database or the code. Any person + # making a contribution to the database or code waives all rights to + # future claims in that contribution or in the TZ Database. + +6. Google double-conversion + +Copyright 2006-2011, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/tree_sitter/unicode/README.md b/src/tree_sitter/unicode/README.md new file mode 100644 index 0000000000..623b8e3843 --- /dev/null +++ b/src/tree_sitter/unicode/README.md @@ -0,0 +1,29 @@ +# ICU Parts + +This directory contains a small subset of files from the Unicode organization's [ICU repository](https://github.com/unicode-org/icu). + +### License + +The license for these files is contained in the `LICENSE` file within this directory. + +### Contents + +* Source files taken from the [`icu4c/source/common/unicode`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c/source/common/unicode) directory: + * `utf8.h` + * `utf16.h` + * `umachine.h` +* Empty source files that are referenced by the above source files, but whose original contents in `libicu` are not needed: + * `ptypes.h` + * `urename.h` + * `utf.h` +* `ICU_SHA` - File containing the Git SHA of the commit in the `icu` repository from which the files were obtained. +* `LICENSE` - The license file from the [`icu4c`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c) directory of the `icu` repository. +* `README.md` - This text file. + +### Updating ICU + +To incorporate changes from the upstream `icu` repository: + +* Update `ICU_SHA` with the new Git SHA. +* Update `LICENSE` with the license text from the directory mentioned above. +* Update `utf8.h`, `utf16.h`, and `umachine.h` with their new contents in the `icu` repository. diff --git a/src/tree_sitter/unicode/ptypes.h b/src/tree_sitter/unicode/ptypes.h new file mode 100644 index 0000000000..ac79ad0f98 --- /dev/null +++ b/src/tree_sitter/unicode/ptypes.h @@ -0,0 +1 @@ +// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/umachine.h b/src/tree_sitter/unicode/umachine.h new file mode 100644 index 0000000000..9195824d5b --- /dev/null +++ b/src/tree_sitter/unicode/umachine.h @@ -0,0 +1,448 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* +****************************************************************************** +* +* Copyright (C) 1999-2015, International Business Machines +* Corporation and others. All Rights Reserved. +* +****************************************************************************** +* file name: umachine.h +* encoding: UTF-8 +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep13 +* created by: Markus W. Scherer +* +* This file defines basic types and constants for ICU to be +* platform-independent. umachine.h and utf.h are included into +* utypes.h to provide all the general definitions for ICU. +* All of these definitions used to be in utypes.h before +* the UTF-handling macros made this unmaintainable. +*/ + +#ifndef __UMACHINE_H__ +#define __UMACHINE_H__ + + +/** + * \file + * \brief Basic types and constants for UTF + * + *

Basic types and constants for UTF

+ * This file defines basic types and constants for utf.h to be + * platform-independent. umachine.h and utf.h are included into + * utypes.h to provide all the general definitions for ICU. + * All of these definitions used to be in utypes.h before + * the UTF-handling macros made this unmaintainable. + * + */ +/*==========================================================================*/ +/* Include platform-dependent definitions */ +/* which are contained in the platform-specific file platform.h */ +/*==========================================================================*/ + +#include "unicode/ptypes.h" /* platform.h is included in ptypes.h */ + +/* + * ANSI C headers: + * stddef.h defines wchar_t + */ +#include + +/*==========================================================================*/ +/* For C wrappers, we use the symbol U_STABLE. */ +/* This works properly if the includer is C or C++. */ +/* Functions are declared U_STABLE return-type U_EXPORT2 function-name()... */ +/*==========================================================================*/ + +/** + * \def U_CFUNC + * This is used in a declaration of a library private ICU C function. + * @stable ICU 2.4 + */ + +/** + * \def U_CDECL_BEGIN + * This is used to begin a declaration of a library private ICU C API. + * @stable ICU 2.4 + */ + +/** + * \def U_CDECL_END + * This is used to end a declaration of a library private ICU C API + * @stable ICU 2.4 + */ + +#ifdef __cplusplus +# define U_CFUNC extern "C" +# define U_CDECL_BEGIN extern "C" { +# define U_CDECL_END } +#else +# define U_CFUNC extern +# define U_CDECL_BEGIN +# define U_CDECL_END +#endif + +#ifndef U_ATTRIBUTE_DEPRECATED +/** + * \def U_ATTRIBUTE_DEPRECATED + * This is used for GCC specific attributes + * @internal + */ +#if U_GCC_MAJOR_MINOR >= 302 +# define U_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated)) +/** + * \def U_ATTRIBUTE_DEPRECATED + * This is used for Visual C++ specific attributes + * @internal + */ +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) +# define U_ATTRIBUTE_DEPRECATED __declspec(deprecated) +#else +# define U_ATTRIBUTE_DEPRECATED +#endif +#endif + +/** This is used to declare a function as a public ICU C API @stable ICU 2.0*/ +#define U_CAPI U_CFUNC U_EXPORT +/** This is used to declare a function as a stable public ICU C API*/ +#define U_STABLE U_CAPI +/** This is used to declare a function as a draft public ICU C API */ +#define U_DRAFT U_CAPI +/** This is used to declare a function as a deprecated public ICU C API */ +#define U_DEPRECATED U_CAPI U_ATTRIBUTE_DEPRECATED +/** This is used to declare a function as an obsolete public ICU C API */ +#define U_OBSOLETE U_CAPI +/** This is used to declare a function as an internal ICU C API */ +#define U_INTERNAL U_CAPI + +/** + * \def U_OVERRIDE + * Defined to the C++11 "override" keyword if available. + * Denotes a class or member which is an override of the base class. + * May result in an error if it applied to something not an override. + * @internal + */ +#ifndef U_OVERRIDE +#define U_OVERRIDE override +#endif + +/** + * \def U_FINAL + * Defined to the C++11 "final" keyword if available. + * Denotes a class or member which may not be overridden in subclasses. + * May result in an error if subclasses attempt to override. + * @internal + */ +#if !defined(U_FINAL) || defined(U_IN_DOXYGEN) +#define U_FINAL final +#endif + +// Before ICU 65, function-like, multi-statement ICU macros were just defined as +// series of statements wrapped in { } blocks and the caller could choose to +// either treat them as if they were actual functions and end the invocation +// with a trailing ; creating an empty statement after the block or else omit +// this trailing ; using the knowledge that the macro would expand to { }. +// +// But doing so doesn't work well with macros that look like functions and +// compiler warnings about empty statements (ICU-20601) and ICU 65 therefore +// switches to the standard solution of wrapping such macros in do { } while. +// +// This will however break existing code that depends on being able to invoke +// these macros without a trailing ; so to be able to remain compatible with +// such code the wrapper is itself defined as macros so that it's possible to +// build ICU 65 and later with the old macro behaviour, like this: +// +// CPPFLAGS='-DUPRV_BLOCK_MACRO_BEGIN="" -DUPRV_BLOCK_MACRO_END=""' +// runConfigureICU ... + +/** + * \def UPRV_BLOCK_MACRO_BEGIN + * Defined as the "do" keyword by default. + * @internal + */ +#ifndef UPRV_BLOCK_MACRO_BEGIN +#define UPRV_BLOCK_MACRO_BEGIN do +#endif + +/** + * \def UPRV_BLOCK_MACRO_END + * Defined as "while (FALSE)" by default. + * @internal + */ +#ifndef UPRV_BLOCK_MACRO_END +#define UPRV_BLOCK_MACRO_END while (FALSE) +#endif + +/*==========================================================================*/ +/* limits for int32_t etc., like in POSIX inttypes.h */ +/*==========================================================================*/ + +#ifndef INT8_MIN +/** The smallest value an 8 bit signed integer can hold @stable ICU 2.0 */ +# define INT8_MIN ((int8_t)(-128)) +#endif +#ifndef INT16_MIN +/** The smallest value a 16 bit signed integer can hold @stable ICU 2.0 */ +# define INT16_MIN ((int16_t)(-32767-1)) +#endif +#ifndef INT32_MIN +/** The smallest value a 32 bit signed integer can hold @stable ICU 2.0 */ +# define INT32_MIN ((int32_t)(-2147483647-1)) +#endif + +#ifndef INT8_MAX +/** The largest value an 8 bit signed integer can hold @stable ICU 2.0 */ +# define INT8_MAX ((int8_t)(127)) +#endif +#ifndef INT16_MAX +/** The largest value a 16 bit signed integer can hold @stable ICU 2.0 */ +# define INT16_MAX ((int16_t)(32767)) +#endif +#ifndef INT32_MAX +/** The largest value a 32 bit signed integer can hold @stable ICU 2.0 */ +# define INT32_MAX ((int32_t)(2147483647)) +#endif + +#ifndef UINT8_MAX +/** The largest value an 8 bit unsigned integer can hold @stable ICU 2.0 */ +# define UINT8_MAX ((uint8_t)(255U)) +#endif +#ifndef UINT16_MAX +/** The largest value a 16 bit unsigned integer can hold @stable ICU 2.0 */ +# define UINT16_MAX ((uint16_t)(65535U)) +#endif +#ifndef UINT32_MAX +/** The largest value a 32 bit unsigned integer can hold @stable ICU 2.0 */ +# define UINT32_MAX ((uint32_t)(4294967295U)) +#endif + +#if defined(U_INT64_T_UNAVAILABLE) +# error int64_t is required for decimal format and rule-based number format. +#else +# ifndef INT64_C +/** + * Provides a platform independent way to specify a signed 64-bit integer constant. + * note: may be wrong for some 64 bit platforms - ensure your compiler provides INT64_C + * @stable ICU 2.8 + */ +# define INT64_C(c) c ## LL +# endif +# ifndef UINT64_C +/** + * Provides a platform independent way to specify an unsigned 64-bit integer constant. + * note: may be wrong for some 64 bit platforms - ensure your compiler provides UINT64_C + * @stable ICU 2.8 + */ +# define UINT64_C(c) c ## ULL +# endif +# ifndef U_INT64_MIN +/** The smallest value a 64 bit signed integer can hold @stable ICU 2.8 */ +# define U_INT64_MIN ((int64_t)(INT64_C(-9223372036854775807)-1)) +# endif +# ifndef U_INT64_MAX +/** The largest value a 64 bit signed integer can hold @stable ICU 2.8 */ +# define U_INT64_MAX ((int64_t)(INT64_C(9223372036854775807))) +# endif +# ifndef U_UINT64_MAX +/** The largest value a 64 bit unsigned integer can hold @stable ICU 2.8 */ +# define U_UINT64_MAX ((uint64_t)(UINT64_C(18446744073709551615))) +# endif +#endif + +/*==========================================================================*/ +/* Boolean data type */ +/*==========================================================================*/ + +/** The ICU boolean type @stable ICU 2.0 */ +typedef int8_t UBool; + +#ifndef TRUE +/** The TRUE value of a UBool @stable ICU 2.0 */ +# define TRUE 1 +#endif +#ifndef FALSE +/** The FALSE value of a UBool @stable ICU 2.0 */ +# define FALSE 0 +#endif + + +/*==========================================================================*/ +/* Unicode data types */ +/*==========================================================================*/ + +/* wchar_t-related definitions -------------------------------------------- */ + +/* + * \def U_WCHAR_IS_UTF16 + * Defined if wchar_t uses UTF-16. + * + * @stable ICU 2.0 + */ +/* + * \def U_WCHAR_IS_UTF32 + * Defined if wchar_t uses UTF-32. + * + * @stable ICU 2.0 + */ +#if !defined(U_WCHAR_IS_UTF16) && !defined(U_WCHAR_IS_UTF32) +# ifdef __STDC_ISO_10646__ +# if (U_SIZEOF_WCHAR_T==2) +# define U_WCHAR_IS_UTF16 +# elif (U_SIZEOF_WCHAR_T==4) +# define U_WCHAR_IS_UTF32 +# endif +# elif defined __UCS2__ +# if (U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) && (U_SIZEOF_WCHAR_T==2) +# define U_WCHAR_IS_UTF16 +# endif +# elif defined(__UCS4__) || (U_PLATFORM == U_PF_OS400 && defined(__UTF32__)) +# if (U_SIZEOF_WCHAR_T==4) +# define U_WCHAR_IS_UTF32 +# endif +# elif U_PLATFORM_IS_DARWIN_BASED || (U_SIZEOF_WCHAR_T==4 && U_PLATFORM_IS_LINUX_BASED) +# define U_WCHAR_IS_UTF32 +# elif U_PLATFORM_HAS_WIN32_API +# define U_WCHAR_IS_UTF16 +# endif +#endif + +/* UChar and UChar32 definitions -------------------------------------------- */ + +/** Number of bytes in a UChar. @stable ICU 2.0 */ +#define U_SIZEOF_UCHAR 2 + +/** + * \def U_CHAR16_IS_TYPEDEF + * If 1, then char16_t is a typedef and not a real type (yet) + * @internal + */ +#if (U_PLATFORM == U_PF_AIX) && defined(__cplusplus) &&(U_CPLUSPLUS_VERSION < 11) +// for AIX, uchar.h needs to be included +# include +# define U_CHAR16_IS_TYPEDEF 1 +#elif defined(_MSC_VER) && (_MSC_VER < 1900) +// Versions of Visual Studio/MSVC below 2015 do not support char16_t as a real type, +// and instead use a typedef. https://msdn.microsoft.com/library/bb531344.aspx +# define U_CHAR16_IS_TYPEDEF 1 +#else +# define U_CHAR16_IS_TYPEDEF 0 +#endif + + +/** + * \var UChar + * + * The base type for UTF-16 code units and pointers. + * Unsigned 16-bit integer. + * Starting with ICU 59, C++ API uses char16_t directly, while C API continues to use UChar. + * + * UChar is configurable by defining the macro UCHAR_TYPE + * on the preprocessor or compiler command line: + * -DUCHAR_TYPE=uint16_t or -DUCHAR_TYPE=wchar_t (if U_SIZEOF_WCHAR_T==2) etc. + * (The UCHAR_TYPE can also be \#defined earlier in this file, for outside the ICU library code.) + * This is for transitional use from application code that uses uint16_t or wchar_t for UTF-16. + * + * The default is UChar=char16_t. + * + * C++11 defines char16_t as bit-compatible with uint16_t, but as a distinct type. + * + * In C, char16_t is a simple typedef of uint_least16_t. + * ICU requires uint_least16_t=uint16_t for data memory mapping. + * On macOS, char16_t is not available because the uchar.h standard header is missing. + * + * @stable ICU 4.4 + */ + +#if 1 + // #if 1 is normal. UChar defaults to char16_t in C++. + // For configuration testing of UChar=uint16_t temporarily change this to #if 0. + // The intltest Makefile #defines UCHAR_TYPE=char16_t, + // so we only #define it to uint16_t if it is undefined so far. +#elif !defined(UCHAR_TYPE) +# define UCHAR_TYPE uint16_t +#endif + +#if defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \ + defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION) + // Inside the ICU library code, never configurable. + typedef char16_t UChar; +#elif defined(UCHAR_TYPE) + typedef UCHAR_TYPE UChar; +#elif defined(__cplusplus) + typedef char16_t UChar; +#else + typedef uint16_t UChar; +#endif + +/** + * \var OldUChar + * Default ICU 58 definition of UChar. + * A base type for UTF-16 code units and pointers. + * Unsigned 16-bit integer. + * + * Define OldUChar to be wchar_t if that is 16 bits wide. + * If wchar_t is not 16 bits wide, then define UChar to be uint16_t. + * + * This makes the definition of OldUChar platform-dependent + * but allows direct string type compatibility with platforms with + * 16-bit wchar_t types. + * + * This is how UChar was defined in ICU 58, for transition convenience. + * Exception: ICU 58 UChar was defined to UCHAR_TYPE if that macro was defined. + * The current UChar responds to UCHAR_TYPE but OldUChar does not. + * + * @stable ICU 59 + */ +#if U_SIZEOF_WCHAR_T==2 + typedef wchar_t OldUChar; +#elif defined(__CHAR16_TYPE__) + typedef __CHAR16_TYPE__ OldUChar; +#else + typedef uint16_t OldUChar; +#endif + +/** + * Define UChar32 as a type for single Unicode code points. + * UChar32 is a signed 32-bit integer (same as int32_t). + * + * The Unicode code point range is 0..0x10ffff. + * All other values (negative or >=0x110000) are illegal as Unicode code points. + * They may be used as sentinel values to indicate "done", "error" + * or similar non-code point conditions. + * + * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined + * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned) + * or else to be uint32_t. + * That is, the definition of UChar32 was platform-dependent. + * + * @see U_SENTINEL + * @stable ICU 2.4 + */ +typedef int32_t UChar32; + +/** + * This value is intended for sentinel values for APIs that + * (take or) return single code points (UChar32). + * It is outside of the Unicode code point range 0..0x10ffff. + * + * For example, a "done" or "error" value in a new API + * could be indicated with U_SENTINEL. + * + * ICU APIs designed before ICU 2.4 usually define service-specific "done" + * values, mostly 0xffff. + * Those may need to be distinguished from + * actual U+ffff text contents by calling functions like + * CharacterIterator::hasNext() or UnicodeString::length(). + * + * @return -1 + * @see UChar32 + * @stable ICU 2.4 + */ +#define U_SENTINEL (-1) + +#include "unicode/urename.h" + +#endif diff --git a/src/tree_sitter/unicode/urename.h b/src/tree_sitter/unicode/urename.h new file mode 100644 index 0000000000..ac79ad0f98 --- /dev/null +++ b/src/tree_sitter/unicode/urename.h @@ -0,0 +1 @@ +// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/utf.h b/src/tree_sitter/unicode/utf.h new file mode 100644 index 0000000000..ac79ad0f98 --- /dev/null +++ b/src/tree_sitter/unicode/utf.h @@ -0,0 +1 @@ +// This file must exist in order for `utf8.h` and `utf16.h` to be used. diff --git a/src/tree_sitter/unicode/utf16.h b/src/tree_sitter/unicode/utf16.h new file mode 100644 index 0000000000..9fd7d5c8a7 --- /dev/null +++ b/src/tree_sitter/unicode/utf16.h @@ -0,0 +1,733 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* +******************************************************************************* +* +* Copyright (C) 1999-2012, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +* file name: utf16.h +* encoding: UTF-8 +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep09 +* created by: Markus W. Scherer +*/ + +/** + * \file + * \brief C API: 16-bit Unicode handling macros + * + * This file defines macros to deal with 16-bit Unicode (UTF-16) code units and strings. + * + * For more information see utf.h and the ICU User Guide Strings chapter + * (http://userguide.icu-project.org/strings). + * + * Usage: + * ICU coding guidelines for if() statements should be followed when using these macros. + * Compound statements (curly braces {}) must be used for if-else-while... + * bodies and all macro statements should be terminated with semicolon. + */ + +#ifndef __UTF16_H__ +#define __UTF16_H__ + +#include "unicode/umachine.h" +#ifndef __UTF_H__ +# include "unicode/utf.h" +#endif + +/* single-code point definitions -------------------------------------------- */ + +/** + * Does this code unit alone encode a code point (BMP, not a surrogate)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U16_IS_SINGLE(c) !U_IS_SURROGATE(c) + +/** + * Is this code unit a lead surrogate (U+d800..U+dbff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800) + +/** + * Is this code unit a trail surrogate (U+dc00..U+dfff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00) + +/** + * Is this code unit a surrogate (U+d800..U+dfff)? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U16_IS_SURROGATE(c) U_IS_SURROGATE(c) + +/** + * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), + * is it a lead surrogate? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0) + +/** + * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)), + * is it a trail surrogate? + * @param c 16-bit code unit + * @return TRUE or FALSE + * @stable ICU 4.2 + */ +#define U16_IS_SURROGATE_TRAIL(c) (((c)&0x400)!=0) + +/** + * Helper constant for U16_GET_SUPPLEMENTARY. + * @internal + */ +#define U16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000) + +/** + * Get a supplementary code point value (U+10000..U+10ffff) + * from its lead and trail surrogates. + * The result is undefined if the input values are not + * lead and trail surrogates. + * + * @param lead lead surrogate (U+d800..U+dbff) + * @param trail trail surrogate (U+dc00..U+dfff) + * @return supplementary code point (U+10000..U+10ffff) + * @stable ICU 2.4 + */ +#define U16_GET_SUPPLEMENTARY(lead, trail) \ + (((UChar32)(lead)<<10UL)+(UChar32)(trail)-U16_SURROGATE_OFFSET) + + +/** + * Get the lead surrogate (0xd800..0xdbff) for a + * supplementary code point (0x10000..0x10ffff). + * @param supplementary 32-bit code point (U+10000..U+10ffff) + * @return lead surrogate (U+d800..U+dbff) for supplementary + * @stable ICU 2.4 + */ +#define U16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0) + +/** + * Get the trail surrogate (0xdc00..0xdfff) for a + * supplementary code point (0x10000..0x10ffff). + * @param supplementary 32-bit code point (U+10000..U+10ffff) + * @return trail surrogate (U+dc00..U+dfff) for supplementary + * @stable ICU 2.4 + */ +#define U16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00) + +/** + * How many 16-bit code units are used to encode this Unicode code point? (1 or 2) + * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff). + * @param c 32-bit code point + * @return 1 or 2 + * @stable ICU 2.4 + */ +#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2) + +/** + * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff). + * @return 2 + * @stable ICU 2.4 + */ +#define U16_MAX_LENGTH 2 + +/** + * Get a code point from a string at a random-access offset, + * without changing the offset. + * "Unsafe" macro, assumes well-formed UTF-16. + * + * The offset may point to either the lead or trail surrogate unit + * for a supplementary code point, in which case the macro will read + * the adjacent matching surrogate as well. + * The result is undefined if the offset points to a single, unpaired surrogate. + * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT. + * + * @param s const UChar * string + * @param i string offset + * @param c output UChar32 variable + * @see U16_GET + * @stable ICU 2.4 + */ +#define U16_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ + (c)=(s)[i]; \ + if(U16_IS_SURROGATE(c)) { \ + if(U16_IS_SURROGATE_LEAD(c)) { \ + (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)+1]); \ + } else { \ + (c)=U16_GET_SUPPLEMENTARY((s)[(i)-1], (c)); \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Get a code point from a string at a random-access offset, + * without changing the offset. + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The offset may point to either the lead or trail surrogate unit + * for a supplementary code point, in which case the macro will read + * the adjacent matching surrogate as well. + * + * The length can be negative for a NUL-terminated string. + * + * If the offset points to a single, unpaired surrogate, then + * c is set to that unpaired surrogate. + * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT. + * + * @param s const UChar * string + * @param start starting string offset (usually 0) + * @param i string offset, must be start<=i(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ + (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ + } \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Get a code point from a string at a random-access offset, + * without changing the offset. + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The offset may point to either the lead or trail surrogate unit + * for a supplementary code point, in which case the macro will read + * the adjacent matching surrogate as well. + * + * The length can be negative for a NUL-terminated string. + * + * If the offset points to a single, unpaired surrogate, then + * c is set to U+FFFD. + * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT_OR_FFFD. + * + * @param s const UChar * string + * @param start starting string offset (usually 0) + * @param i string offset, must be start<=i(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ + (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ + } else { \ + (c)=0xfffd; \ + } \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/* definitions with forward iteration --------------------------------------- */ + +/** + * Get a code point from a string at a code point boundary offset, + * and advance the offset to the next code point boundary. + * (Post-incrementing forward iteration.) + * "Unsafe" macro, assumes well-formed UTF-16. + * + * The offset may point to the lead surrogate unit + * for a supplementary code point, in which case the macro will read + * the following trail surrogate as well. + * If the offset points to a trail surrogate, then that itself + * will be returned as the code point. + * The result is undefined if the offset points to a single, unpaired lead surrogate. + * + * @param s const UChar * string + * @param i string offset + * @param c output UChar32 variable + * @see U16_NEXT + * @stable ICU 2.4 + */ +#define U16_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ + (c)=(s)[(i)++]; \ + if(U16_IS_LEAD(c)) { \ + (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)++]); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Get a code point from a string at a code point boundary offset, + * and advance the offset to the next code point boundary. + * (Post-incrementing forward iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * The offset may point to the lead surrogate unit + * for a supplementary code point, in which case the macro will read + * the following trail surrogate as well. + * If the offset points to a trail surrogate or + * to a single, unpaired lead surrogate, then c is set to that unpaired surrogate. + * + * @param s const UChar * string + * @param i string offset, must be i>10)+0xd7c0); \ + (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Append a code point to a string, overwriting 1 or 2 code units. + * The offset points to the current end of the string contents + * and is advanced (post-increment). + * "Safe" macro, checks for a valid code point. + * If a surrogate pair is written, checks for sufficient space in the string. + * If the code point is not valid or a trail surrogate does not fit, + * then isError is set to TRUE. + * + * @param s const UChar * string buffer + * @param i string offset, must be i>10)+0xd7c0); \ + (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \ + } else /* c>0x10ffff or not enough space */ { \ + (isError)=TRUE; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the next. + * (Post-incrementing iteration.) + * "Unsafe" macro, assumes well-formed UTF-16. + * + * @param s const UChar * string + * @param i string offset + * @see U16_FWD_1 + * @stable ICU 2.4 + */ +#define U16_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + if(U16_IS_LEAD((s)[(i)++])) { \ + ++(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the next. + * (Post-incrementing iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * @param s const UChar * string + * @param i string offset, must be i0) { \ + U16_FWD_1_UNSAFE(s, i); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the n-th next one, + * i.e., move forward by n code points. + * (Post-incrementing iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * @param s const UChar * string + * @param i int32_t string offset, must be i0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \ + U16_FWD_1(s, i, length); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary + * at the start of a code point. + * If the offset points to the trail surrogate of a surrogate pair, + * then the offset is decremented. + * Otherwise, it is not modified. + * "Unsafe" macro, assumes well-formed UTF-16. + * + * @param s const UChar * string + * @param i string offset + * @see U16_SET_CP_START + * @stable ICU 2.4 + */ +#define U16_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + if(U16_IS_TRAIL((s)[i])) { \ + --(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary + * at the start of a code point. + * If the offset points to the trail surrogate of a surrogate pair, + * then the offset is decremented. + * Otherwise, it is not modified. + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * @param s const UChar * string + * @param start starting string offset (usually 0) + * @param i string offset, must be start<=i + * @see U16_SET_CP_START_UNSAFE + * @stable ICU 2.4 + */ +#define U16_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ + if(U16_IS_TRAIL((s)[i]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \ + --(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +/* definitions with backward iteration -------------------------------------- */ + +/** + * Move the string offset from one code point boundary to the previous one + * and get the code point between them. + * (Pre-decrementing backward iteration.) + * "Unsafe" macro, assumes well-formed UTF-16. + * + * The input offset may be the same as the string length. + * If the offset is behind a trail surrogate unit + * for a supplementary code point, then the macro will read + * the preceding lead surrogate as well. + * If the offset is behind a lead surrogate, then that itself + * will be returned as the code point. + * The result is undefined if the offset is behind a single, unpaired trail surrogate. + * + * @param s const UChar * string + * @param i string offset + * @param c output UChar32 variable + * @see U16_PREV + * @stable ICU 2.4 + */ +#define U16_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ + (c)=(s)[--(i)]; \ + if(U16_IS_TRAIL(c)) { \ + (c)=U16_GET_SUPPLEMENTARY((s)[--(i)], (c)); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the previous one + * and get the code point between them. + * (Pre-decrementing backward iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The input offset may be the same as the string length. + * If the offset is behind a trail surrogate unit + * for a supplementary code point, then the macro will read + * the preceding lead surrogate as well. + * If the offset is behind a lead surrogate or behind a single, unpaired + * trail surrogate, then c is set to that unpaired surrogate. + * + * @param s const UChar * string + * @param start starting string offset (usually 0) + * @param i string offset, must be start(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ + --(i); \ + (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the previous one + * and get the code point between them. + * (Pre-decrementing backward iteration.) + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The input offset may be the same as the string length. + * If the offset is behind a trail surrogate unit + * for a supplementary code point, then the macro will read + * the preceding lead surrogate as well. + * If the offset is behind a lead surrogate or behind a single, unpaired + * trail surrogate, then c is set to U+FFFD. + * + * @param s const UChar * string + * @param start starting string offset (usually 0) + * @param i string offset, must be start(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \ + --(i); \ + (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \ + } else { \ + (c)=0xfffd; \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the previous one. + * (Pre-decrementing backward iteration.) + * The input offset may be the same as the string length. + * "Unsafe" macro, assumes well-formed UTF-16. + * + * @param s const UChar * string + * @param i string offset + * @see U16_BACK_1 + * @stable ICU 2.4 + */ +#define U16_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + if(U16_IS_TRAIL((s)[--(i)])) { \ + --(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the previous one. + * (Pre-decrementing backward iteration.) + * The input offset may be the same as the string length. + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * @param s const UChar * string + * @param start starting string offset (usually 0) + * @param i string offset, must be start(start) && U16_IS_LEAD((s)[(i)-1])) { \ + --(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the n-th one before it, + * i.e., move backward by n code points. + * (Pre-decrementing backward iteration.) + * The input offset may be the same as the string length. + * "Unsafe" macro, assumes well-formed UTF-16. + * + * @param s const UChar * string + * @param i string offset + * @param n number of code points to skip + * @see U16_BACK_N + * @stable ICU 2.4 + */ +#define U16_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ + int32_t __N=(n); \ + while(__N>0) { \ + U16_BACK_1_UNSAFE(s, i); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the n-th one before it, + * i.e., move backward by n code points. + * (Pre-decrementing backward iteration.) + * The input offset may be the same as the string length. + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * @param s const UChar * string + * @param start start of string + * @param i string offset, must be start0 && (i)>(start)) { \ + U16_BACK_1(s, start, i); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary after a code point. + * If the offset is behind the lead surrogate of a surrogate pair, + * then the offset is incremented. + * Otherwise, it is not modified. + * The input offset may be the same as the string length. + * "Unsafe" macro, assumes well-formed UTF-16. + * + * @param s const UChar * string + * @param i string offset + * @see U16_SET_CP_LIMIT + * @stable ICU 2.4 + */ +#define U16_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + if(U16_IS_LEAD((s)[(i)-1])) { \ + ++(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary after a code point. + * If the offset is behind the lead surrogate of a surrogate pair, + * then the offset is incremented. + * Otherwise, it is not modified. + * The input offset may be the same as the string length. + * "Safe" macro, handles unpaired surrogates and checks for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * @param s const UChar * string + * @param start int32_t starting string offset (usually 0) + * @param i int32_t string offset, start<=i<=length + * @param length int32_t string length + * @see U16_SET_CP_LIMIT_UNSAFE + * @stable ICU 2.4 + */ +#define U16_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \ + if((start)<(i) && ((i)<(length) || (length)<0) && U16_IS_LEAD((s)[(i)-1]) && U16_IS_TRAIL((s)[i])) { \ + ++(i); \ + } \ +} UPRV_BLOCK_MACRO_END + +#endif diff --git a/src/tree_sitter/unicode/utf8.h b/src/tree_sitter/unicode/utf8.h new file mode 100644 index 0000000000..bb00130374 --- /dev/null +++ b/src/tree_sitter/unicode/utf8.h @@ -0,0 +1,881 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +/* +******************************************************************************* +* +* Copyright (C) 1999-2015, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +* file name: utf8.h +* encoding: UTF-8 +* tab size: 8 (not used) +* indentation:4 +* +* created on: 1999sep13 +* created by: Markus W. Scherer +*/ + +/** + * \file + * \brief C API: 8-bit Unicode handling macros + * + * This file defines macros to deal with 8-bit Unicode (UTF-8) code units (bytes) and strings. + * + * For more information see utf.h and the ICU User Guide Strings chapter + * (http://userguide.icu-project.org/strings). + * + * Usage: + * ICU coding guidelines for if() statements should be followed when using these macros. + * Compound statements (curly braces {}) must be used for if-else-while... + * bodies and all macro statements should be terminated with semicolon. + */ + +#ifndef __UTF8_H__ +#define __UTF8_H__ + +#include "unicode/umachine.h" +#ifndef __UTF_H__ +# include "unicode/utf.h" +#endif + +/* internal definitions ----------------------------------------------------- */ + +/** + * Counts the trail bytes for a UTF-8 lead byte. + * Returns 0 for 0..0xc1 as well as for 0xf5..0xff. + * leadByte might be evaluated multiple times. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is called by public macros in this file and thus must remain stable. + * + * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff. + * @internal + */ +#define U8_COUNT_TRAIL_BYTES(leadByte) \ + (U8_IS_LEAD(leadByte) ? \ + ((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)+1 : 0) + +/** + * Counts the trail bytes for a UTF-8 lead byte of a valid UTF-8 sequence. + * Returns 0 for 0..0xc1. Undefined for 0xf5..0xff. + * leadByte might be evaluated multiple times. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is called by public macros in this file and thus must remain stable. + * + * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff. + * @internal + */ +#define U8_COUNT_TRAIL_BYTES_UNSAFE(leadByte) \ + (((uint8_t)(leadByte)>=0xc2)+((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)) + +/** + * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is called by public macros in this file and thus must remain stable. + * @internal + */ +#define U8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1) + +/** + * Internal bit vector for 3-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD3_AND_T1. + * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence. + * Lead byte E0..EF bits 3..0 are used as byte index, + * first trail byte bits 7..5 are used as bit index into that byte. + * @see U8_IS_VALID_LEAD3_AND_T1 + * @internal + */ +#define U8_LEAD3_T1_BITS "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30" + +/** + * Internal 3-byte UTF-8 validity check. + * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid sequence. + * @internal + */ +#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) (U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5))) + +/** + * Internal bit vector for 4-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD4_AND_T1. + * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence. + * First trail byte bits 7..4 are used as byte index, + * lead byte F0..F4 bits 2..0 are used as bit index into that byte. + * @see U8_IS_VALID_LEAD4_AND_T1 + * @internal + */ +#define U8_LEAD4_T1_BITS "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00" + +/** + * Internal 4-byte UTF-8 validity check. + * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid sequence. + * @internal + */ +#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) (U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7))) + +/** + * Function for handling "next code point" with error-checking. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this + * file and thus must remain stable, and should not be hidden when other internal + * functions are hidden (otherwise public macros would fail to compile). + * @internal + */ +U_STABLE UChar32 U_EXPORT2 +utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict); + +/** + * Function for handling "append code point" with error-checking. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this + * file and thus must remain stable, and should not be hidden when other internal + * functions are hidden (otherwise public macros would fail to compile). + * @internal + */ +U_STABLE int32_t U_EXPORT2 +utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool *pIsError); + +/** + * Function for handling "previous code point" with error-checking. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this + * file and thus must remain stable, and should not be hidden when other internal + * functions are hidden (otherwise public macros would fail to compile). + * @internal + */ +U_STABLE UChar32 U_EXPORT2 +utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict); + +/** + * Function for handling "skip backward one code point" with error-checking. + * + * This is internal since it is not meant to be called directly by external clients; + * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this + * file and thus must remain stable, and should not be hidden when other internal + * functions are hidden (otherwise public macros would fail to compile). + * @internal + */ +U_STABLE int32_t U_EXPORT2 +utf8_back1SafeBody(const uint8_t *s, int32_t start, int32_t i); + +/* single-code point definitions -------------------------------------------- */ + +/** + * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)? + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U8_IS_SINGLE(c) (((c)&0x80)==0) + +/** + * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4) + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U8_IS_LEAD(c) ((uint8_t)((c)-0xc2)<=0x32) +// 0x32=0xf4-0xc2 + +/** + * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF) + * @param c 8-bit code unit (byte) + * @return TRUE or FALSE + * @stable ICU 2.4 + */ +#define U8_IS_TRAIL(c) ((int8_t)(c)<-0x40) + +/** + * How many code units (bytes) are used for the UTF-8 encoding + * of this Unicode code point? + * @param c 32-bit code point + * @return 1..4, or 0 if c is a surrogate or not a Unicode code point + * @stable ICU 2.4 + */ +#define U8_LENGTH(c) \ + ((uint32_t)(c)<=0x7f ? 1 : \ + ((uint32_t)(c)<=0x7ff ? 2 : \ + ((uint32_t)(c)<=0xd7ff ? 3 : \ + ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \ + ((uint32_t)(c)<=0xffff ? 3 : 4)\ + ) \ + ) \ + ) \ + ) + +/** + * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff). + * @return 4 + * @stable ICU 2.4 + */ +#define U8_MAX_LENGTH 4 + +/** + * Get a code point from a string at a random-access offset, + * without changing the offset. + * The offset may point to either the lead byte or one of the trail bytes + * for a code point, in which case the macro will read all of the bytes + * for the code point. + * The result is undefined if the offset points to an illegal UTF-8 + * byte sequence. + * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT. + * + * @param s const uint8_t * string + * @param i string offset + * @param c output UChar32 variable + * @see U8_GET + * @stable ICU 2.4 + */ +#define U8_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ + int32_t _u8_get_unsafe_index=(int32_t)(i); \ + U8_SET_CP_START_UNSAFE(s, _u8_get_unsafe_index); \ + U8_NEXT_UNSAFE(s, _u8_get_unsafe_index, c); \ +} UPRV_BLOCK_MACRO_END + +/** + * Get a code point from a string at a random-access offset, + * without changing the offset. + * The offset may point to either the lead byte or one of the trail bytes + * for a code point, in which case the macro will read all of the bytes + * for the code point. + * + * The length can be negative for a NUL-terminated string. + * + * If the offset points to an illegal UTF-8 byte sequence, then + * c is set to a negative value. + * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT. + * + * @param s const uint8_t * string + * @param start int32_t starting string offset + * @param i int32_t string offset, must be start<=i=0xe0 ? \ + ((c)<0xf0 ? /* U+0800..U+FFFF except surrogates */ \ + U8_LEAD3_T1_BITS[(c)&=0xf]&(1<<((__t=(s)[i])>>5)) && \ + (__t&=0x3f, 1) \ + : /* U+10000..U+10FFFF */ \ + ((c)-=0xf0)<=4 && \ + U8_LEAD4_T1_BITS[(__t=(s)[i])>>4]&(1<<(c)) && \ + ((c)=((c)<<6)|(__t&0x3f), ++(i)!=(length)) && \ + (__t=(s)[i]-0x80)<=0x3f) && \ + /* valid second-to-last trail byte */ \ + ((c)=((c)<<6)|__t, ++(i)!=(length)) \ + : /* U+0080..U+07FF */ \ + (c)>=0xc2 && ((c)&=0x1f, 1)) && \ + /* last trail byte */ \ + (__t=(s)[i]-0x80)<=0x3f && \ + ((c)=((c)<<6)|__t, ++(i), 1)) { \ + } else { \ + (c)=(sub); /* ill-formed*/ \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Append a code point to a string, overwriting 1 to 4 bytes. + * The offset points to the current end of the string contents + * and is advanced (post-increment). + * "Unsafe" macro, assumes a valid code point and sufficient space in the string. + * Otherwise, the result is undefined. + * + * @param s const uint8_t * string buffer + * @param i string offset + * @param c code point to append + * @see U8_APPEND + * @stable ICU 2.4 + */ +#define U8_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ + uint32_t __uc=(c); \ + if(__uc<=0x7f) { \ + (s)[(i)++]=(uint8_t)__uc; \ + } else { \ + if(__uc<=0x7ff) { \ + (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \ + } else { \ + if(__uc<=0xffff) { \ + (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \ + } else { \ + (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \ + (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \ + } \ + (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ + } \ + (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Append a code point to a string, overwriting 1 to 4 bytes. + * The offset points to the current end of the string contents + * and is advanced (post-increment). + * "Safe" macro, checks for a valid code point. + * If a non-ASCII code point is written, checks for sufficient space in the string. + * If the code point is not valid or trail bytes do not fit, + * then isError is set to TRUE. + * + * @param s const uint8_t * string buffer + * @param i int32_t string offset, must be i>6)|0xc0); \ + (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ + } else if((__uc<=0xd7ff || (0xe000<=__uc && __uc<=0xffff)) && (i)+2<(capacity)) { \ + (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \ + (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ + (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ + } else if(0xffff<__uc && __uc<=0x10ffff && (i)+3<(capacity)) { \ + (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \ + (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \ + (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \ + (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \ + } else { \ + (isError)=TRUE; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the next. + * (Post-incrementing iteration.) + * "Unsafe" macro, assumes well-formed UTF-8. + * + * @param s const uint8_t * string + * @param i string offset + * @see U8_FWD_1 + * @stable ICU 2.4 + */ +#define U8_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + (i)+=1+U8_COUNT_TRAIL_BYTES_UNSAFE((s)[i]); \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the next. + * (Post-incrementing iteration.) + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * @param s const uint8_t * string + * @param i int32_t string offset, must be i=0xf0 */ { \ + if(U8_IS_VALID_LEAD4_AND_T1(__b, __t1) && \ + ++(i)!=(length) && U8_IS_TRAIL((s)[i]) && \ + ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \ + ++(i); \ + } \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the n-th next one, + * i.e., move forward by n code points. + * (Post-incrementing iteration.) + * "Unsafe" macro, assumes well-formed UTF-8. + * + * @param s const uint8_t * string + * @param i string offset + * @param n number of code points to skip + * @see U8_FWD_N + * @stable ICU 2.4 + */ +#define U8_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \ + int32_t __N=(n); \ + while(__N>0) { \ + U8_FWD_1_UNSAFE(s, i); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Advance the string offset from one code point boundary to the n-th next one, + * i.e., move forward by n code points. + * (Post-incrementing iteration.) + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * @param s const uint8_t * string + * @param i int32_t string offset, must be i0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \ + U8_FWD_1(s, i, length); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary + * at the start of a code point. + * If the offset points to a UTF-8 trail byte, + * then the offset is moved backward to the corresponding lead byte. + * Otherwise, it is not modified. + * "Unsafe" macro, assumes well-formed UTF-8. + * + * @param s const uint8_t * string + * @param i string offset + * @see U8_SET_CP_START + * @stable ICU 2.4 + */ +#define U8_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + while(U8_IS_TRAIL((s)[i])) { --(i); } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary + * at the start of a code point. + * If the offset points to a UTF-8 trail byte, + * then the offset is moved backward to the corresponding lead byte. + * Otherwise, it is not modified. + * + * "Safe" macro, checks for illegal sequences and for string boundaries. + * Unlike U8_TRUNCATE_IF_INCOMPLETE(), this macro always reads s[i]. + * + * @param s const uint8_t * string + * @param start int32_t starting string offset (usually 0) + * @param i int32_t string offset, must be start<=i + * @see U8_SET_CP_START_UNSAFE + * @see U8_TRUNCATE_IF_INCOMPLETE + * @stable ICU 2.4 + */ +#define U8_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \ + if(U8_IS_TRAIL((s)[(i)])) { \ + (i)=utf8_back1SafeBody(s, start, (i)); \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * If the string ends with a UTF-8 byte sequence that is valid so far + * but incomplete, then reduce the length of the string to end before + * the lead byte of that incomplete sequence. + * For example, if the string ends with E1 80, the length is reduced by 2. + * + * In all other cases (the string ends with a complete sequence, or it is not + * possible for any further trail byte to extend the trailing sequence) + * the length remains unchanged. + * + * Useful for processing text split across multiple buffers + * (save the incomplete sequence for later) + * and for optimizing iteration + * (check for string length only once per character). + * + * "Safe" macro, checks for illegal sequences and for string boundaries. + * Unlike U8_SET_CP_START(), this macro never reads s[length]. + * + * (In UTF-16, simply check for U16_IS_LEAD(last code unit).) + * + * @param s const uint8_t * string + * @param start int32_t starting string offset (usually 0) + * @param length int32_t string length (usually start<=length) + * @see U8_SET_CP_START + * @stable ICU 61 + */ +#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) UPRV_BLOCK_MACRO_BEGIN { \ + if((length)>(start)) { \ + uint8_t __b1=s[(length)-1]; \ + if(U8_IS_SINGLE(__b1)) { \ + /* common ASCII character */ \ + } else if(U8_IS_LEAD(__b1)) { \ + --(length); \ + } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \ + uint8_t __b2=s[(length)-2]; \ + if(0xe0<=__b2 && __b2<=0xf4) { \ + if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \ + U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \ + (length)-=2; \ + } \ + } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \ + uint8_t __b3=s[(length)-3]; \ + if(0xf0<=__b3 && __b3<=0xf4 && U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \ + (length)-=3; \ + } \ + } \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/* definitions with backward iteration -------------------------------------- */ + +/** + * Move the string offset from one code point boundary to the previous one + * and get the code point between them. + * (Pre-decrementing backward iteration.) + * "Unsafe" macro, assumes well-formed UTF-8. + * + * The input offset may be the same as the string length. + * If the offset is behind a multi-byte sequence, then the macro will read + * the whole sequence. + * If the offset is behind a lead byte, then that itself + * will be returned as the code point. + * The result is undefined if the offset is behind an illegal UTF-8 sequence. + * + * @param s const uint8_t * string + * @param i string offset + * @param c output UChar32 variable + * @see U8_PREV + * @stable ICU 2.4 + */ +#define U8_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \ + (c)=(uint8_t)(s)[--(i)]; \ + if(U8_IS_TRAIL(c)) { \ + uint8_t __b, __count=1, __shift=6; \ +\ + /* c is a trail byte */ \ + (c)&=0x3f; \ + for(;;) { \ + __b=(s)[--(i)]; \ + if(__b>=0xc0) { \ + U8_MASK_LEAD_BYTE(__b, __count); \ + (c)|=(UChar32)__b<<__shift; \ + break; \ + } else { \ + (c)|=(UChar32)(__b&0x3f)<<__shift; \ + ++__count; \ + __shift+=6; \ + } \ + } \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the previous one + * and get the code point between them. + * (Pre-decrementing backward iteration.) + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * The input offset may be the same as the string length. + * If the offset is behind a multi-byte sequence, then the macro will read + * the whole sequence. + * If the offset is behind a lead byte, then that itself + * will be returned as the code point. + * If the offset is behind an illegal UTF-8 sequence, then c is set to a negative value. + * + * @param s const uint8_t * string + * @param start int32_t starting string offset (usually 0) + * @param i int32_t string offset, must be start0) { \ + U8_BACK_1_UNSAFE(s, i); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Move the string offset from one code point boundary to the n-th one before it, + * i.e., move backward by n code points. + * (Pre-decrementing backward iteration.) + * The input offset may be the same as the string length. + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * @param s const uint8_t * string + * @param start int32_t index of the start of the string + * @param i int32_t string offset, must be start0 && (i)>(start)) { \ + U8_BACK_1(s, start, i); \ + --__N; \ + } \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary after a code point. + * If the offset is behind a partial multi-byte sequence, + * then the offset is incremented to behind the whole sequence. + * Otherwise, it is not modified. + * The input offset may be the same as the string length. + * "Unsafe" macro, assumes well-formed UTF-8. + * + * @param s const uint8_t * string + * @param i string offset + * @see U8_SET_CP_LIMIT + * @stable ICU 2.4 + */ +#define U8_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \ + U8_BACK_1_UNSAFE(s, i); \ + U8_FWD_1_UNSAFE(s, i); \ +} UPRV_BLOCK_MACRO_END + +/** + * Adjust a random-access offset to a code point boundary after a code point. + * If the offset is behind a partial multi-byte sequence, + * then the offset is incremented to behind the whole sequence. + * Otherwise, it is not modified. + * The input offset may be the same as the string length. + * "Safe" macro, checks for illegal sequences and for string boundaries. + * + * The length can be negative for a NUL-terminated string. + * + * @param s const uint8_t * string + * @param start int32_t starting string offset (usually 0) + * @param i int32_t string offset, must be start<=i<=length + * @param length int32_t string length + * @see U8_SET_CP_LIMIT_UNSAFE + * @stable ICU 2.4 + */ +#define U8_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \ + if((start)<(i) && ((i)<(length) || (length)<0)) { \ + U8_BACK_1(s, start, i); \ + U8_FWD_1(s, i, length); \ + } \ +} UPRV_BLOCK_MACRO_END + +#endif -- cgit From f486d2145a5c252d7f37ea399f9a769ddd745934 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 16 Dec 2019 19:41:15 +0100 Subject: tree-sitter: fix relative paths in unicode/ subdir --- src/tree_sitter/unicode.h | 4 ++-- src/tree_sitter/unicode/umachine.h | 4 ++-- src/tree_sitter/unicode/utf16.h | 4 ++-- src/tree_sitter/unicode/utf8.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/tree_sitter/unicode.h b/src/tree_sitter/unicode.h index 0fba56a612..2ab51c2a3a 100644 --- a/src/tree_sitter/unicode.h +++ b/src/tree_sitter/unicode.h @@ -10,8 +10,8 @@ extern "C" { #define U_EXPORT #define U_EXPORT2 -#include "unicode/utf8.h" -#include "unicode/utf16.h" +#include "./unicode/utf8.h" +#include "./unicode/utf16.h" static const int32_t TS_DECODE_ERROR = U_SENTINEL; diff --git a/src/tree_sitter/unicode/umachine.h b/src/tree_sitter/unicode/umachine.h index 9195824d5b..bbf6ef9c8b 100644 --- a/src/tree_sitter/unicode/umachine.h +++ b/src/tree_sitter/unicode/umachine.h @@ -43,7 +43,7 @@ /* which are contained in the platform-specific file platform.h */ /*==========================================================================*/ -#include "unicode/ptypes.h" /* platform.h is included in ptypes.h */ +#include "./ptypes.h" /* platform.h is included in ptypes.h */ /* * ANSI C headers: @@ -443,6 +443,6 @@ typedef int32_t UChar32; */ #define U_SENTINEL (-1) -#include "unicode/urename.h" +#include "./urename.h" #endif diff --git a/src/tree_sitter/unicode/utf16.h b/src/tree_sitter/unicode/utf16.h index 9fd7d5c8a7..b547922441 100644 --- a/src/tree_sitter/unicode/utf16.h +++ b/src/tree_sitter/unicode/utf16.h @@ -34,9 +34,9 @@ #ifndef __UTF16_H__ #define __UTF16_H__ -#include "unicode/umachine.h" +#include "./umachine.h" #ifndef __UTF_H__ -# include "unicode/utf.h" +# include "./utf.h" #endif /* single-code point definitions -------------------------------------------- */ diff --git a/src/tree_sitter/unicode/utf8.h b/src/tree_sitter/unicode/utf8.h index bb00130374..3b37873e37 100644 --- a/src/tree_sitter/unicode/utf8.h +++ b/src/tree_sitter/unicode/utf8.h @@ -34,9 +34,9 @@ #ifndef __UTF8_H__ #define __UTF8_H__ -#include "unicode/umachine.h" +#include "./umachine.h" #ifndef __UTF_H__ -# include "unicode/utf.h" +# include "./utf.h" #endif /* internal definitions ----------------------------------------------------- */ -- cgit From c21511b2f48685461bf2655b28eff4434c91d449 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 21 Dec 2019 09:57:33 +0100 Subject: tree-sitter: fix prototypes (to be upstreamed) --- src/tree_sitter/query.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c index a5ce86032c..2563325248 100644 --- a/src/tree_sitter/query.c +++ b/src/tree_sitter/query.c @@ -224,7 +224,7 @@ static void stream_scan_identifier(Stream *stream) { * CaptureListPool ******************/ -static CaptureListPool capture_list_pool_new() { +static CaptureListPool capture_list_pool_new(void) { return (CaptureListPool) { .list = array_new(), .usage_map = UINT32_MAX, @@ -269,7 +269,7 @@ static void capture_list_pool_release(CaptureListPool *self, uint16_t id) { * SymbolTable **************/ -static SymbolTable symbol_table_new() { +static SymbolTable symbol_table_new(void) { return (SymbolTable) { .characters = array_new(), .slices = array_new(), -- cgit From 440695c29696f261337227e5c419aa1cf313c2dd Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 28 Sep 2019 14:27:20 +0200 Subject: tree-sitter: implement query functionality and highlighting prototype [skip.lint] --- src/nvim/api/buffer.c | 91 ++++++++- src/nvim/api/private/helpers.h | 6 + src/nvim/api/vim.c | 33 ++++ src/nvim/buffer_defs.h | 6 + src/nvim/buffer_updates.c | 11 +- src/nvim/func_attr.h | 2 + src/nvim/generators/c_grammar.lua | 1 + src/nvim/generators/gen_api_dispatch.lua | 15 +- src/nvim/generators/gen_eval.lua | 2 +- src/nvim/globals.h | 7 + src/nvim/lua/executor.c | 25 ++- src/nvim/lua/treesitter.c | 321 +++++++++++++++++++++++++++++-- src/nvim/screen.c | 105 +++++++++- 13 files changed, 578 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 8f5718d97e..e6f8f73b9d 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -169,21 +169,21 @@ Boolean nvim_buf_attach(uint64_t channel_id, goto error; } cb.on_lines = v->data.luaref; - v->data.integer = LUA_NOREF; + v->data.luaref = LUA_NOREF; } else if (is_lua && strequal("on_changedtick", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); goto error; } cb.on_changedtick = v->data.luaref; - v->data.integer = LUA_NOREF; + v->data.luaref = LUA_NOREF; } else if (is_lua && strequal("on_detach", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); goto error; } cb.on_detach = v->data.luaref; - v->data.integer = LUA_NOREF; + v->data.luaref = LUA_NOREF; } else if (is_lua && strequal("utf_sizes", k.data)) { if (v->type != kObjectTypeBoolean) { api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); @@ -231,6 +231,90 @@ Boolean nvim_buf_detach(uint64_t channel_id, return true; } +static void buf_clear_luahl(buf_T *buf, bool force) +{ + if (buf->b_luahl || force) { + executor_free_luaref(buf->b_luahl_start); + executor_free_luaref(buf->b_luahl_window); + executor_free_luaref(buf->b_luahl_line); + executor_free_luaref(buf->b_luahl_end); + } + buf->b_luahl_start = LUA_NOREF; + buf->b_luahl_window = LUA_NOREF; + buf->b_luahl_line = LUA_NOREF; + buf->b_luahl_end = LUA_NOREF; +} + +/// Unstabilized interface for defining syntax hl in lua. +/// +/// This is not yet safe for general use, lua callbacks will need to +/// be restricted, like textlock and probably other stuff. +/// +/// The API on_line/nvim__put_attr is quite raw and not intended to be the +/// final shape. Ideally this should operate on chunks larger than a single +/// line to reduce interpreter overhead, and generate annotation objects +/// (bufhl/virttext) on the fly but using the same representation. +void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer, + DictionaryOf(LuaRef) opts, Error *err) + FUNC_API_LUA_ONLY +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return; + } + + redraw_buf_later(buf, NOT_VALID); + buf_clear_luahl(buf, false); + + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("on_start", k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, "callback is not a function"); + goto error; + } + buf->b_luahl_start = v->data.luaref; + v->data.luaref = LUA_NOREF; + } else if (strequal("on_window", k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, "callback is not a function"); + goto error; + } + buf->b_luahl_window = v->data.luaref; + v->data.luaref = LUA_NOREF; + } else if (strequal("on_line", k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, "callback is not a function"); + goto error; + } + buf->b_luahl_line = v->data.luaref; + v->data.luaref = LUA_NOREF; + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + goto error; + } + } + buf->b_luahl = true; + return; +error: + buf_clear_luahl(buf, true); + buf->b_luahl = false; +} + +void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, + Error *err) + FUNC_API_LUA_ONLY +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return; + } + + redraw_buf_range_later(buf, (linenr_T)first+1, (linenr_T)last); +} + /// Sets a buffer line /// /// @deprecated use nvim_buf_set_lines instead. @@ -1112,7 +1196,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, return rv; } limit = v->data.integer; - v->data.integer = LUA_NOREF; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); return rv; diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 8930f252f6..048b937136 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -60,6 +60,12 @@ #define ADD(array, item) \ kv_push(array, item) +#define FIXED_TEMP_ARRAY(name, fixsize) \ + Array name = ARRAY_DICT_INIT; \ + Object name##__items[fixsize]; \ + args.size = fixsize; \ + args.items = name##__items; \ + #define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1}) /// Create a new String instance, putting data in allocated memory diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 19601b6539..2f59edee33 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -189,6 +189,15 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) return hl_get_attr_by_id(attrcode, rgb, err); } +/// Gets a highlight group by name +/// +/// similar to |hlID()|, but allocates a new ID if not present. +Integer nvim_get_hl_id_by_name(String name) + FUNC_API_SINCE(7) +{ + return syn_check_group((const char_u *)name.data, (int)name.size); +} + /// Sends input-keys to Nvim, subject to various quirks controlled by `mode` /// flags. This is a blocking call, unlike |nvim_input()|. /// @@ -2546,3 +2555,27 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) } return ret; } + +/// Set attrs in nvim__buf_set_lua_hl callbacks +/// +/// TODO(bfredl): This is rather pedestrian. The final +/// interface should probably be derived from a reformed +/// bufhl/virttext interface with full support for multi-line +/// ranges etc +void nvim__put_attr(Integer id, Integer c0, Integer c1) + FUNC_API_LUA_ONLY +{ + if (!lua_attr_active) { + return; + } + if (id == 0 || syn_get_final_id((int)id) == 0) { + return; + } + int attr = syn_id2attr((int)id); + c0 = MAX(c0, 0); + c1 = MIN(c1, (Integer)lua_attr_bufsize); + for (Integer c = c0; c < c1; c++) { + lua_attr_buf[c] = (sattr_T)hl_combine_attr(lua_attr_buf[c], (int)attr); + } + return; +} diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 700d8b82e6..bc4fb2997d 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -832,6 +832,12 @@ struct file_buffer { // The number for times the current line has been flushed in the memline. int flush_count; + bool b_luahl; + LuaRef b_luahl_start; + LuaRef b_luahl_window; + LuaRef b_luahl_line; + LuaRef b_luahl_end; + int b_diff_failed; // internal diff failed for this buffer }; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 3604578b50..d12527d6ac 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -157,7 +157,7 @@ void buf_updates_unregister_all(buf_T *buf) args.items[0] = BUFFER_OBJ(buf->handle); textlock++; - executor_exec_lua_cb(cb.on_detach, "detach", args, false); + executor_exec_lua_cb(cb.on_detach, "detach", args, false, NULL); textlock--; } free_update_callbacks(cb); @@ -265,7 +265,7 @@ void buf_updates_send_changes(buf_T *buf, args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); } textlock++; - Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true); + Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { @@ -293,10 +293,7 @@ void buf_updates_changedtick(buf_T *buf) BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); bool keep = true; if (cb.on_changedtick != LUA_NOREF) { - Array args = ARRAY_DICT_INIT; - Object items[2]; - args.size = 2; - args.items = items; + FIXED_TEMP_ARRAY(args, 2); // the first argument is always the buffer handle args.items[0] = BUFFER_OBJ(buf->handle); @@ -306,7 +303,7 @@ void buf_updates_changedtick(buf_T *buf) textlock++; Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", - args, true); + args, true, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index 14b8158c7f..38b51b5564 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -211,6 +211,8 @@ # define FUNC_API_NOEXPORT /// API function not exposed in VimL/eval. # define FUNC_API_REMOTE_ONLY +/// API function not exposed in VimL/remote. +# define FUNC_API_LUA_ONLY /// API function introduced at the given API level. # define FUNC_API_SINCE(X) /// API function deprecated since the given API level. diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index 1aa8223da0..de098b7a7b 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -42,6 +42,7 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_FAST') * Cc(true)), 'fast') ^ -1) * (fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) * (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) * + (fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) * (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * (fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) * (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) * diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 76dcf849d1..e861cfda35 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -192,7 +192,7 @@ end -- the real API. for i = 1, #functions do local fn = functions[i] - if fn.impl_name == nil then + if fn.impl_name == nil and not fn.lua_only then local args = {} output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') @@ -310,12 +310,13 @@ void msgpack_rpc_init_method_table(void) for i = 1, #functions do local fn = functions[i] - output:write(' msgpack_rpc_add_method_handler('.. - '(String) {.data = "'..fn.name..'", '.. - '.size = sizeof("'..fn.name..'") - 1}, '.. - '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name).. - ', .fast = '..tostring(fn.fast)..'});\n') - + if not fn.lua_only then + output:write(' msgpack_rpc_add_method_handler('.. + '(String) {.data = "'..fn.name..'", '.. + '.size = sizeof("'..fn.name..'") - 1}, '.. + '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name).. + ', .fast = '..tostring(fn.fast)..'});\n') + end end output:write('\n}\n\n') diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 2c6f8f2603..d16453530f 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do - if not fun.remote_only then + if not (fun.remote_only or fun.lua_only) then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 172c190df2..0a7a2d551e 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -126,6 +126,13 @@ typedef off_t off_T; */ EXTERN int mod_mask INIT(= 0x0); /* current key modifiers */ + +// TODO(bfredl): for the final interface this should find a more suitable +// location. +EXTERN sattr_T *lua_attr_buf INIT(= NULL); +EXTERN size_t lua_attr_bufsize INIT(= 0); +EXTERN bool lua_attr_active INIT(= false); + /* * Cmdline_row is the row where the command line starts, just below the * last window. diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 25f4be1c4d..1d3d9929d3 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -835,7 +835,7 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) } Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, - bool retval) + bool retval, Error *err) { lua_State *const lstate = nlua_enter(); nlua_pushref(lstate, ref); @@ -845,16 +845,24 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, } if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) { - // TODO(bfredl): callbacks:s might not always be msg-safe, for instance - // lua callbacks for redraw events. Later on let the caller deal with the - // error instead. - nlua_error(lstate, _("Error executing lua callback: %.*s")); + // if err is passed, the caller will deal with the error. + if (err) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, errstr); + } else { + nlua_error(lstate, _("Error executing lua callback: %.*s")); + } return NIL; } - Error err = ERROR_INIT; if (retval) { - return nlua_pop_Object(lstate, false, &err); + Error dummy = ERROR_INIT; + if (err == NULL) { + err = &dummy; + } + return nlua_pop_Object(lstate, false, err); } else { return NIL; } @@ -1007,4 +1015,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); + + lua_pushcfunction(lstate, ts_lua_parse_query); + lua_setfield(lstate, -2, "_ts_parse_query"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index d2072402bb..874fabd89f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -26,6 +26,11 @@ typedef struct { TSTree *tree; // internal tree, used for editing/reparsing } TSLua_parser; +typedef struct { + TSQueryCursor *cursor; + int predicated_match; +} TSLua_cursor; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/treesitter.c.generated.h" #endif @@ -66,6 +71,20 @@ static struct luaL_Reg node_meta[] = { { "descendant_for_range", node_descendant_for_range }, { "named_descendant_for_range", node_named_descendant_for_range }, { "parent", node_parent }, + { "_rawquery", node_rawquery }, + { NULL, NULL } +}; + +static struct luaL_Reg query_meta[] = { + { "__gc", query_gc }, + { "__tostring", query_tostring }, + { "inspect", query_inspect }, + { NULL, NULL } +}; + +// cursor is not exposed, but still needs garbage collection +static struct luaL_Reg querycursor_meta[] = { + { "__gc", querycursor_gc }, { NULL, NULL } }; @@ -96,6 +115,8 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_parser", parser_meta); build_meta(L, "treesitter_tree", tree_meta); build_meta(L, "treesitter_node", node_meta); + build_meta(L, "treesitter_query", query_meta); + build_meta(L, "treesitter_querycursor", querycursor_meta); } int tslua_register_lang(lua_State *L) @@ -276,13 +297,33 @@ static int parser_parse_buf(lua_State *L) } TSInput input = { payload, input_cb, TSInputEncodingUTF8 }; TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input); + + uint32_t n_ranges = 0; + TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree, + &n_ranges) : NULL; if (p->tree) { ts_tree_delete(p->tree); } p->tree = new_tree; tslua_push_tree(L, p->tree); - return 1; + + lua_createtable(L, n_ranges, 0); + for (size_t i = 0; i < n_ranges; i++) { + lua_createtable(L, 4, 0); + lua_pushinteger(L, changed[i].start_point.row); + lua_rawseti(L, -2, 1); + lua_pushinteger(L, changed[i].start_point.column); + lua_rawseti(L, -2, 2); + lua_pushinteger(L, changed[i].end_point.row); + lua_rawseti(L, -2, 3); + lua_pushinteger(L, changed[i].end_point.column); + lua_rawseti(L, -2, 4); + + lua_rawseti(L, -2, i+1); + } + xfree(changed); + return 2; } static int parser_tree(lua_State *L) @@ -383,7 +424,7 @@ static int tree_root(lua_State *L) return 0; } TSNode root = ts_tree_root_node(tree); - push_node(L, root); + push_node(L, root, 1); return 1; } @@ -394,18 +435,19 @@ static int tree_root(lua_State *L) /// top of stack must either be the tree this node belongs to or another node /// of the same tree! This value is not popped. Can only be called inside a /// cfunction with the tslua environment. -static void push_node(lua_State *L, TSNode node) +static void push_node(lua_State *L, TSNode node, int uindex) { + assert(uindex > 0 || uindex < -LUA_MINSTACK); if (ts_node_is_null(node)) { - lua_pushnil(L); // [src, nil] + lua_pushnil(L); // [nil] return; } - TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [src, udata] + TSNode *ud = lua_newuserdata(L, sizeof(TSNode)); // [udata] *ud = node; - lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [src, udata, meta] - lua_setmetatable(L, -2); // [src, udata] - lua_getfenv(L, -2); // [src, udata, reftable] - lua_setfenv(L, -2); // [src, udata] + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_node"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] + lua_getfenv(L, uindex); // [udata, reftable] + lua_setfenv(L, -2); // [udata] } static bool node_check(lua_State *L, TSNode *res) @@ -586,8 +628,7 @@ static int node_child(lua_State *L) long num = lua_tointeger(L, 2); TSNode child = ts_node_child(node, (uint32_t)num); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -600,8 +641,7 @@ static int node_named_child(lua_State *L) long num = lua_tointeger(L, 2); TSNode child = ts_node_named_child(node, (uint32_t)num); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -617,8 +657,7 @@ static int node_descendant_for_range(lua_State *L) (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_descendant_for_point_range(node, start, end); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -634,8 +673,7 @@ static int node_named_descendant_for_range(lua_State *L) (uint32_t)lua_tointeger(L, 5) }; TSNode child = ts_node_named_descendant_for_point_range(node, start, end); - lua_pushvalue(L, 1); - push_node(L, child); + push_node(L, child, 1); return 1; } @@ -646,7 +684,254 @@ static int node_parent(lua_State *L) return 0; } TSNode parent = ts_node_parent(node); - push_node(L, parent); + push_node(L, parent, 1); + return 1; +} + +/// assumes the match table being on top of the stack +static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) +{ + for (int i = 0; i < match->capture_count; i++) { + push_node(L, match->captures[i].node, nodeidx); + lua_rawseti(L, -2, match->captures[i].index+1); + } +} + +static int query_next_match(lua_State *L) +{ + TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1)); + TSQueryCursor *cursor = ud->cursor; + + TSQuery *query = query_check(L, lua_upvalueindex(3)); + TSQueryMatch match; + if (ts_query_cursor_next_match(cursor, &match)) { + lua_pushinteger(L, match.pattern_index+1); // [index] + lua_createtable(L, ts_query_capture_count(query), 2); // [index, match] + set_match(L, &match, lua_upvalueindex(2)); + return 2; + } + return 0; +} + + +static int query_next_capture(lua_State *L) +{ + TSLua_cursor *ud = lua_touserdata(L, lua_upvalueindex(1)); + TSQueryCursor *cursor = ud->cursor; + + TSQuery *query = query_check(L, lua_upvalueindex(3)); + + if (ud->predicated_match > -1) { + lua_getfield(L, lua_upvalueindex(4), "active"); + bool active = lua_toboolean(L, -1); + lua_pop(L, 1); + if (!active) { + ts_query_cursor_remove_match(cursor, ud->predicated_match); + } + ud->predicated_match = -1; + } + + TSQueryMatch match; + uint32_t capture_index; + if (ts_query_cursor_next_capture(cursor, &match, &capture_index)) { + TSQueryCapture capture = match.captures[capture_index]; + + lua_pushinteger(L, capture.index+1); // [index] + push_node(L, capture.node, lua_upvalueindex(2)); // [index, node] + + uint32_t n_pred; + ts_query_predicates_for_pattern(query, match.pattern_index, &n_pred); + if (n_pred > 0 && capture_index == 0) { + lua_pushvalue(L, lua_upvalueindex(4)); // [index, node, match] + set_match(L, &match, lua_upvalueindex(2)); + lua_pushinteger(L, match.pattern_index+1); + lua_setfield(L, -2, "pattern"); + + if (match.capture_count > 1) { + ud->predicated_match = match.id; + lua_pushboolean(L, false); + lua_setfield(L, -2, "active"); + } + return 3; + } + return 2; + } + return 0; +} + +static int node_rawquery(lua_State *L) +{ + TSNode node; + if (!node_check(L, &node)) { + return 0; + } + TSQuery *query = query_check(L, 2); + // TODO(bfredl): these are expensive allegedly, + // use a reuse list later on? + TSQueryCursor *cursor = ts_query_cursor_new(); + ts_query_cursor_exec(cursor, query, node); + + bool captures = lua_toboolean(L, 3); + + if (lua_gettop(L) >= 4) { + int start = luaL_checkinteger(L, 4); + int end = lua_gettop(L) >= 5 ? luaL_checkinteger(L, 5) : MAXLNUM; + ts_query_cursor_set_point_range(cursor, + (TSPoint){ start, 0 }, (TSPoint){ end, 0 }); + } + + TSLua_cursor *ud = lua_newuserdata(L, sizeof(*ud)); // [udata] + ud->cursor = cursor; + ud->predicated_match = -1; + + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_querycursor"); + lua_setmetatable(L, -2); // [udata] + lua_pushvalue(L, 1); // [udata, node] + + // include query separately, as to keep a ref to it for gc + lua_pushvalue(L, 2); // [udata, node, query] + + if (captures) { + // placeholder for match state + lua_createtable(L, ts_query_capture_count(query), 2); // [u, n, q, match] + lua_pushcclosure(L, query_next_capture, 4); // [closure] + } else { + lua_pushcclosure(L, query_next_match, 3); // [closure] + } + return 1; } +static int querycursor_gc(lua_State *L) +{ + TSLua_cursor *ud = luaL_checkudata(L, 1, "treesitter_querycursor"); + ts_query_cursor_delete(ud->cursor); + return 0; +} + +// Query methods + +int ts_lua_parse_query(lua_State *L) +{ + if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { + return luaL_error(L, "string expected"); + } + + const char *lang_name = lua_tostring(L, 1); + TSLanguage *lang = pmap_get(cstr_t)(langs, lang_name); + if (!lang) { + return luaL_error(L, "no such language: %s", lang_name); + } + + size_t len; + const char *src = lua_tolstring(L, 2, &len); + + uint32_t error_offset; + TSQueryError error_type; + TSQuery *query = ts_query_new(lang, src, len, &error_offset, &error_type); + + if (!query) { + return luaL_error(L, "query: %s at position %d", + query_err_string(error_type), (int)error_offset); + } + + TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata] + *ud = query; + lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_query"); // [udata, meta] + lua_setmetatable(L, -2); // [udata] + return 1; +} + + +static const char *query_err_string(TSQueryError err) { + switch (err) { + case TSQueryErrorSyntax: return "invalid syntax"; + case TSQueryErrorNodeType: return "invalid node type"; + case TSQueryErrorField: return "invalid field"; + case TSQueryErrorCapture: return "invalid capture"; + default: return "error"; + } +} + +static TSQuery *query_check(lua_State *L, int index) +{ + TSQuery **ud = luaL_checkudata(L, index, "treesitter_query"); + return *ud; +} + +static int query_gc(lua_State *L) +{ + TSQuery *query = query_check(L, 1); + if (!query) { + return 0; + } + + ts_query_delete(query); + return 0; +} + +static int query_tostring(lua_State *L) +{ + lua_pushstring(L, ""); + return 1; +} + +static int query_inspect(lua_State *L) +{ + TSQuery *query = query_check(L, 1); + if (!query) { + return 0; + } + + uint32_t n_pat = ts_query_pattern_count(query); + lua_createtable(L, 0, 2); // [retval] + lua_createtable(L, n_pat, 1); // [retval, patterns] + for (size_t i = 0; i < n_pat; i++) { + uint32_t len; + const TSQueryPredicateStep *step = ts_query_predicates_for_pattern(query, + i, &len); + if (len == 0) { + continue; + } + lua_createtable(L, len/4, 1); // [retval, patterns, pat] + lua_createtable(L, 3, 0); // [retval, patterns, pat, pred] + int nextpred = 1; + int nextitem = 1; + for (size_t k = 0; k < len; k++) { + if (step[k].type == TSQueryPredicateStepTypeDone) { + lua_rawseti(L, -2, nextpred++); // [retval, patterns, pat] + lua_createtable(L, 3, 0); // [retval, patterns, pat, pred] + nextitem = 1; + continue; + } + + if (step[k].type == TSQueryPredicateStepTypeString) { + uint32_t strlen; + const char *str = ts_query_string_value_for_id(query, step[k].value_id, + &strlen); + lua_pushlstring(L, str, strlen); // [retval, patterns, pat, pred, item] + } else if (step[k].type == TSQueryPredicateStepTypeCapture) { + lua_pushnumber(L, step[k].value_id+1); // [..., pat, pred, item] + } else { + abort(); + } + lua_rawseti(L, -2, nextitem++); // [retval, patterns, pat, pred] + } + // last predicate should have ended with TypeDone + lua_pop(L, 1); // [retval, patters, pat] + lua_rawseti(L, -2, i+1); // [retval, patterns] + } + lua_setfield(L, -2, "patterns"); // [retval] + + uint32_t n_captures = ts_query_capture_count(query); + lua_createtable(L, n_captures, 0); // [retval, captures] + for (size_t i = 0; i < n_captures; i++) { + uint32_t strlen; + const char *str = ts_query_capture_name_for_id(query, i, &strlen); + lua_pushlstring(L, str, strlen); // [retval, captures, capture] + lua_rawseti(L, -2, i+1); + } + lua_setfield(L, -2, "captures"); // [retval] + + return 1; +} diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 1d29ae064e..4082208dd4 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -116,6 +116,8 @@ #include "nvim/window.h" #include "nvim/os/time.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/vim.h" +#include "nvim/lua/executor.h" #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ @@ -232,6 +234,22 @@ void redraw_buf_line_later(buf_T *buf, linenr_T line) } } +void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && lastline >= wp->w_topline && firstline < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > firstline) { + wp->w_redraw_top = firstline; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { + wp->w_redraw_bot = lastline; + } + redraw_win_later(wp, VALID); + } + } +} + /* * Changed something in the current window, at buffer line "lnum", that * requires that line and possibly other lines to be redrawn. @@ -477,6 +495,19 @@ int update_screen(int type) if (wwp == wp && syntax_present(wp)) { syn_stack_apply_changes(wp->w_buffer); } + + buf_T *buf = wp->w_buffer; + if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) { + Error err = ERROR_INIT; + FIXED_TEMP_ARRAY(args, 2); + args.items[0] = BUFFER_OBJ(buf->handle); + args.items[1] = INTEGER_OBJ(display_tick); + executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err); + if (ERROR_SET(&err)) { + ELOG("error in luahl start: %s", err.msg); + api_clear_error(&err); + } + } } } @@ -1181,7 +1212,27 @@ static void win_update(win_T *wp) idx = 0; /* first entry in w_lines[].wl_size */ row = 0; srow = 0; - lnum = wp->w_topline; /* first line shown in window */ + lnum = wp->w_topline; // first line shown in window + + if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) { + Error err = ERROR_INIT; + FIXED_TEMP_ARRAY(args, 4); + linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) + ? wp->w_botline + : (wp->w_topline + wp->w_height_inner)); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(buf->handle); + args.items[2] = INTEGER_OBJ(wp->w_topline-1); + args.items[3] = INTEGER_OBJ(knownmax); + // TODO(bfredl): we could allow this callback to change mod_top, mod_bot. + // For now the "start" callback is expected to use nvim__buf_redraw_range. + executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err); + if (ERROR_SET(&err)) { + ELOG("error in luahl window: %s", err.msg); + api_clear_error(&err); + } + } + for (;; ) { /* stop updating when reached the end of the window (check for _past_ * the end of the window is at the end of the loop) */ @@ -2229,6 +2280,8 @@ win_line ( row = startrow; + char *luatext = NULL; + if (!number_only) { // To speed up the loop below, set extra_check when there is linebreak, // trailing white space and/or syntax processing to be done. @@ -2454,6 +2507,41 @@ win_line ( line = ml_get_buf(wp->w_buffer, lnum, FALSE); ptr = line; + buf_T *buf = wp->w_buffer; + if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) { + size_t size = STRLEN(line); + if (lua_attr_bufsize < size) { + xfree(lua_attr_buf); + lua_attr_buf = xcalloc(size, sizeof(*lua_attr_buf)); + lua_attr_bufsize = size; + } else if (lua_attr_buf) { + memset(lua_attr_buf, 0, size * sizeof(*lua_attr_buf)); + } + Error err = ERROR_INIT; + // TODO(bfredl): build a macro for the "static array" pattern + // in buf_updates_send_changes? + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(buf->handle); + args.items[2] = INTEGER_OBJ(lnum-1); + lua_attr_active = true; + extra_check = true; + Object o = executor_exec_lua_cb(buf->b_luahl_line, "line", + args, true, &err); + lua_attr_active = false; + if (o.type == kObjectTypeString) { + // TODO(bfredl): this is a bit of a hack. A final API should use an + // "unified" interface where luahl can add both bufhl and virttext + luatext = o.data.string.data; + do_virttext = true; + } else if (ERROR_SET(&err)) { + ELOG("error in luahl line: %s", err.msg); + luatext = err.msg; + do_virttext = true; + api_clear_error(&err); + } + } + if (has_spell && !number_only) { // For checking first word with a capital skip white space. if (cap_col == 0) { @@ -3429,6 +3517,10 @@ win_line ( } } + if (buf->b_luahl && v > 0 && v < (long)lua_attr_bufsize+1) { + char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]); + } + if (wp->w_buffer->terminal) { char_attr = hl_combine_attr(term_attrs[vcol], char_attr); } @@ -3917,8 +4009,14 @@ win_line ( int rightmost_vcol = 0; int i; - VirtText virt_text = do_virttext ? bufhl_info.line->virt_text - : (VirtText)KV_INITIAL_VALUE; + VirtText virt_text; + if (luatext) { + virt_text = (VirtText)KV_INITIAL_VALUE; + kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 })); + } else { + virt_text = do_virttext ? bufhl_info.line->virt_text + : (VirtText)KV_INITIAL_VALUE; + } size_t virt_pos = 0; LineState s = LINE_STATE((char_u *)""); int virt_attr = 0; @@ -4319,6 +4417,7 @@ win_line ( } xfree(p_extra_free); + xfree(luatext); return row; } -- cgit From bc8da6cdbe4e300344e45dbc2eb5b22a7afc9e89 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 11:24:48 -0500 Subject: vim-patch:8.0.1767: with 'incsearch' text may jump up and down Problem: With 'incsearch' text may jump up and down. () Solution: Besides w_botline also save and restore w_empty_rows. (closes # 2530) https://github.com/vim/vim/commit/9d34d90210ba52ebaf45973282e5921f5af364c7 --- src/nvim/ex_getln.c | 8 ++++++++ src/nvim/testdir/test_search.vim | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 8065d764b9..35159060b8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -161,6 +161,8 @@ typedef struct command_line_state { int init_topfill; linenr_T old_botline; linenr_T init_botline; + int old_empty_rows; + int init_empty_rows; pos_T match_start; pos_T match_end; int did_incsearch; @@ -253,6 +255,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) s->init_topline = curwin->w_topline; s->init_topfill = curwin->w_topfill; s->init_botline = curwin->w_botline; + s->init_empty_rows = curwin->w_empty_rows; if (s->firstc == -1) { s->firstc = NUL; @@ -275,6 +278,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) s->old_topline = curwin->w_topline; s->old_topfill = curwin->w_topfill; s->old_botline = curwin->w_botline; + s->old_empty_rows = curwin->w_empty_rows; assert(indent >= 0); @@ -449,6 +453,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) curwin->w_topline = s->old_topline; curwin->w_topfill = s->old_topfill; curwin->w_botline = s->old_botline; + curwin->w_empty_rows = s->old_empty_rows; highlight_match = false; validate_cursor(); // needed for TAB redraw_all_later(SOME_VALID); @@ -1118,6 +1123,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) s->old_topline = curwin->w_topline; s->old_topfill = curwin->w_topfill; s->old_botline = curwin->w_botline; + s->old_empty_rows = curwin->w_empty_rows; update_screen(NOT_VALID); redrawcmdline(); } else { @@ -1243,6 +1249,7 @@ static int command_line_handle_key(CommandLineState *s) s->old_topline = s->init_topline; s->old_topfill = s->init_topfill; s->old_botline = s->init_botline; + s->old_empty_rows = s->init_empty_rows; } redrawcmd(); } else if (ccline.cmdlen == 0 && s->c != Ctrl_W @@ -1876,6 +1883,7 @@ static int command_line_changed(CommandLineState *s) curwin->w_topline = s->old_topline; curwin->w_topfill = s->old_topfill; curwin->w_botline = s->old_botline; + curwin->w_empty_rows = s->old_empty_rows; changed_cline_bef_curs(); update_topline(); diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 68eb311e3c..5d99027ca5 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -1,6 +1,7 @@ " Test for the search command source shared.vim +source screendump.vim func Test_search_cmdline() " See test/functional/legacy/search_spec.lua @@ -549,6 +550,36 @@ func Test_incsearch_with_change() call delete('Xis_change_script') endfunc +func Test_incsearch_scrolling() + if !CanRunVimInTerminal() + return + endif + call assert_equal(0, &scrolloff) + call writefile([ + \ 'let dots = repeat(".", 120)', + \ 'set incsearch cmdheight=2 scrolloff=0', + \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', + \ 'normal gg', + \ 'redraw', + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) + " Need to send one key at a time to force a redraw + call term_sendkeys(buf, '/') + sleep 100m + call term_sendkeys(buf, 't') + sleep 100m + call term_sendkeys(buf, 'a') + sleep 100m + call term_sendkeys(buf, 'r') + sleep 100m + call term_sendkeys(buf, 'g') + call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) + + call term_sendkeys(buf, "\") + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + func Test_search_undefined_behaviour() if !has("terminal") return -- cgit From 80ebfc304e295cdb1bed3d0f2781d7884933719a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 18:45:17 -0500 Subject: vim-patch:8.2.0030: "gF" does not work on output of "verbose command" Problem: "gF" does not work on output of "verbose command". Solution: Recognize " line " and translations. (closes vim/vim#5391) https://github.com/vim/vim/commit/64e74c9cc7d5aab215cf72d9bdd3aac32e128191 --- src/nvim/eval.c | 2 +- src/nvim/eval/typval.c | 11 +---------- src/nvim/globals.h | 2 ++ src/nvim/testdir/test_gf.vim | 8 ++++++++ src/nvim/window.c | 12 +++++++++++- 5 files changed, 23 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 04899f2c99..70fea1c4a9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -24076,7 +24076,7 @@ void option_last_set_msg(LastSet last_set) MSG_PUTS(_("\n\tLast set from ")); MSG_PUTS(p); if (last_set.script_ctx.sc_lnum > 0) { - MSG_PUTS(_(" line ")); + MSG_PUTS(_(line_msg)); msg_outnum((long)last_set.script_ctx.sc_lnum); } if (should_free) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 72ee45a03a..728e3a7fa3 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2732,16 +2732,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) return n; } case VAR_SPECIAL: { - switch (tv->vval.v_special) { - case kSpecialVarTrue: { - return 1; - } - case kSpecialVarFalse: - case kSpecialVarNull: { - return 0; - } - } - break; + return tv->vval.v_special == kSpecialVarTrue ? 1 : 0; } case VAR_UNKNOWN: { emsgf(_(e_intern2), "tv_get_number(UNKNOWN)"); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0a7a2d551e..c6ab574a0f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1059,6 +1059,8 @@ EXTERN char_u e_floatexchange[] INIT(=N_( EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); +EXTERN char line_msg[] INIT(= N_(" line ")); + // For undo we need to know the lowest time possible. EXTERN time_t starttime; diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index d301874891..4a4ffcefa1 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -58,6 +58,14 @@ func Test_gF() call assert_equal('Xfile', bufname('%')) call assert_equal(3, getcurpos()[1]) + enew! + call setline(1, ['one', 'the Xfile line 2, and more', 'three']) + w! Xfile2 + normal 2GfX + normal gF + call assert_equal('Xfile', bufname('%')) + call assert_equal(2, getcurpos()[1]) + set isfname& call delete('Xfile') call delete('Xfile2') diff --git a/src/nvim/window.c b/src/nvim/window.c index 76fc36607c..af78c89618 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6020,10 +6020,20 @@ file_name_in_line ( if (file_lnum != NULL) { char_u *p; + const char *line_english = " line "; + const char *line_transl = _(line_msg); // Get the number after the file name and a separator character. + // Also accept " line 999" with and without the same translation as + // used in last_set_msg(). p = ptr + len; - p = skipwhite(p); + if (STRNCMP(p, line_english, STRLEN(line_english)) == 0) { + p += STRLEN(line_english); + } else if (STRNCMP(p, line_transl, STRLEN(line_transl)) == 0) { + p += STRLEN(line_transl); + } else { + p = skipwhite(p); + } if (*p != NUL) { if (!isdigit(*p)) { p++; // skip the separator -- cgit From 33f6c25af88ec8de2d45327942f4677d6d6bae4f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 21:02:49 -0500 Subject: eval: fix pvs/V1048 --- src/nvim/eval.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 70fea1c4a9..8749c7d8f0 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6626,8 +6626,6 @@ call_func( error = ERROR_NONE; executor_call_lua((const char *)funcname, len, argvars, argcount, rettv); - } else { - error = ERROR_UNKNOWN; } } else if (!builtin_function((const char *)rfname, -1)) { // User defined function. @@ -11797,7 +11795,6 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; case kCdScopeGlobal: // The global scope never has a local directory - rettv->vval.v_number = 0; break; case kCdScopeInvalid: // We should never get here @@ -11909,16 +11906,12 @@ static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int i; - const char *const history = tv_get_string_chk(&argvars[0]); - - i = history == NULL ? HIST_CMD - 1 : get_histtype(history, strlen(history), - false); + HistoryType i = history == NULL + ? HIST_INVALID + : get_histtype(history, strlen(history), false); if (i != HIST_INVALID) { i = get_history_idx(i); - } else { - i = -1; } rettv->vval.v_number = i; } @@ -17736,9 +17729,7 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - if (lastmatch == NULL) { - rettv->vval.v_number = -1; - } else { + if (lastmatch != NULL) { rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); } } -- cgit From 8758e96bf01f5c3619556e89e84db52de1fc8370 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 21:11:18 -0500 Subject: tag: fix pvs/v1048 --- src/nvim/tag.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 3629b37c32..a412ed0276 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2537,8 +2537,6 @@ parse_match( tagp->command_end = p; if (p > tagp->command && p[-1] == '|') { tagp->command_end = p - 1; // drop trailing bar - } else { - tagp->command_end = p; } p += 2; // skip ";\"" if (*p++ == TAB) { -- cgit From 0c70aa65093697d16f5364dacdc92ef0da4e128d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 21:14:04 -0500 Subject: charset: fix pvs/v1048 --- src/nvim/charset.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 78e7861d9d..e9140f8ec5 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1087,8 +1087,6 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he } if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { - added = 0; - if (*p_sbr != NUL) { if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { // Calculate effective window width. -- cgit From dd2e2f8aaaf30779dfadbc5aba6ab1af35cc576d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 21:17:30 -0500 Subject: getchar: fix pvs/v1048 --- src/nvim/getchar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index c038977127..f582670141 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -3106,7 +3106,7 @@ int do_map(int maptype, char_u *arg, int mode, bool is_abbrev) case 0: break; case 1: - result = 1; // invalid arguments + // invalid arguments goto free_and_return; default: assert(false && "Unknown return code from str_to_mapargs!"); -- cgit From a16de288c3d3e033ab0cd60fec2a2d2042774685 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 22 Dec 2019 22:26:05 -0500 Subject: spellfile: fix pvs/v1048 --- src/nvim/spellfile.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 4fac001bc5..8e4f405d99 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1238,17 +1238,14 @@ static int read_sal_section(FILE *fd, slang_T *slang) p = xmalloc(1); p[0] = NUL; smp->sm_lead = p; + smp->sm_lead_w = mb_str2wide(smp->sm_lead); smp->sm_leadlen = 0; smp->sm_oneof = NULL; + smp->sm_oneof_w = NULL; smp->sm_rules = p; smp->sm_to = NULL; - if (has_mbyte) { - smp->sm_lead_w = mb_str2wide(smp->sm_lead); - smp->sm_leadlen = 0; - smp->sm_oneof_w = NULL; - smp->sm_to_w = NULL; - } - ++gap->ga_len; + smp->sm_to_w = NULL; + gap->ga_len++; } // Fill the first-index table. @@ -1713,7 +1710,6 @@ read_tree_node ( if (c == BY_NOFLAGS && !prefixtree) { // No flags, all regions. idxs[idx] = 0; - c = 0; } else if (c != BY_INDEX) { if (prefixtree) { // Read the optional pflags byte, the prefix ID and the -- cgit From 7dacab9afd21485dff1a578a58840b71f3455bca Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 24 Dec 2019 00:40:39 -0500 Subject: vim-patch:8.2.0033: make_extmatch() OOM #11602 Problem: Crash when make_extmatch() runs out of memory. Solution: Check for NULL. (Dominique Pelle, closs vim/vim#5392) https://github.com/vim/vim/commit/7c77b3496710f1be3232cfdc7f6812347fbd914a --- src/nvim/regexp.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 9bc7ef07eb..90dc8ab90f 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -3575,6 +3575,7 @@ theend: * Create a new extmatch and mark it as referenced once. */ static reg_extmatch_T *make_extmatch(void) + FUNC_ATTR_NONNULL_RET { reg_extmatch_T *em = xcalloc(1, sizeof(reg_extmatch_T)); em->refcnt = 1; -- cgit From 31cfd674537236950ead3c25d495f756c751267b Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Tue, 24 Dec 2019 06:41:13 +0100 Subject: version.c: update [ci skip] #11600 vim-patch:8.2.0001: #endif comments do reflect corresponding #ifdef vim-patch:8.2.0003: Build file dependencies are incomplete vim-patch:8.2.0009: VMS: terminal version doesn't build vim-patch:8.2.0022: click in popup window doesn't close it in the GUI vim-patch:8.2.0029: MS-Windows: crash with empty job command vim-patch:8.2.0031: MS-Windows: test for empty job fails vim-patch:8.2.0032: MS-Windows: test for blank job fails vim-patch:8.2.0034: missing check for out of memory --- src/nvim/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 4cadc9fd6c..4360b3776b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -151,7 +151,7 @@ static const int included_patches[] = { 1770, // 1769, 1768, - // 1767, + 1767, 1766, 1765, 1764, -- cgit From b3686b1597ea202de464df72a88fb5c76fd1b814 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 24 Dec 2019 07:53:56 +0100 Subject: system(), jobstart(): raise error on non-executable #11234 * tv_to_argv: error when cmd is not executable Callers always assume that emsg was emitted: - https://github.com/neovim/neovim/blob/57fbf288/src/nvim/eval.c#L12509 - https://github.com/neovim/neovim/blob/57fbf288/src/nvim/eval.c#L17923 - https://github.com/neovim/neovim/blob/57fbf288/src/nvim/eval.c#L18202 * test/functional/provider: display reason from missing_provider * provider#node#Detect: skip / handle non-existing node executable --- src/nvim/eval.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8749c7d8f0..65f029649c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12536,6 +12536,9 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) char *exe_resolved = NULL; if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) { if (arg0 && executable) { + char buf[IOSIZE]; + snprintf(buf, sizeof(buf), "'%s' is not executable", arg0); + EMSG3(_(e_invargNval), "cmd", buf); *executable = false; } return NULL; -- cgit From bbad324b175f1bd35201f0ba73fba1b11af7093d Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Sun, 8 Dec 2019 01:05:49 +0100 Subject: fillchars: adding foldopen, foldsep, foldclose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit You can try it with set fillchars+=foldopen:▾,foldsep:│,foldclose:▸ --- src/nvim/buffer_defs.h | 3 ++ src/nvim/mouse.c | 2 +- src/nvim/option.c | 3 ++ src/nvim/screen.c | 139 +++++++++++++++++++++++++++---------------------- 4 files changed, 83 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index bc4fb2997d..265fc05f67 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1126,6 +1126,9 @@ struct window_S { int stlnc; int vert; int fold; + int foldopen; ///< when fold is open + int foldclosed; ///< when fold is closed + int foldsep; ///< continuous fold marker int diff; int msgsep; int eob; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index deb7ee6342..d29db2cf92 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -350,7 +350,7 @@ retnomove: count |= CURSOR_MOVED; // Cursor has moved } - if (mouse_char == '+') { + if (mouse_char == curwin->w_p_fcs_chars.foldclosed) { count |= MOUSE_FOLD_OPEN; } else if (mouse_char != ' ') { count |= MOUSE_FOLD_CLOSE; diff --git a/src/nvim/option.c b/src/nvim/option.c index e48ed201e8..de25ee3218 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3542,6 +3542,9 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · + { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, + { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, + { &wp->w_p_fcs_chars.foldsep, "foldsep", '|' }, { &wp->w_p_fcs_chars.diff, "diff", '-' }, { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, { &wp->w_p_fcs_chars.eob, "eob", '~' }, diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 4082208dd4..1b7eeeecb8 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -126,7 +126,8 @@ // temporary buffer for rendering a single screenline, so it can be -// comparared with previous contents to calculate smallest delta. +// compared with previous contents to calculate smallest delta. +// Per-cell attributes static size_t linebuf_size = 0; static schar_T *linebuf_char = NULL; static sattr_T *linebuf_attr = NULL; @@ -1814,27 +1815,6 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T col++; } - // 2. Add the 'foldcolumn' - // Reduce the width when there is not enough space. - fdc = compute_foldcolumn(wp, col); - if (fdc > 0) { - fill_foldcolumn(buf, wp, TRUE, lnum); - if (wp->w_p_rl) { - int i; - - copy_text_attr(off + wp->w_grid.Columns - fdc - col, buf, fdc, - win_hl_attr(wp, HLF_FC)); - // reverse the fold column - for (i = 0; i < fdc; i++) { - schar_from_ascii(linebuf_char[off + wp->w_grid.Columns - i - 1 - col], - buf[i]); - } - } else { - copy_text_attr(off + col, buf, fdc, win_hl_attr(wp, HLF_FC)); - } - col += fdc; - } - # define RL_MEMSET(p, v, l) \ do { \ if (wp->w_p_rl) { \ @@ -1848,6 +1828,25 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } \ } while (0) + // 2. Add the 'foldcolumn' + // Reduce the width when there is not enough space. + fdc = compute_foldcolumn(wp, col); + if (fdc > 0) { + fill_foldcolumn(buf, wp, true, lnum); + const char_u *it = &buf[0]; + for (int i = 0; i < fdc; i++) { + int mb_c = mb_ptr2char_adv(&it); + if (wp->w_p_rl) { + schar_from_char(linebuf_char[off + wp->w_grid.Columns - i - 1 - col], + mb_c); + } else { + schar_from_char(linebuf_char[off + col + i], mb_c); + } + } + RL_MEMSET(col, win_hl_attr(wp, HLF_FC), fdc); + col += fdc; + } + /* Set all attributes of the 'number' or 'relativenumber' column and the * text */ RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col); @@ -2068,58 +2067,73 @@ static void copy_text_attr(int off, char_u *buf, int len, int attr) } } -/* - * Fill the foldcolumn at "p" for window "wp". - * Only to be called when 'foldcolumn' > 0. - */ -static void -fill_foldcolumn ( +/// Fills the foldcolumn at "p" for window "wp". +/// Only to be called when 'foldcolumn' > 0. +/// +/// @param[out] p Char array to write into +/// @param lnum Absolute current line number +/// @param closed Whether it is in 'foldcolumn' mode +/// +/// Assume monocell characters +/// @return number of chars added to \param p +static size_t +fill_foldcolumn( char_u *p, win_T *wp, - int closed, /* TRUE of FALSE */ - linenr_T lnum /* current line number */ + int closed, + linenr_T lnum ) { int i = 0; int level; int first_level; - int empty; - int fdc = compute_foldcolumn(wp, 0); - + int fdc = compute_foldcolumn(wp, 0); // available cell width + size_t char_counter = 0; + int symbol = 0; + int len = 0; // Init to all spaces. - memset(p, ' ', (size_t)fdc); + memset(p, ' ', MAX_MCO * fdc + 1); level = win_foldinfo.fi_level; - if (level > 0) { - // If there is only one column put more info in it. - empty = (fdc == 1) ? 0 : 1; - - // If the column is too narrow, we start at the lowest level that - // fits and use numbers to indicated the depth. - first_level = level - fdc - closed + 1 + empty; - if (first_level < 1) { - first_level = 1; - } - - for (i = 0; i + empty < fdc; i++) { - if (win_foldinfo.fi_lnum == lnum - && first_level + i >= win_foldinfo.fi_low_level) { - p[i] = '-'; - } else if (first_level == 1) { - p[i] = '|'; - } else if (first_level + i <= 9) { - p[i] = '0' + first_level + i; - } else { - p[i] = '>'; - } - if (first_level + i == level) { - break; - } + + // If the column is too narrow, we start at the lowest level that + // fits and use numbers to indicated the depth. + first_level = level - fdc - closed + 1; + if (first_level < 1) { + first_level = 1; + } + + for (i = 0; i < MIN(fdc, level); i++) { + if (win_foldinfo.fi_lnum == lnum + && first_level + i >= win_foldinfo.fi_low_level) { + symbol = wp->w_p_fcs_chars.foldopen; + } else if (first_level == 1) { + symbol = wp->w_p_fcs_chars.foldsep; + } else if (first_level + i <= 9) { + symbol = '0' + first_level + i; + } else { + symbol = '>'; + } + + len = utf_char2bytes(symbol, &p[char_counter]); + char_counter += len; + if (first_level + i >= level) { + i++; + break; } } + if (closed) { - p[i >= fdc ? i - 1 : i] = '+'; + if (symbol != 0) { + // rollback length + char_counter -= len; + } + symbol = wp->w_p_fcs_chars.foldclosed; + len = utf_char2bytes(symbol, &p[char_counter]); + char_counter += len; } + + return MAX(char_counter + (fdc-i), (size_t)fdc); } /* @@ -2807,9 +2821,8 @@ win_line ( // Draw the 'foldcolumn'. Allocate a buffer, "extra" may // already be in use. xfree(p_extra_free); - p_extra_free = xmalloc(12 + 1); - fill_foldcolumn(p_extra_free, wp, false, lnum); - n_extra = fdc; + p_extra_free = xmalloc(MAX_MCO * fdc + 1); + n_extra = fill_foldcolumn(p_extra_free, wp, false, lnum); p_extra_free[n_extra] = NUL; p_extra = p_extra_free; c_extra = NUL; -- cgit From c740e3b4b5274e04b9a88a1467abb0fd5590301f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 06:28:10 -0500 Subject: clang/'Logic error': set ret_tv if non-null --- src/nvim/lua/executor.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1d3d9929d3..2cd6c0db66 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -770,8 +770,10 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, typval_T *ret_tv) { if (check_restricted() || check_secure()) { - ret_tv->v_type = VAR_NUMBER; - ret_tv->vval.v_number = 0; + if (ret_tv) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; + } return; } -- cgit From 35c3985da617ffa7ff7f6d7aee4b08267e03e898 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 06:36:48 -0500 Subject: clang/'Dead store': remove dead code --- src/nvim/ex_cmds.c | 4 +--- src/nvim/mouse.c | 2 +- src/nvim/ops.c | 11 ----------- src/nvim/quickfix.c | 4 ++-- 4 files changed, 4 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 0c3b467612..6de3c6bca2 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3249,7 +3249,6 @@ static void extmark_move_regmatch_single(lpos_T startpos, static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i) { colnr_T mincol; - linenr_T u_lnum; mincol = s.startpos.col + 1; linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; @@ -3266,7 +3265,7 @@ static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i) // -- Delete Pattern -- // 1. Move marks in the pattern mincol = s.startpos.col + 1; - u_lnum = n_u_lnum; + linenr_T u_lnum = n_u_lnum; assert(n_u_lnum == u_lnum); extmark_copy_and_place(curbuf, s.lnum, mincol, @@ -3311,7 +3310,6 @@ static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i) assert(s.startpos.lnum == 0); mincol = s.startpos.col + 1; - u_lnum = n_u_lnum; if (!s.newline_in_pat && s.newline_in_sub) { // -- Delete Pattern -- diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index deb7ee6342..de2db63651 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -479,7 +479,7 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) { if (*gridp == msg_grid.handle) { - rowp += msg_grid_pos; + // rowp += msg_grid_pos; // PVS: dead store #11612 *gridp = DEFAULT_GRID_HANDLE; } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 0ca16e2c25..b597c5b214 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1672,17 +1672,6 @@ setmarks: extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0); } - - // Delete characters within one line, - // The case with multiple lines is handled by do_join - } else if (oap->motion_type == kMTCharWise && oap->line_count == 1) { - // + 1 to change to buf mode, then plus 1 to fit function requirements - endcol = oap->end.col + 1 + 1; - - lnum = curwin->w_cursor.lnum; - if (oap->is_VIsual == false) { - endcol = MAX(endcol - 1, mincol); - } } return OK; } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 5d30ca624f..3c1cebfcf3 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4513,7 +4513,7 @@ static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n) { while (n-- > 0 && !got_int) { - qfline_T *first_entry = entry; + // qfline_T *first_entry = entry; int first_errornr = *errornr; // Treat all the entries on the same line in this file as one @@ -4523,7 +4523,7 @@ static void qf_get_nth_below_entry(qfline_T *entry, || entry->qf_next->qf_fnum != entry->qf_fnum) { // If multiple entries are on the same line, then use the first // entry - entry = first_entry; + // entry = first_entry; *errornr = first_errornr; break; } -- cgit From 362c3a3ccfbe0e779b8fbfd0eaa829f1ea20c9cb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 06:48:27 -0500 Subject: api/vim: fix pvs/v1048 --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 2f59edee33..661946e8d2 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -335,7 +335,7 @@ void nvim_input_mouse(String button, String action, String modifier, if (strequal(action.data, "down")) { code = KE_MOUSEUP; } else if (strequal(action.data, "up")) { - code = KE_MOUSEDOWN; + // code = KE_MOUSEDOWN } else if (strequal(action.data, "left")) { code = KE_MOUSERIGHT; } else if (strequal(action.data, "right")) { -- cgit From 0be59d2b5e5d9460c6f8cbf14898538f30721f89 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 06:52:34 -0500 Subject: hardcopy: fix pvs/v1048 --- src/nvim/hardcopy.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 4b361d2d45..cb5c5338a1 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -2305,13 +2305,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) || TOLOWER_ASC(printer_opts[OPT_PRINT_COLLATE].string[0]) == 'y'); if (prt_collate) { - /* TODO: Get number of collated copies wanted. */ - psettings->n_collated_copies = 1; + // TODO(vim): Get number of collated copies wanted. } else { - /* TODO: Get number of uncollated copies wanted and update the cached - * count. - */ - prt_num_copies = 1; + // TODO(vim): Get number of uncollated copies wanted and update the cached + // count. } psettings->jobname = jobname; -- cgit From 6a0242beead9196a60836a375d0207aa50126973 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 07:02:45 -0500 Subject: quickfix: qf_parse_fmt_plus never fails --- src/nvim/quickfix.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3c1cebfcf3..71c6f06ac0 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1341,9 +1341,10 @@ static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) /// Parse the match for '%+' format pattern. The whole matching line is included /// in the error string. Return the matched line in "fields->errmsg". -static int qf_parse_fmt_plus(char_u *linebuf, - size_t linelen, - qffields_T *fields) +static void qf_parse_fmt_plus(const char_u *linebuf, + size_t linelen, + qffields_T *fields) + FUNC_ATTR_NONNULL_ALL { if (linelen >= fields->errmsglen) { // linelen + null terminator @@ -1351,7 +1352,6 @@ static int qf_parse_fmt_plus(char_u *linebuf, fields->errmsglen = linelen + 1; } STRLCPY(fields->errmsg, linebuf, linelen + 1); - return QF_OK; } /// Parse the match for error message ('%m') pattern in regmatch. @@ -1508,7 +1508,7 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, status = qf_parse_fmt_f(regmatch, midx, fields, idx); } else if (i == 5) { if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ - status = qf_parse_fmt_plus(linebuf, linelen, fields); + qf_parse_fmt_plus(linebuf, linelen, fields); } else if (midx > 0) { // %m status = qf_parse_fmt_m(regmatch, midx, fields); } -- cgit From fd429345c9a2d889fe04356300a7ceee483ef097 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 17:15:52 -0500 Subject: search: fix pvs/v1048 --- src/nvim/search.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/search.c b/src/nvim/search.c index 5e32715e49..3ee9777805 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2175,17 +2175,14 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) * Return MAXCOL if not, otherwise return the column. * TODO: skip strings. */ -static int check_linecomment(char_u *line) +static int check_linecomment(const char_u *line) { - char_u *p; - - p = line; - /* skip Lispish one-line comments */ + const char_u *p = line; // scan from start + // skip Lispish one-line comments if (curbuf->b_p_lisp) { if (vim_strchr(p, ';') != NULL) { /* there may be comments */ int in_str = FALSE; /* inside of string */ - p = line; /* scan from start */ while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL) { if (*p == '"') { if (in_str) { -- cgit From f6b4547598fda6b6c45477ade00716fa93b271e9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 18:23:14 -0500 Subject: ex_cmds: fix pvs/v781 --- src/nvim/ex_cmds.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 6de3c6bca2..85048427b1 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4945,17 +4945,21 @@ help_heuristic( * If the match is more than 2 chars from the start, multiply by 200 to * put it after matches at the start. */ - if (ASCII_ISALNUM(matched_string[offset]) && offset > 0 - && ASCII_ISALNUM(matched_string[offset - 1])) + if (offset > 0 + && ASCII_ISALNUM(matched_string[offset]) + && ASCII_ISALNUM(matched_string[offset - 1])) { offset += 10000; - else if (offset > 2) + } else if (offset > 2) { offset *= 200; - if (wrong_case) + } + if (wrong_case) { offset += 5000; - /* Features are less interesting than the subjects themselves, but "+" - * alone is not a feature. */ - if (matched_string[0] == '+' && matched_string[1] != NUL) + } + // Features are less interesting than the subjects themselves, but "+" + // alone is not a feature. + if (matched_string[0] == '+' && matched_string[1] != NUL) { offset += 100; + } return (int)(100 * num_letters + STRLEN(matched_string) + offset); } -- cgit From 23dbe73585fbedfdeb09aac032ffe12ae79f7397 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 18:30:28 -0500 Subject: ex_docmd: fix pvs/v781 --- src/nvim/ex_docmd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d16ad9db2c..afe2660cdf 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2807,9 +2807,11 @@ int modifier_len(char_u *cmd) for (j = 0; p[j] != NUL; ++j) if (p[j] != cmdmods[i].name[j]) break; - if (!ASCII_ISALPHA(p[j]) && j >= cmdmods[i].minlen - && (p == cmd || cmdmods[i].has_count)) + if (j >= cmdmods[i].minlen + && !ASCII_ISALPHA(p[j]) + && (p == cmd || cmdmods[i].has_count)) { return j + (int)(p - cmd); + } } return 0; } -- cgit From 251177b63b591018cf865e809692ba6eb18f7a5c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 18:32:29 -0500 Subject: ex_getln: fix pvs/v781 --- src/nvim/ex_getln.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 35159060b8..e5f5e5ad87 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3575,7 +3575,7 @@ static int ccheck_abbr(int c) // Do not consider '<,'> be part of the mapping, skip leading whitespace. // Actually accepts any mark. - while (ascii_iswhite(ccline.cmdbuff[spos]) && spos < ccline.cmdlen) { + while (spos < ccline.cmdlen && ascii_iswhite(ccline.cmdbuff[spos])) { spos++; } if (ccline.cmdlen - spos > 5 -- cgit From d6ba578ccb68e7b79df9fabb8999e8daa2f2a5a5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 18:42:07 -0500 Subject: misc1: fix pvs/v781 --- src/nvim/misc1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index a871d424c6..8c19a2de66 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -277,7 +277,7 @@ int get_last_leader_offset(char_u *line, char_u **flags) // whitespace. Otherwise we would think we are inside a // comment if the middle part appears somewhere in the middle // of the line. E.g. for C the "*" appears often. - for (j = 0; ascii_iswhite(line[j]) && j <= i; j++) { + for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { } if (j < i) { continue; -- cgit From 234c4a846bc78b2cc119b72b994b3ac3f401c8c3 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 25 Dec 2019 19:39:44 -0500 Subject: os/env: fix pvs/v781 --- src/nvim/os/env.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 360609c50d..ec266796a8 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -266,10 +266,8 @@ void os_copy_fullenv(char **env, size_t env_size) extern char **environ; # endif - size_t i = 0; - while (environ[i] != NULL && i < env_size) { + for (size_t i = 0; i < env_size && environ[i] != NULL; i++) { env[i] = xstrdup(environ[i]); - i++; } #endif } -- cgit From 703ed11c97256997aa0ce8aa5fe04b6e89e8e829 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 10 Sep 2019 00:42:59 -0400 Subject: vim-patch:8.0.1491: the minimum width of the popup menu is hard coded Problem: The minimum width of the popup menu is hard coded. Solution: Add the 'pumwidth' option. (Christian Brabandt, James McCoy, closes vim/vim#2314) https://github.com/vim/vim/commit/a8f04aa275984183bab5bb583b128f38c64abb69 --- src/nvim/option_defs.h | 1 + src/nvim/options.lua | 7 ++++++ src/nvim/popupmnu.c | 68 ++++++++++++++++++++++++++++++++++++++++++++------ src/nvim/screen.c | 2 -- src/nvim/screen.h | 3 +++ 5 files changed, 71 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 4f9f32794b..b03342b387 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -372,6 +372,7 @@ EXTERN int p_confirm; // 'confirm' EXTERN int p_cp; // 'compatible' EXTERN char_u *p_cot; // 'completeopt' EXTERN long p_ph; // 'pumheight' +EXTERN long p_pw; // 'pumwidth' EXTERN long p_pb; // 'pumblend' EXTERN char_u *p_cpo; // 'cpoptions' EXTERN char_u *p_csprg; // 'cscopeprg' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 93bfc1c0b1..e80d8e4cc2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1822,6 +1822,13 @@ return { varname='p_ph', defaults={if_true={vi=0}} }, + { + full_name='pumwidth', abbreviation='pw', + type='number', scope={'global'}, + vi_def=true, + varname='p_pw', + defaults={if_true={vi=0}} + }, { full_name='pumblend', abbreviation='pb', type='number', scope={'global'}, diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 7a7f8a9d75..be585b78de 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -82,6 +82,13 @@ static void pum_compute_size(void) } } +// Return the minimum width of the popup menu. +static int pum_get_width(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return p_pw == 0 ? PUM_DEF_WIDTH : (int)p_pw; +} + /// Show the popup menu with items "array[size]". /// "array" must remain valid until pum_undisplay() is called! /// When possible the leftmost character is aligned with screen column "col". @@ -161,7 +168,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } } - def_width = PUM_DEF_WIDTH; + def_width = pum_get_width(); win_T *pvwin = NULL; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -277,11 +284,13 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, def_width = max_width; } - if ((((col < Columns - PUM_DEF_WIDTH) || (col < Columns - max_width)) + if ((((col < Columns - pum_get_width()) || (col < Columns - max_width)) && !curwin->w_p_rl) - || (curwin->w_p_rl && ((col > PUM_DEF_WIDTH) || (col > max_width)))) { + || (curwin->w_p_rl && ((col > pum_get_width()) || (col > max_width)))) { // align pum column with "col" pum_col = col; + + // start with the maximum space available if (curwin->w_p_rl) { pum_width = pum_col - pum_scrollbar + 1; } else { @@ -291,11 +300,54 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) - && (pum_width > PUM_DEF_WIDTH)) { + && (pum_width > pum_get_width())) { + // the width is too much, make it narrower pum_width = max_width + pum_kind_width + pum_extra_width + 1; - if (pum_width < PUM_DEF_WIDTH) { - pum_width = PUM_DEF_WIDTH; + if (pum_width < pum_get_width()) { + pum_width = pum_get_width(); + } + } + } else if (((col > pum_get_width() || col > max_width) + && !curwin->w_p_rl) + || (curwin->w_p_rl + && (col < Columns - pum_get_width() + || col < Columns - max_width))) { + // align right pum edge with "col" + if (curwin->w_p_rl) { + pum_col = col + max_width + pum_scrollbar + 1; + if (pum_col >= Columns) { + pum_col = Columns - 1; + } + } else { + pum_col = col - max_width - pum_scrollbar; + if (pum_col < 0) { + pum_col = 0; + } + } + + if (curwin->w_p_rl) { + pum_width = W_ENDCOL(curwin) - pum_col - pum_scrollbar + 1; + } else { + pum_width = pum_col - pum_scrollbar; + } + + if (pum_width < pum_get_width()) { + pum_width = pum_get_width(); + if (curwin->w_p_rl) { + if (pum_width > pum_col) { + pum_width = pum_col; + } + } else { + if (pum_width >= Columns - pum_col) { + pum_width = Columns - pum_col - 1; + } + } + } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 + && pum_width > pum_get_width()) { + pum_width = max_width + pum_kind_width + pum_extra_width + 1; + if (pum_width < pum_get_width()) { + pum_width = pum_get_width(); } } } else if (Columns < def_width) { @@ -309,9 +361,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, assert(Columns - 1 >= INT_MIN); pum_width = (int)(Columns - 1); } else { - if (max_width > PUM_DEF_WIDTH) { + if (max_width > pum_get_width()) { // truncate - max_width = PUM_DEF_WIDTH; + max_width = pum_get_width(); } if (curwin->w_p_rl) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 1b7eeeecb8..bfb256a0ad 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -121,8 +121,6 @@ #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ -#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) -#define W_ENDROW(wp) (wp->w_winrow + wp->w_height) // temporary buffer for rendering a single screenline, so it can be diff --git a/src/nvim/screen.h b/src/nvim/screen.h index 61ed98247d..9267672cf1 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -56,6 +56,9 @@ extern StlClickDefinition *tab_page_click_defs; /// Size of the tab_page_click_defs array extern long tab_page_click_defs_size; +#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) +#define W_ENDROW(wp) (wp->w_winrow + wp->w_height) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.h.generated.h" #endif -- cgit From 669d675ef3f7c7ed4c8b702f53e3a77a986bc7cb Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 10 Sep 2019 18:54:31 -0400 Subject: vim-patch:8.0.1495: having 'pumwidth' default to zero has no merit Problem: Having 'pumwidth' default to zero has no merit. Solution: Make the default 15, as the actual default value. https://github.com/vim/vim/commit/42443c7d7fecc3a2a72154bb6139b028438617c2 Includes 'pumwidth' documentation changes from 8.0.1531. Sort 'pum*' option in alphabetical order. --- src/nvim/option_defs.h | 2 +- src/nvim/options.lua | 16 ++++++++-------- src/nvim/popupmnu.c | 38 +++++++++++++++----------------------- 3 files changed, 24 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b03342b387..fcad6836bf 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -371,9 +371,9 @@ EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' EXTERN int p_cp; // 'compatible' EXTERN char_u *p_cot; // 'completeopt' +EXTERN long p_pb; // 'pumblend' EXTERN long p_ph; // 'pumheight' EXTERN long p_pw; // 'pumwidth' -EXTERN long p_pb; // 'pumblend' EXTERN char_u *p_cpo; // 'cpoptions' EXTERN char_u *p_csprg; // 'cscopeprg' EXTERN int p_csre; // 'cscoperelative' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index e80d8e4cc2..7d080b8d56 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1816,26 +1816,26 @@ return { defaults={if_true={vi=true}} }, { - full_name='pumheight', abbreviation='ph', + full_name='pumblend', abbreviation='pb', type='number', scope={'global'}, vi_def=true, - varname='p_ph', + redraw={'ui_option'}, + varname='p_pb', defaults={if_true={vi=0}} }, { - full_name='pumwidth', abbreviation='pw', + full_name='pumheight', abbreviation='ph', type='number', scope={'global'}, vi_def=true, - varname='p_pw', + varname='p_ph', defaults={if_true={vi=0}} }, { - full_name='pumblend', abbreviation='pb', + full_name='pumwidth', abbreviation='pw', type='number', scope={'global'}, vi_def=true, - redraw={'ui_option'}, - varname='p_pb', - defaults={if_true={vi=0}} + varname='p_pw', + defaults={if_true={vi=15}} }, { full_name='pyxversion', abbreviation='pyx', diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index be585b78de..2ea55c0710 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -82,13 +82,6 @@ static void pum_compute_size(void) } } -// Return the minimum width of the popup menu. -static int pum_get_width(void) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - return p_pw == 0 ? PUM_DEF_WIDTH : (int)p_pw; -} - /// Show the popup menu with items "array[size]". /// "array" must remain valid until pum_undisplay() is called! /// When possible the leftmost character is aligned with screen column "col". @@ -104,7 +97,6 @@ static int pum_get_width(void) void pum_display(pumitem_T *array, int size, int selected, bool array_changed, int cmd_startcol) { - int def_width; int context_lines; int above_row; int below_row; @@ -168,7 +160,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } } - def_width = pum_get_width(); + int def_width = (int)p_pw; win_T *pvwin = NULL; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -284,9 +276,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, def_width = max_width; } - if ((((col < Columns - pum_get_width()) || (col < Columns - max_width)) + if ((((col < Columns - p_pw) || (col < Columns - max_width)) && !curwin->w_p_rl) - || (curwin->w_p_rl && ((col > pum_get_width()) || (col > max_width)))) { + || (curwin->w_p_rl && ((col > p_pw) || (col > max_width)))) { // align pum column with "col" pum_col = col; @@ -300,18 +292,18 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) - && (pum_width > pum_get_width())) { + && (pum_width > p_pw)) { // the width is too much, make it narrower pum_width = max_width + pum_kind_width + pum_extra_width + 1; - if (pum_width < pum_get_width()) { - pum_width = pum_get_width(); + if (pum_width < p_pw) { + pum_width = (int)p_pw; } } - } else if (((col > pum_get_width() || col > max_width) + } else if (((col > p_pw || col > max_width) && !curwin->w_p_rl) || (curwin->w_p_rl - && (col < Columns - pum_get_width() + && (col < Columns - p_pw || col < Columns - max_width))) { // align right pum edge with "col" if (curwin->w_p_rl) { @@ -332,8 +324,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_width = pum_col - pum_scrollbar; } - if (pum_width < pum_get_width()) { - pum_width = pum_get_width(); + if (pum_width < p_pw) { + pum_width = (int)p_pw; if (curwin->w_p_rl) { if (pum_width > pum_col) { pum_width = pum_col; @@ -344,10 +336,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, } } } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 - && pum_width > pum_get_width()) { + && pum_width > p_pw) { pum_width = max_width + pum_kind_width + pum_extra_width + 1; - if (pum_width < pum_get_width()) { - pum_width = pum_get_width(); + if (pum_width < p_pw) { + pum_width = (int)p_pw; } } } else if (Columns < def_width) { @@ -361,9 +353,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, assert(Columns - 1 >= INT_MIN); pum_width = (int)(Columns - 1); } else { - if (max_width > pum_get_width()) { + if (max_width > p_pw) { // truncate - max_width = pum_get_width(); + max_width = (int)p_pw; } if (curwin->w_p_rl) { -- cgit From ac85d1f52f0d298b03f7d0e0be3f4b7ce777cae7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 10 Sep 2019 19:12:29 -0400 Subject: vim-patch:8.1.0670: macro for popup menu width is unused Problem: Macro for popup menu width is unused. Solution: Remove it. (Hirohito Higashi) https://github.com/vim/vim/commit/3d631cb0b34b03c7bdf45ad852d3644c7cf62743 --- src/nvim/popupmnu.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 2ea55c0710..561cb846f1 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -54,7 +54,6 @@ static bool pum_invalid = false; // the screen was just cleared # include "popupmnu.c.generated.h" #endif #define PUM_DEF_HEIGHT 10 -#define PUM_DEF_WIDTH 15 static void pum_compute_size(void) { -- cgit From d56f36f46c400ead225caaef8ac2717ff1e4bfec Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 10 Sep 2019 20:47:33 -0400 Subject: vim-patch:8.0.1522: popup menu is positioned in the wrong place Problem: Popup menu is positioned in the wrong place. (Davit Samvelyan, Boris Staletic) Solution: Correct computation of the column and the conditions for that. (Hirohito Higashi, closes vim/vim#2640) https://github.com/vim/vim/commit/4287ed33ddc324d26dd05d3e19596dd74cf479d6 --- src/nvim/popupmnu.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 561cb846f1..a715129f8a 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -305,22 +305,25 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, && (col < Columns - p_pw || col < Columns - max_width))) { // align right pum edge with "col" - if (curwin->w_p_rl) { + if (curwin->w_p_rl + && col < max_width + pum_scrollbar + 1) { pum_col = col + max_width + pum_scrollbar + 1; if (pum_col >= Columns) { pum_col = Columns - 1; } - } else { - pum_col = col - max_width - pum_scrollbar; - if (pum_col < 0) { - pum_col = 0; + } else if (!curwin->w_p_rl) { + if (col > Columns - max_width - pum_scrollbar) { + pum_col = col - max_width - pum_scrollbar; + if (pum_col < 0) { + pum_col = 0; + } } } if (curwin->w_p_rl) { - pum_width = W_ENDCOL(curwin) - pum_col - pum_scrollbar + 1; + pum_width = pum_col - pum_scrollbar + 1; } else { - pum_width = pum_col - pum_scrollbar; + pum_width = Columns - pum_col - pum_scrollbar; } if (pum_width < p_pw) { -- cgit From 51c9e3c4d19f26af11a86a8f736a74a5cb6f2fa2 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 10 Sep 2019 20:53:13 -0400 Subject: vim-patch:8.0.1538: popupmenu is too far left when completion is long Problem: Popupmenu is too far left when completion is long. (Linwei) Solution: Adjust column computations. (Hirohito Higashi, closes vim/vim#2661) https://github.com/vim/vim/commit/bb008dd3239c5fe3ac04501e38e4c950fa9426c8 --- src/nvim/popupmnu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index a715129f8a..d53f2c2c7b 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -306,13 +306,13 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, || col < Columns - max_width))) { // align right pum edge with "col" if (curwin->w_p_rl - && col < max_width + pum_scrollbar + 1) { + && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { pum_col = col + max_width + pum_scrollbar + 1; if (pum_col >= Columns) { pum_col = Columns - 1; } } else if (!curwin->w_p_rl) { - if (col > Columns - max_width - pum_scrollbar) { + if (curwin->w_wincol > Columns - max_width - pum_scrollbar) { pum_col = col - max_width - pum_scrollbar; if (pum_col < 0) { pum_col = 0; -- cgit From 1d3d84fe818efaf47d8f818fcc44368d144443a1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 26 Dec 2019 13:21:35 -0500 Subject: vim-patch:8.1.0554: popup menu overlaps with preview window Problem: Popup menu overlaps with preview window. Solution: Adjust the height computation. (Hirohito Higashi, closes vim/vim#3414) https://github.com/vim/vim/commit/614ab8aa00346724bfc27980d25985d482269b75 Cherry-picked "row -> pum_win_row" rename changes from patch 8.1.0062. --- src/nvim/popupmnu.c | 45 ++++++++++++++++++----------------------- src/nvim/testdir/test_popup.vim | 22 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index d53f2c2c7b..c9ff49d233 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -100,7 +100,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, int above_row; int below_row; int redo_count = 0; - int row; + int pum_win_row; int col; if (!pum_is_visible) { @@ -121,12 +121,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, // wildoptions=pum if (State == CMDLINE) { - row = ui_has(kUICmdline) ? 0 : cmdline_row; + pum_win_row = ui_has(kUICmdline) ? 0 : cmdline_row; col = cmd_startcol; pum_anchor_grid = ui_has(kUICmdline) ? -1 : DEFAULT_GRID_HANDLE; } else { // anchor position: the start of the completed word - row = curwin->w_wrow; + pum_win_row = curwin->w_wrow; if (curwin->w_p_rl) { col = curwin->w_width - curwin->w_wcol - 1; } else { @@ -136,7 +136,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_anchor_grid = (int)curwin->w_grid.handle; if (!ui_has(kUIMultigrid)) { pum_anchor_grid = (int)default_grid.handle; - row += curwin->w_winrow; + pum_win_row += curwin->w_winrow; col += curwin->w_wincol; } } @@ -152,7 +152,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info))); ADD(arr, ARRAY_OBJ(item)); } - ui_call_popupmenu_show(arr, selected, row, col, pum_anchor_grid); + ui_call_popupmenu_show(arr, selected, pum_win_row, col, + pum_anchor_grid); } else { ui_call_popupmenu_select(selected); return; @@ -188,11 +189,11 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_height = (int)p_ph; } - // Put the pum below "row" if possible. If there are few lines decide on - // where there is more room. - if (row + 2 >= below_row - pum_height - && row - above_row > (below_row - above_row) / 2) { - // pum above "row" + // Put the pum below "pum_win_row" if possible. + // If there are few lines decide on where there is more room. + if (pum_win_row + 2 >= below_row - pum_height + && pum_win_row - above_row > (below_row - above_row) / 2) { + // pum above "pum_win_row" pum_above = true; // Leave two lines of context if possible @@ -202,12 +203,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, context_lines = curwin->w_wrow - curwin->w_cline_row; } - if (row >= size + context_lines) { - pum_row = row - size - context_lines; + if (pum_win_row >= size + context_lines) { + pum_row = pum_win_row - size - context_lines; pum_height = size; } else { pum_row = 0; - pum_height = row - context_lines; + pum_height = pum_win_row - context_lines; } if ((p_ph > 0) && (pum_height > p_ph)) { @@ -215,7 +216,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_height = (int)p_ph; } } else { - // pum below "row" + // pum below "pum_win_row" pum_above = false; // Leave two lines of context if possible @@ -226,7 +227,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, + curwin->w_cline_height - curwin->w_wrow; } - pum_row = row + context_lines; + pum_row = pum_win_row + context_lines; if (size > below_row - pum_row) { pum_height = below_row - pum_row; } else { @@ -243,16 +244,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, return; } - // If there is a preview window above, avoid drawing over it. - // Do keep at least 10 entries. - if (pvwin != NULL && pum_row < above_row && pum_height > 10) { - if (row - above_row < 10) { - pum_row = row - 10; - pum_height = 10; - } else { - pum_row = above_row; - pum_height = row - above_row; - } + // If there is a preview window above avoid drawing over it. + if (pvwin != NULL && pum_row < above_row && pum_height > above_row) { + pum_row = above_row; + pum_height = pum_win_row - above_row; } if (pum_external) { return; diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 9db6112eeb..27c90f9114 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -733,6 +733,28 @@ func Test_popup_and_preview_autocommand() bw! endfunc +func Test_popup_and_previewwindow_dump() + if !CanRunVimInTerminal() + return + endif + call writefile([ + \ 'set previewheight=9', + \ 'silent! pedit', + \ 'call setline(1, map(repeat(["ab"], 10), "v:val. v:key"))', + \ 'exec "norm! G\\"', + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {}) + + " Test that popup and previewwindow do not overlap. + call term_sendkeys(buf, "o\\") + sleep 100m + call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {}) + + call term_sendkeys(buf, "\u") + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + func Test_popup_position() if !CanRunVimInTerminal() return -- cgit From be4165308f072c499a1bdbf8af36a865ec29b43f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 26 Dec 2019 21:38:41 -0500 Subject: screen: fix pvs/v1048 --- src/nvim/screen.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index bfb256a0ad..0612575e67 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2445,8 +2445,6 @@ win_line ( pos.lnum = lnum; pos.col = search_match_endcol; getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); - } else { - tocol = MAXCOL; } // do at least one character; happens when past end of line if (fromcol == tocol) { -- cgit From e80f61020adfe6f2503c59cfea86f47fc6b0887d Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 28 Dec 2019 00:46:40 -0500 Subject: vim-patch:8.0.1540: popup menu positioning fails with longer string Problem: Popup menu positioning fails with longer string. Solution: Only align with right side of window when width is less than 'pumwidth' (closes vim/vim#2661) https://github.com/vim/vim/commit/2b10bcbfc1c025bf7e6358326ee70105e7d30e96 --- src/nvim/popupmnu.c | 45 +++++++++++++++++++++++------------------ src/nvim/testdir/test_popup.vim | 9 +++++++++ 2 files changed, 34 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index c9ff49d233..4ba2a1032d 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -83,7 +83,7 @@ static void pum_compute_size(void) /// Show the popup menu with items "array[size]". /// "array" must remain valid until pum_undisplay() is called! -/// When possible the leftmost character is aligned with screen column "col". +/// When possible the leftmost character is aligned with cursor column. /// The menu appears above the screen line "row" or at "row" + "height" - 1. /// /// @param array @@ -101,7 +101,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, int below_row; int redo_count = 0; int pum_win_row; - int col; + int cursor_col; if (!pum_is_visible) { // To keep the code simple, we only allow changing the @@ -122,22 +122,22 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, // wildoptions=pum if (State == CMDLINE) { pum_win_row = ui_has(kUICmdline) ? 0 : cmdline_row; - col = cmd_startcol; + cursor_col = cmd_startcol; pum_anchor_grid = ui_has(kUICmdline) ? -1 : DEFAULT_GRID_HANDLE; } else { // anchor position: the start of the completed word pum_win_row = curwin->w_wrow; if (curwin->w_p_rl) { - col = curwin->w_width - curwin->w_wcol - 1; + cursor_col = curwin->w_width - curwin->w_wcol - 1; } else { - col = curwin->w_wcol; + cursor_col = curwin->w_wcol; } pum_anchor_grid = (int)curwin->w_grid.handle; if (!ui_has(kUIMultigrid)) { pum_anchor_grid = (int)default_grid.handle; pum_win_row += curwin->w_winrow; - col += curwin->w_wincol; + cursor_col += curwin->w_wincol; } } @@ -152,7 +152,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info))); ADD(arr, ARRAY_OBJ(item)); } - ui_call_popupmenu_show(arr, selected, pum_win_row, col, + ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col, pum_anchor_grid); } else { ui_call_popupmenu_select(selected); @@ -270,11 +270,13 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, def_width = max_width; } - if ((((col < Columns - p_pw) || (col < Columns - max_width)) + if ((((cursor_col < Columns - p_pw) + || (cursor_col < Columns - max_width)) && !curwin->w_p_rl) - || (curwin->w_p_rl && ((col > p_pw) || (col > max_width)))) { - // align pum column with "col" - pum_col = col; + || (curwin->w_p_rl + && ((cursor_col > p_pw) || (cursor_col > max_width)))) { + // align pum with "cursor_col" + pum_col = cursor_col; // start with the maximum space available if (curwin->w_p_rl) { @@ -287,28 +289,31 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) && (pum_width > p_pw)) { - // the width is too much, make it narrower + // the width is more than needed for the items, make it + // narrower pum_width = max_width + pum_kind_width + pum_extra_width + 1; if (pum_width < p_pw) { pum_width = (int)p_pw; } } - } else if (((col > p_pw || col > max_width) + } else if (((cursor_col > p_pw || cursor_col > max_width) && !curwin->w_p_rl) || (curwin->w_p_rl - && (col < Columns - p_pw - || col < Columns - max_width))) { - // align right pum edge with "col" + && (cursor_col < Columns - p_pw + || cursor_col < Columns - max_width))) { + // align pum edge with "cursor_col" if (curwin->w_p_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { - pum_col = col + max_width + pum_scrollbar + 1; + pum_col = cursor_col + max_width + pum_scrollbar + 1; if (pum_col >= Columns) { pum_col = Columns - 1; } } else if (!curwin->w_p_rl) { - if (curwin->w_wincol > Columns - max_width - pum_scrollbar) { - pum_col = col - max_width - pum_scrollbar; + if (curwin->w_wincol > Columns - max_width - pum_scrollbar + && max_width <= p_pw) { + // use full width to end of the screen + pum_col = cursor_col - max_width - pum_scrollbar; if (pum_col < 0) { pum_col = 0; } @@ -515,7 +520,7 @@ void pum_redraw(void) if (size < pum_width) { // Most left character requires 2-cells but only 1 cell - // is available on screen. Put a '<' on the left of the + // is available on screen. Put a '<' on the left of the // pum item *(--rt) = '<'; size++; diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 27c90f9114..e5696f4cbb 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -784,6 +784,15 @@ func Test_popup_position() call term_sendkeys(buf, "GA\") call VerifyScreenDump(buf, 'Test_popup_position_03', {'rows': 8}) + " completed text wider than the window and 'pumwidth' smaller than available + " space + call term_sendkeys(buf, "\u") + call term_sendkeys(buf, ":set pumwidth=20\") + call term_sendkeys(buf, "ggI123456789_\") + call term_sendkeys(buf, "jI123456789_\") + call term_sendkeys(buf, "GA\") + call VerifyScreenDump(buf, 'Test_popup_position_04', {'rows': 10}) + call term_sendkeys(buf, "\u") call StopVimInTerminal(buf) call delete('Xtest') -- cgit From 74e37ac665bf2f5ec5d20f73352972cca8808f04 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 29 Dec 2019 22:42:51 -0500 Subject: vim-patch:8.1.2087: cannot easily select one test function to execute Problem: Cannot easily select one test function to execute. Solution: Support the $TEST_FILTER environment variable. (Ozaki Kiichi, closes vim/vim#2695) https://github.com/vim/vim/commit/a7f6c3cf071bb6267e0bd2eb3d27ca240381ba87 --- src/nvim/testdir/runtest.vim | 6 ++++++ src/nvim/testdir/summarize.vim | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 2d4134a644..fd49e48c2d 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -319,6 +319,12 @@ if argc() > 1 let s:tests = filter(s:tests, 'v:val =~ argv(1)') endif +" If the environment variable $TEST_FILTER is set then filter the function +" names against it. +if $TEST_FILTER != '' + let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER') +endif + " Execute the tests in alphabetical order. for s:test in sort(s:tests) " Silence, please! diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim index 5bbf0b338a..9cf3f694ca 100644 --- a/src/nvim/testdir/summarize.vim +++ b/src/nvim/testdir/summarize.vim @@ -8,7 +8,7 @@ if 1 let g:failed += a:match+0 elseif a:type ==# 'skipped' let g:skipped += 1 - call extend(g:skipped_output, ["\t".a:match]) + call extend(g:skipped_output, ["\t" .. a:match]) endif endfunc @@ -19,6 +19,10 @@ if 1 let g:failed_output = [] let output = [""] + if $TEST_FILTER != '' + call extend(g:skipped_output, ["\tAll tests not matching $TEST_FILTER: '" .. $TEST_FILTER .. "'"]) + endif + try " This uses the :s command to just fetch and process the output of the " tests, it doesn't actually replace anything. -- cgit From 1250a01325102890499de1ec5ff3c132c28331d4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 29 Dec 2019 22:43:38 -0500 Subject: vim-patch:8.2.0058: running tests changes ~/.viminfo Problem: Running tests changes ~/.viminfo. Solution: Make 'viminfo' empty when summarizing tests results. (closes vim/vim#5414) https://github.com/vim/vim/commit/eb698d0b3295675f184ad4b62034e064cded4ade --- src/nvim/testdir/summarize.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim index 9cf3f694ca..4e4135287b 100644 --- a/src/nvim/testdir/summarize.vim +++ b/src/nvim/testdir/summarize.vim @@ -1,6 +1,7 @@ if 1 " This is executed only with the eval feature set nocompatible + set viminfo= func Count(match, type) if a:type ==# 'executed' let g:executed += (a:match+0) -- cgit From 5fc8a7ee096cf1a5cf78058a483b781c5c0c9af5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Dec 2019 00:32:42 -0500 Subject: vim-patch:8.1.1300: in a terminal 'ballooneval' does not work right away Problem: In a terminal 'ballooneval' does not work right away. Solution: Flush output after drawing the balloon. Add the key code. Add a test. https://github.com/vim/vim/commit/2f10658b06bbdd8f25c4ff152266c808234cee0a --- src/nvim/keymap.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index b8b9c945b9..4b8b9992f5 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -309,6 +309,7 @@ static const struct key_name_entry { { K_ZERO, "Nul" }, { K_SNR, "SNR" }, { K_PLUG, "Plug" }, + { K_IGNORE, "Ignore" }, { K_COMMAND, "Cmd" }, { 0, NULL } // NOTE: When adding a long name update MAX_KEY_NAME_LEN. -- cgit From 78aa41354ea9b6bb2d6ce750f02f0f8b3d7d4418 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Dec 2019 00:47:32 -0500 Subject: vim-patch:8.1.2377: GUI: when losing focus a pending operator is executed Problem: GUI: when losing focus a pending operator is executed. Solution: Do not execute an operator when getting K_IGNORE. (closes vim/vim#5300) https://github.com/vim/vim/commit/fa5612c7d836eb789e0f8ff4b10461b8640a14b2 --- src/nvim/normal.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 9c5434a0dd..9c707a6fdc 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -874,8 +874,10 @@ static void normal_finish_command(NormalState *s) s->old_mapped_len = typebuf_maplen(); } - // If an operation is pending, handle it... - do_pending_operator(&s->ca, s->old_col, false); + // If an operation is pending, handle it. But not for K_IGNORE. + if (s->ca.cmdchar != K_IGNORE) { + do_pending_operator(&s->ca, s->old_col, false); + } // Wait for a moment when a message is displayed that will be overwritten // by the mode message. -- cgit From 5e1cad6d3378398c059e1a7144e1310b7bcb2398 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Dec 2019 00:56:57 -0500 Subject: vim-patch:8.0.1356: using simalt in a GUIEnter autocommand inserts characters Problem: Using simalt in a GUIEnter autocommand inserts strange characters. (Chih-Long Chang) Solution: Ignore K_NOP in Insert mode. (closes vim/vim#2379) https://github.com/vim/vim/commit/c5aa55db7e5bc791f99fb15b0f4be0d5dd166f62 --- src/nvim/edit.c | 2 +- src/nvim/ex_getln.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index eecea03a19..cb5c7023d7 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -638,7 +638,7 @@ static int insert_check(VimState *state) static int insert_execute(VimState *state, int key) { - if (key == K_IGNORE) { + if (key == K_IGNORE || key == K_NOP) { return -1; // get another key } InsertState *s = (InsertState *)state; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index e5f5e5ad87..c09a3c08ef 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -536,7 +536,7 @@ static int command_line_check(VimState *state) static int command_line_execute(VimState *state, int key) { - if (key == K_IGNORE) { + if (key == K_IGNORE || key == K_NOP) { return -1; // get another key } -- cgit From 27b678f577eb422ccc67e803727c3b26756787f3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 28 Dec 2019 02:15:06 -0800 Subject: gen_vimdoc.py: fix deprecated check --- src/nvim/api/vim.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 661946e8d2..a649eab97c 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -457,6 +457,7 @@ Object nvim_eval(String expr, Error *err) } /// @deprecated Use nvim_exec_lua() instead. +/// @see nvim_exec_lua Object nvim_execute_lua(String code, Array args, Error *err) FUNC_API_SINCE(3) FUNC_API_DEPRECATED_SINCE(7) -- cgit From 6e6544d645f9a9c151a2e027e23d8f79ecd8f7c9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Dec 2019 16:24:21 -0500 Subject: vim-patch:8.1.1739: deleted match highlighting not updated in other window Problem: Deleted match highlighting not updated in other window. Solution: Mark the window for refresh. (closes vim/vim#4720) Also fix that ambi-width check clears with wrong attributes. https://github.com/vim/vim/commit/06029a857a3d4d90b3162090506c1e00dc84c60b --- src/nvim/testdir/test_match.vim | 24 ++++++++++++++++++++++++ src/nvim/window.c | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 90dfcf952d..921c3e3c55 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -1,6 +1,8 @@ " Test for :match, :2match, :3match, clearmatches(), getmatches(), matchadd(), " matchaddpos(), matcharg(), matchdelete(), and setmatches(). +source screendump.vim + function Test_match() highlight MyGroup1 term=bold ctermbg=red guibg=red highlight MyGroup2 term=italic ctermbg=green guibg=green @@ -248,4 +250,26 @@ func Test_matchaddpos_using_negative_priority() set hlsearch& endfunc +func Test_matchdelete_other_window() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + + let lines =<< trim END + call setline(1, 'Hello Vim world') + let mid = matchadd('Error', 'world', 1) + let winid = win_getid() + new + END + call writefile(lines, 'XscriptMatchDelete') + let buf = RunVimInTerminal('-S XscriptMatchDelete', #{rows: 12}) + call term_wait(buf) + call term_sendkeys(buf, ":call matchdelete(mid, winid)\") + call VerifyScreenDump(buf, 'Test_matchdelete_1', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchDelete') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index af78c89618..5d4332c75a 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6680,7 +6680,7 @@ int match_delete(win_T *wp, int id, int perr) rtype = VALID; } xfree(cur); - redraw_later(rtype); + redraw_win_later(wp, rtype); return 0; } -- cgit From 0e7baed2195fec8e99df69112e464791c02b2555 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Dec 2019 16:27:26 -0500 Subject: vim-patch:8.1.1741: cleared/added match highlighting not updated in other window Problem: Cleared/added match highlighting not updated in other window. (Andi Massimino) Solution: Mark the right window for refresh. https://github.com/vim/vim/commit/4ef18dcc2e3a6a9aea2dc90bbdb742c3c9231394 --- src/nvim/testdir/test_match.vim | 45 +++++++++++++++++++++++++++++++++-------- src/nvim/window.c | 4 ++-- 2 files changed, 39 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 921c3e3c55..c134cfb1c0 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -250,25 +250,54 @@ func Test_matchaddpos_using_negative_priority() set hlsearch& endfunc -func Test_matchdelete_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif - +func OtherWindowCommon() let lines =<< trim END call setline(1, 'Hello Vim world') let mid = matchadd('Error', 'world', 1) let winid = win_getid() new END - call writefile(lines, 'XscriptMatchDelete') - let buf = RunVimInTerminal('-S XscriptMatchDelete', #{rows: 12}) + call writefile(lines, 'XscriptMatchCommon') + let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 12}) call term_wait(buf) + return buf +endfunc + +func Test_matchdelete_other_window() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchdelete(mid, winid)\") call VerifyScreenDump(buf, 'Test_matchdelete_1', {}) call StopVimInTerminal(buf) - call delete('XscriptMatchDelete') + call delete('XscriptMatchCommon') +endfunc + +func Test_matchclear_other_window() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + let buf = OtherWindowCommon() + call term_sendkeys(buf, ":call clearmatches(winid)\") + call VerifyScreenDump(buf, 'Test_matchclear_1', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchCommon') +endfunc + +func Test_matchadd_other_window() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + let buf = OtherWindowCommon() + call term_sendkeys(buf, ":call matchadd('Search', 'Hello', 1, -1, #{window: winid})\") + call term_sendkeys(buf, ":\") + call VerifyScreenDump(buf, 'Test_matchadd_1', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchCommon') endfunc diff --git a/src/nvim/window.c b/src/nvim/window.c index 5d4332c75a..79d7a8acba 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6622,7 +6622,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, prev->next = m; m->next = cur; - redraw_later(rtype); + redraw_win_later(wp, rtype); return id; fail: @@ -6698,7 +6698,7 @@ void clear_matches(win_T *wp) xfree(wp->w_match_head); wp->w_match_head = m; } - redraw_later(SOME_VALID); + redraw_win_later(wp, SOME_VALID); } /* -- cgit From eeabd3a8c6e904bf3e01017b60336c0063356943 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 30 Dec 2019 16:55:46 -0500 Subject: vim-patch:8.2.0063: wrong size argument to vim_snprintf() Problem: Wrong size argument to vim_snprintf(). (Dominique Pelle) Solution: Reduce the size by the length. (related to vim/vim#5410) https://github.com/vim/vim/commit/08b28b7ad52d5ee3cb5fa5982b647e325a410484 --- src/nvim/ops.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ops.c b/src/nvim/ops.c index b597c5b214..6a621cdaa6 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5654,7 +5654,8 @@ void cursor_pos_info(dict_T *dict) bom_count = bomb_size(); if (dict == NULL && bom_count > 0) { - vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), + const size_t len = STRLEN(IObuff); + vim_snprintf((char *)IObuff + len, IOSIZE - len, _("(+%" PRId64 " for BOM)"), (int64_t)bom_count); } if (dict == NULL) { -- cgit From 93e7c7e3bd30ae141b613e71a6a3a863e6064d91 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 10 Dec 2019 01:24:20 -0800 Subject: doc [ci skip] --- src/nvim/mark.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 93bc497cf0..8b2f342142 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -179,8 +179,8 @@ void setpcmark(void) } if (jop_flags & JOP_STACK) { - // If we're somewhere in the middle of the jumplist discard everything - // after the current index. + // jumpoptions=stack: if we're somewhere in the middle of the jumplist + // discard everything after the current index. if (curwin->w_jumplistidx < curwin->w_jumplistlen - 1) { // Discard the rest of the jumplist by cutting the length down to // contain nothing beyond the current index. @@ -1214,14 +1214,14 @@ void cleanup_jumplist(win_T *wp, bool checktail) break; } } + bool mustfree; - if (i >= wp->w_jumplistlen) { // not duplicate + if (i >= wp->w_jumplistlen) { // not duplicate mustfree = false; - } else if (i > from + 1) { // non-adjacent duplicate - // When the jump options include "stack", duplicates are only removed from - // the jumplist when they are adjacent. + } else if (i > from + 1) { // non-adjacent duplicate + // jumpoptions=stack: remove duplicates only when adjacent. mustfree = !(jop_flags & JOP_STACK); - } else { // adjacent duplicate + } else { // adjacent duplicate mustfree = true; } -- cgit From 0301de758ba90139a4af9edc3993ef438a4d4cd1 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 31 Dec 2019 23:15:42 -0500 Subject: vim-patch:8.1.1346: error for Python exception does not show useful info Problem: Error for Python exception does not show useful info. Solution: Show the last line instead of the first one. (Ben Jackson, closes vim/vim#4381) https://github.com/vim/vim/commit/7f3a28490abb7c495239fc438825e3d1aaafa76d --- src/nvim/testdir/test_python2.vim | 8 ++++++++ src/nvim/testdir/test_python3.vim | 8 ++++++++ src/nvim/testdir/test_pyx2.vim | 8 ++++++++ src/nvim/testdir/test_pyx3.vim | 8 ++++++++ 4 files changed, 32 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim index 8d55b59c31..9628a298b9 100644 --- a/src/nvim/testdir/test_python2.vim +++ b/src/nvim/testdir/test_python2.vim @@ -164,3 +164,11 @@ func Test_Write_To_Current_Buffer_Fixes_Cursor_Str() bwipe! endfunction + +func Test_Catch_Exception_Message() + try + py raise RuntimeError( 'TEST' ) + catch /.*/ + call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception ) + endtry +endfunc diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index cd07b0883f..e64a8fcc47 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -164,3 +164,11 @@ func Test_Write_To_Current_Buffer_Fixes_Cursor_Str() bwipe! endfunction + +func Test_Catch_Exception_Message() + try + py3 raise RuntimeError( 'TEST' ) + catch /.*/ + call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception ) + endtry +endfunc diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim index 50e57c3bfb..10ff3b6e58 100644 --- a/src/nvim/testdir/test_pyx2.vim +++ b/src/nvim/testdir/test_pyx2.vim @@ -72,3 +72,11 @@ func Test_pyxfile() call assert_match(s:py3pattern, split(var)[0]) endif endfunc + +func Test_Catch_Exception_Message() + try + pyx raise RuntimeError( 'TEST' ) + catch /.*/ + call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception ) + endtry +endfunc diff --git a/src/nvim/testdir/test_pyx3.vim b/src/nvim/testdir/test_pyx3.vim index 64546b4688..2044af3abe 100644 --- a/src/nvim/testdir/test_pyx3.vim +++ b/src/nvim/testdir/test_pyx3.vim @@ -72,3 +72,11 @@ func Test_pyxfile() call assert_match(s:py2pattern, split(var)[0]) endif endfunc + +func Test_Catch_Exception_Message() + try + pyx raise RuntimeError( 'TEST' ) + catch /.*/ + call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception ) + endtry +endfunc -- cgit From 0f47870d1b5e893857df007901f5f062939a9eb7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 31 Dec 2019 23:22:58 -0500 Subject: vim-patch:8.2.0068: crash when using Python 3 with "utf32" encoding Problem: Crash when using Python 3 with "utf32" encoding. (Dominique Pelle) Solution: Use "utf-8" whenever enc_utf8 is set. (closes vim/vim#5423) https://github.com/vim/vim/commit/556684ff71e044a642879d759373a7ebc5047fad --- src/nvim/testdir/test_python3.vim | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index e64a8fcc47..5268dc9d1e 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -172,3 +172,10 @@ func Test_Catch_Exception_Message() call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception ) endtry endfunc + +func Test_unicode() + " this crashed Vim once + " set encoding=utf32 + py3 print('hello') + " set encoding=utf8 +endfunc -- cgit From dec165b268803d08425e0f173b2487c76a2c440c Mon Sep 17 00:00:00 2001 From: Husain Alshehhi Date: Wed, 1 Jan 2020 07:59:37 -0600 Subject: PVS/V618: fix emsgf format specifier #11643 --- src/nvim/eval/typval.h | 2 +- src/nvim/spellfile.c | 7 ++++--- src/nvim/undo.c | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 02d241f6f5..008453b87f 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -798,7 +798,7 @@ static inline bool tv_get_float_chk(const typval_T *const tv, *ret_f = (float_T)tv->vval.v_number; return true; } - emsgf(_("E808: Number or Float required")); + emsgf("%s", _("E808: Number or Float required")); return false; } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 8e4f405d99..14abfda9ff 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -625,7 +625,7 @@ spell_load_file ( switch (scms_ret) { case SP_FORMERROR: case SP_TRUNCERROR: { - emsgf(_("E757: This does not look like a spell file")); + emsgf("%s", _("E757: This does not look like a spell file")); goto endFAIL; } case SP_OTHERERROR: { @@ -2654,8 +2654,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } if (compsylmax != 0) { - if (syllable == NULL) - smsg(_("COMPOUNDSYLMAX used without SYLLABLE")); + if (syllable == NULL) { + smsg("%s", _("COMPOUNDSYLMAX used without SYLLABLE")); + } aff_check_number(spin->si_compsylmax, compsylmax, "COMPOUNDSYLMAX"); spin->si_compsylmax = compsylmax; } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 539d42765d..980399a3ef 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1057,7 +1057,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, if (file_name == NULL) { if (p_verbose > 0) { verbose_enter(); - smsg(_("Cannot write undo file in any directory in 'undodir'")); + smsg("%s", _("Cannot write undo file in any directory in 'undodir'")); verbose_leave(); } return; -- cgit From 5f0d5ec9852cf82c9b865ac2d3cf7f6e131adf4a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 1 Jan 2020 10:16:25 -0500 Subject: vim-patch:8.2.0070: crash when using Python 3 with "debug" encoding Problem: Crash when using Python 3 with "debug" encoding. (Dominique Pelle) Solution: Use "euc-jp" whenever enc_dbcs is set. https://github.com/vim/vim/commit/d518f952f0812778758b25139308bcf45df6988c --- src/nvim/testdir/test_python3.vim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 5268dc9d1e..edd24c7be5 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -174,8 +174,13 @@ func Test_Catch_Exception_Message() endfunc func Test_unicode() + throw 'skipped: Nvim only supports "utf-8" for "encoding" option' " this crashed Vim once - " set encoding=utf32 + set encoding=utf32 py3 print('hello') - " set encoding=utf8 + set encoding=debug + py3 print('hello') + set encoding=euc-tw + py3 print('hello') + set encoding=utf8 endfunc -- cgit From abaabd1d03fd723630f6addeadee9928faa4cdde Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 1 Jan 2020 13:03:06 -0500 Subject: vim-patch:8.2.0074: Python 3 unicode test someitmes fails Problem: Python 3 unicode test someitmes fails. Solution: Make 'termencoding' empty. Correct number of error message. https://github.com/vim/vim/commit/4b7cdca23035eacf6cd0e30b90546cf32f7efe9e --- src/nvim/change.c | 2 +- src/nvim/options.lua | 6 ------ src/nvim/testdir/test_python3.vim | 9 ++++++++- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/change.c b/src/nvim/change.c index 8a782c2b20..05cacaf2c2 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -757,7 +757,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } // If "count" is negative the caller must be doing something wrong. if (count < 1) { - IEMSGN("E950: Invalid count for del_bytes(): %ld", count); + IEMSGN("E292: Invalid count for del_bytes(): %ld", count); return FAIL; } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 7d080b8d56..a5a14a1a25 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2488,12 +2488,6 @@ return { varname='p_tbidi', defaults={if_true={vi=false}} }, - { - full_name='termencoding', abbreviation='tenc', - type='string', scope={'global'}, - vi_def=true, - defaults={if_true={vi=""}} - }, { full_name='termguicolors', abbreviation='tgc', type='bool', scope={'global'}, diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index edd24c7be5..9ffca12706 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -174,13 +174,20 @@ func Test_Catch_Exception_Message() endfunc func Test_unicode() - throw 'skipped: Nvim only supports "utf-8" for "encoding" option' + throw 'skipped: Nvim does not support "termencoding" option and only supports "utf-8" for "encoding" option' " this crashed Vim once + let save_tenc = &tenc + set tenc= + set encoding=utf32 py3 print('hello') + set encoding=debug py3 print('hello') + set encoding=euc-tw py3 print('hello') + set encoding=utf8 + let &tenc = save_tenc endfunc -- cgit From a11c3b7920cbfef7e609f34d54093da2195c2a08 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 1 Jan 2020 13:12:43 -0500 Subject: vim-patch:8.2.0075: Python 3 unicode test still sometimes fails Problem: Python 3 unicode test still sometimes fails. Solution: Skip the test when 'termencoding' is not empty. https://github.com/vim/vim/commit/2466aea508f22d44099d54ed6d15c0a1d90f8d6e --- src/nvim/testdir/test_python3.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 9ffca12706..259ba1350a 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -176,8 +176,9 @@ endfunc func Test_unicode() throw 'skipped: Nvim does not support "termencoding" option and only supports "utf-8" for "encoding" option' " this crashed Vim once - let save_tenc = &tenc - set tenc= + if &tenc != '' + throw "Skipped: 'termencoding' is not empty" + endif set encoding=utf32 py3 print('hello') @@ -189,5 +190,4 @@ func Test_unicode() py3 print('hello') set encoding=utf8 - let &tenc = save_tenc endfunc -- cgit From 7dca8383db4d595a8e9de929aff1263c0b0b8db5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 1 Jan 2020 13:13:40 -0500 Subject: vim-patch:8.2.0076: Python 3 unicode test fails on MS-Windows Problem: Python 3 unicode test fails on MS-Windows. Solution: Do not set 'encoding' to "debug" on MS-Windows. https://github.com/vim/vim/commit/955f4e6f36ea009b90803d12a62108c94f446778 --- src/nvim/testdir/test_python3.vim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 259ba1350a..948ef0a07f 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -183,8 +183,10 @@ func Test_unicode() set encoding=utf32 py3 print('hello') - set encoding=debug - py3 print('hello') + if !has('win32') + set encoding=debug + py3 print('hello') + endif set encoding=euc-tw py3 print('hello') -- cgit From ea4127e9a7a624484f51c21e17f37c766da15da0 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 27 Nov 2019 20:45:41 +0100 Subject: lua: metatable for empty dict value --- src/nvim/lua/converter.c | 13 +++++++++++++ src/nvim/lua/executor.c | 13 +++++++++++++ src/nvim/lua/executor.h | 1 + src/nvim/lua/vim.lua | 4 ++++ 4 files changed, 31 insertions(+) (limited to 'src') diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 09d1a68898..fca74b5901 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -156,6 +156,13 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) && other_keys_num == 0 && ret.string_keys_num == 0)) { ret.type = kObjectTypeArray; + if (tsize == 0 && lua_getmetatable(lstate, -1)) { + nlua_pushref(lstate, nlua_empty_dict_ref); + if (lua_rawequal(lstate, -2, -1)) { + ret.type = kObjectTypeDictionary; + } + lua_pop(lstate, 2); + } } else if (ret.string_keys_num == tsize) { ret.type = kObjectTypeDictionary; } else { @@ -465,6 +472,8 @@ static bool typval_conv_special = false; nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ } else { \ lua_createtable(lstate, 0, 0); \ + nlua_pushref(lstate, nlua_empty_dict_ref); \ + lua_setmetatable(lstate, -2); \ } \ } while (0) @@ -695,6 +704,10 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); } else { lua_createtable(lstate, 0, (int)dict.size); + if (dict.size == 0 && !special) { + nlua_pushref(lstate, nlua_empty_dict_ref); + lua_setmetatable(lstate, -2); + } } for (size_t i = 0; i < dict.size; i++) { nlua_push_String(lstate, dict.items[i].key, special); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 2cd6c0db66..242d4e18d1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -324,6 +324,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL nlua_nil_ref = nlua_ref(lstate, -1); lua_setfield(lstate, -2, "NIL"); + // vim._empty_dict_mt + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_empty_dict_tostring); + lua_setfield(lstate, -2, "__tostring"); + nlua_empty_dict_ref = nlua_ref(lstate, -1); + lua_setfield(lstate, -2, "_empty_dict_mt"); + // internal vim._treesitter... API nlua_add_treesitter(lstate); @@ -665,6 +672,12 @@ static int nlua_nil_tostring(lua_State *lstate) return 1; } +static int nlua_empty_dict_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "vim.empty_dict()"); + return 1; +} + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 32f66b629c..3259fc0fa1 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -13,6 +13,7 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); +EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); #define set_api_error(s, err) \ do { \ diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index e7c5458102..8ba550ea31 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -243,6 +243,10 @@ function vim.schedule_wrap(cb) end) end +function vim.empty_dict() + return setmetatable({}, vim._empty_dict_mt) +end + -- vim.fn.{func}(...) vim.fn = setmetatable({}, { __index = function(t, key) -- cgit From dc6077535fc38885471f1983ea8059d6a95ae4c9 Mon Sep 17 00:00:00 2001 From: Ghjuvan Lacambre Date: Thu, 2 Jan 2020 11:00:39 +0100 Subject: API: fix crash on copy_object(kObjectTypeWindow) #11651 Closes #11646 --- src/nvim/api/private/helpers.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index b8d62e42a1..cdd1f42ae1 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1365,6 +1365,9 @@ Dictionary copy_dictionary(Dictionary dict) Object copy_object(Object obj) { switch (obj.type) { + case kObjectTypeBuffer: + case kObjectTypeTabpage: + case kObjectTypeWindow: case kObjectTypeNil: case kObjectTypeBoolean: case kObjectTypeInteger: -- cgit From cbc8d72fde4b19176028490934ff7a447afe523c Mon Sep 17 00:00:00 2001 From: butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Thu, 2 Jan 2020 06:06:11 -0800 Subject: tabpage: track last-used tabpage #11626 In a multi-window scenario, it is possible to return focus to the last accessed window via n_CTRL-W_p. However, in the case of a multi-tab scenario, there was previously no way to return focus to the last accessed *tab*. Here, that ability is added via n_g. Additionally, the index of the previous tab is exposed via tabpagenr('#'), mirroring the existing functionality of winnr('#'). --- src/nvim/eval.c | 4 ++++ src/nvim/globals.h | 1 + src/nvim/normal.c | 20 ++++++++++++++------ src/nvim/window.c | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 65f029649c..cf4d3af232 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18172,6 +18172,10 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (arg != NULL) { if (strcmp(arg, "$") == 0) { nr = tabpage_index(NULL) - 1; + } else if (strcmp(arg, "#") == 0) { + nr = valid_tabpage(lastused_tabpage) + ? tabpage_index(lastused_tabpage) + : nr; } else { EMSG2(_(e_invexpr2), arg); } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index c6ab574a0f..148ab0a02b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -459,6 +459,7 @@ EXTERN frame_T *topframe; /* top of the window frame tree */ * one in the list, "curtab" is the current one. */ EXTERN tabpage_T *first_tabpage; +EXTERN tabpage_T *lastused_tabpage; EXTERN tabpage_T *curtab; EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */ diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 9c707a6fdc..d0b9fd4589 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6312,9 +6312,7 @@ static void nv_gomark(cmdarg_T *cap) } } -/* - * Handle CTRL-O, CTRL-I, "g;" and "g," commands. - */ +// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. static void nv_pcmark(cmdarg_T *cap) { pos_T *pos; @@ -6322,11 +6320,16 @@ static void nv_pcmark(cmdarg_T *cap) const bool old_KeyTyped = KeyTyped; // getting file may reset it if (!checkclearopq(cap->oap)) { - if (cap->cmdchar == 'g') + if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { + goto_tabpage_lastused(); + return; + } + if (cap->cmdchar == 'g') { pos = movechangelist((int)cap->count1); - else + } else { pos = movemark((int)cap->count1); - if (pos == (pos_T *)-1) { /* jump to other file */ + } + if (pos == (pos_T *)-1) { // jump to other file curwin->w_set_curswant = true; check_cursor(); } else if (pos != NULL) /* can jump */ @@ -7059,6 +7062,11 @@ static void nv_g_cmd(cmdarg_T *cap) if (!checkclearop(oap)) goto_tabpage(-(int)cap->count1); break; + case TAB: + if (!checkclearop(oap)) { + goto_tabpage_lastused(); + } + break; case '+': case '-': /* "g+" and "g-": undo or redo along the timeline */ diff --git a/src/nvim/window.c b/src/nvim/window.c index 79d7a8acba..4d105dd11e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -519,6 +519,10 @@ wingotofile: do_nv_ident('g', xchar); break; + case TAB: + goto_tabpage_lastused(); + break; + case 'f': /* CTRL-W gf: "gf" in a new tab page */ case 'F': /* CTRL-W gF: "gF" in a new tab page */ cmdmod.tab = tabpage_index(curtab) + 1; @@ -3691,6 +3695,10 @@ void free_tabpage(tabpage_T *tp) hash_init(&tp->tp_vars->dv_hashtab); unref_var_dict(tp->tp_vars); + if (tp == lastused_tabpage) { + lastused_tabpage = NULL; + } + xfree(tp->tp_localdir); xfree(tp); } @@ -3750,6 +3758,8 @@ int win_new_tabpage(int after, char_u *filename) tabpage_check_windows(tp); + lastused_tabpage = tp; + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); @@ -3976,6 +3986,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au if (curtab->tp_old_Columns != Columns && starting == 0) shell_new_columns(); /* update window widths */ + lastused_tabpage = old_curtab; /* Apply autocommands after updating the display, when 'rows' and * 'columns' have been set correctly. */ @@ -4095,6 +4106,14 @@ void goto_tabpage_tp(tabpage_T *tp, int trigger_enter_autocmds, int trigger_leav } } +// Go to the last accessed tab page, if there is one. +void goto_tabpage_lastused(void) +{ + if (valid_tabpage(lastused_tabpage)) { + goto_tabpage_tp(lastused_tabpage, true, true); + } +} + /* * Enter window "wp" in tab page "tp". * Also updates the GUI tab. -- cgit From 77b61cb0dff55318fdec9e28ccbaedd051996c5a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 00:42:52 -0500 Subject: option: restore termencoding (readonly) #11662 'termencoding' option was removed in abaabd1d03fd723630f6addeadee9928faa4cdde but some plugins check its value. --- src/nvim/option.c | 7 +++++++ src/nvim/testdir/test_python3.vim | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index de25ee3218..a8a4ad6484 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4737,6 +4737,13 @@ bool get_tty_option(char *name, char **value) return true; } + if (strequal(name, "tenc") || strequal(name, "termencoding")) { + if (value) { + *value = xstrdup("utf-8"); + } + return true; + } + if (strequal(name, "ttytype")) { if (value) { *value = p_ttytype ? xstrdup(p_ttytype) : xstrdup("nvim"); diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 948ef0a07f..d0f80b8958 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -174,7 +174,6 @@ func Test_Catch_Exception_Message() endfunc func Test_unicode() - throw 'skipped: Nvim does not support "termencoding" option and only supports "utf-8" for "encoding" option' " this crashed Vim once if &tenc != '' throw "Skipped: 'termencoding' is not empty" -- cgit From 94cc8a20b42bfa6ef5a344361cd7311cb6036b56 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 07:13:45 -0500 Subject: vim-patch:8.2.0079: test still fails on MS-Windows #11663 Problem: Python 3 unicode test still fails on MS-Windows. Solution: Do not set 'encoding' to "euc-tw" on MS-Windows. https://github.com/vim/vim/commit/7fc4785ea19306b7e94beb61f226cf40c32b1aba --- src/nvim/testdir/test_python3.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index d0f80b8958..108f78f976 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -185,10 +185,10 @@ func Test_unicode() if !has('win32') set encoding=debug py3 print('hello') - endif - set encoding=euc-tw - py3 print('hello') + set encoding=euc-tw + py3 print('hello') + endif set encoding=utf8 endfunc -- cgit From 42aa8764881ed8572d563766575c053db085f6db Mon Sep 17 00:00:00 2001 From: We're Yet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Wed, 1 Jan 2020 09:52:13 -0800 Subject: vim-patch:8.1.0972: cannot switch from terminal window to next tabpage Problem: Cannot switch from terminal window to next tabpage. Solution: Make CTRL-W gt move to next tabpage. https://github.com/vim/vim/commit/72e83c1ae535e2ebc35b114d34d0a811eb62b068 --- src/nvim/window.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 4d105dd11e..d62510b951 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -528,6 +528,9 @@ wingotofile: cmdmod.tab = tabpage_index(curtab) + 1; nchar = xchar; goto wingotofile; + case 't': // CTRL-W gt: go to next tab page + goto_tabpage((int)Prenum); + break; case 'e': if (curwin->w_floating || !ui_has(kUIMultigrid)) { -- cgit From a7b6b375196ad2d0531550b2e1c7888502c9512b Mon Sep 17 00:00:00 2001 From: We're Yet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Wed, 1 Jan 2020 10:08:06 -0800 Subject: vim-patch:8.1.0974: cannot switch from terminal window to previous tabpage Problem: Cannot switch from terminal window to previous tabpage. Solution: Make CTRL-W gT move to previous tabpage. https://github.com/vim/vim/commit/882d02eeb571a13a502fe82a04c9eaffa630c294 --- src/nvim/window.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index d62510b951..8feedf2de6 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -85,10 +85,7 @@ do_window( size_t len; char cbuf[40]; - if (Prenum == 0) - Prenum1 = 1; - else - Prenum1 = Prenum; + Prenum1 = Prenum == 0 ? 1 : Prenum; # define CHECK_CMDWIN \ do { \ @@ -532,6 +529,10 @@ wingotofile: goto_tabpage((int)Prenum); break; + case 'T': // CTRL-W gT: go to previous tab page + goto_tabpage(-(int)Prenum1); + break; + case 'e': if (curwin->w_floating || !ui_has(kUIMultigrid)) { beep_flush(); -- cgit From 4c4bcecc4a1b817be742856ac8600c90d24c42f4 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 20:08:13 -0500 Subject: vim-patch:8.0.1817: a timer may change v:count unexpectedly Problem: A timer may change v:count unexpectedly. Solution: Save and restore v:count and similar variables when a timer callback is invoked. (closes vim/vim#2897) https://github.com/vim/vim/commit/b0f42ba60d9e6d101d103421ba0c351811615c15 --- src/nvim/testdir/test_timers.vim | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index bd63d94729..cadec88af9 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -5,6 +5,7 @@ if !has('timers') endif source shared.vim +source screendump.vim source load.vim func MyHandler(timer) @@ -263,4 +264,35 @@ func Test_ex_mode() call timer_stop(timer) endfunc +func Test_restore_count() + if !CanRunVimInTerminal() + return + endif + " Check that v:count is saved and restored, not changed by a timer. + call writefile([ + \ 'nnoremap L v:count ? v:count . "l" : "l"', + \ 'func Doit(id)', + \ ' normal 3j', + \ 'endfunc', + \ 'call timer_start(100, "Doit")', + \ ], 'Xtrcscript') + call writefile([ + \ '1-1234', + \ '2-1234', + \ '3-1234', + \ ], 'Xtrctext') + let buf = RunVimInTerminal('-S Xtrcscript Xtrctext', {}) + + " Wait for the timer to move the cursor to the third line. + call WaitForAssert({-> assert_equal(3, term_getcursor(buf)[0])}) + call assert_equal(1, term_getcursor(buf)[1]) + " Now check that v:count has not been set to 3 + call term_sendkeys(buf, 'L') + call WaitForAssert({-> assert_equal(2, term_getcursor(buf)[1])}) + + call StopVimInTerminal(buf) + call delete('Xtrcscript') + call delete('Xtrctext') +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From a2554858df533fcfa0ba6074648e21cda50934d9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 22:43:06 -0500 Subject: vim-patch:8.1.0840: getchar(0) never returns a character in the terminal Problem: getchar(0) never returns a character in the terminal. Solution: Call wait_func() at least once. https://github.com/vim/vim/commit/12dfc9eef14fe74c46145aa9e6cba9666f1bcd40 --- src/nvim/testdir/test_timers.vim | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index cadec88af9..161a89c38c 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -253,6 +253,16 @@ func Test_peek_and_get_char() call timer_stop(intr) endfunc +func Test_getchar_zero() + call timer_start(20, {id -> feedkeys('x', 'L')}) + let c = 0 + while c == 0 + let c = getchar(0) + sleep 10m + endwhile + call assert_equal('x', nr2char(c)) +endfunc + func Test_ex_mode() " Function with an empty line. func Foo(...) -- cgit From 25613fa65bf1c3bbe2b675ff273acb94f47656bc Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 22:47:38 -0500 Subject: vim-patch:8.1.0842: getchar_zero test fails on MS-Windows Problem: getchar_zero test fails on MS-Windows. Solution: Disable the test for now. https://github.com/vim/vim/commit/cb908a813cebf7fb4608ff43fc3d258cf2768809 --- src/nvim/testdir/test_timers.vim | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 161a89c38c..e4d9329ac6 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -254,13 +254,20 @@ func Test_peek_and_get_char() endfunc func Test_getchar_zero() - call timer_start(20, {id -> feedkeys('x', 'L')}) + if has('win32') + " Console: no low-level input + " GUI: somehow doesn't work + return + endif + + let id = timer_start(20, {id -> feedkeys('x', 'L')}) let c = 0 while c == 0 let c = getchar(0) sleep 10m endwhile call assert_equal('x', nr2char(c)) + call timer_stop(id) endfunc func Test_ex_mode() @@ -268,7 +275,7 @@ func Test_ex_mode() func Foo(...) endfunc - let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) + let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. exe "normal Qsleep 100m\rvi\r" call timer_stop(timer) -- cgit From d139fb5cd08a9bdee30e0735e9e795585036ce7b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 22:48:53 -0500 Subject: vim-patch:8.1.0844: when timer fails test will hang forever Problem: When timer fails test will hang forever. Solution: Use reltime() to limit waiting time. (Ozaki Kiichi, closes vim/vim#3878) https://github.com/vim/vim/commit/50948e4ac24314d5a70404bbc592ffc28755ad9f --- src/nvim/testdir/test_timers.vim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index e4d9329ac6..6c336e6fa6 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -260,9 +260,11 @@ func Test_getchar_zero() return endif + " Measure the elapsed time to avoid a hang when it fails. + let start = reltime() let id = timer_start(20, {id -> feedkeys('x', 'L')}) let c = 0 - while c == 0 + while c == 0 && reltimefloat(reltime(start)) < 0.2 let c = getchar(0) sleep 10m endwhile -- cgit From e7d49dc909623c0618b3e5cfaecb09fb0b38e1fa Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 5 Jan 2020 08:17:55 -0500 Subject: vim-patch:8.0.1786: no test for 'termwinkey' Problem: No test for 'termwinkey'. Solution: Add a test. Make feedkeys() handle terminal_loop() returning before characters are consumed. https://github.com/vim/vim/commit/b2ac14c0b5e23f8ab97c5c784bcd83e13ba8ded3 --- src/nvim/getchar.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index f582670141..b82839103f 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1562,7 +1562,7 @@ int vpeekc(void) { if (old_char != -1) return old_char; - return vgetorpeek(FALSE); + return vgetorpeek(false); } /* @@ -1615,20 +1615,20 @@ vungetc ( /* unget one character (can only be done once!) */ /// Also stores the result of mappings. /// Also used for the ":normal" command. /// 3. from the user -/// This may do a blocking wait if "advance" is TRUE. +/// This may do a blocking wait if "advance" is true. /// -/// if "advance" is TRUE (vgetc()): +/// if "advance" is true (vgetc()): /// Really get the character. /// KeyTyped is set to TRUE in the case the user typed the key. /// KeyStuffed is TRUE if the character comes from the stuff buffer. -/// if "advance" is FALSE (vpeekc()): +/// if "advance" is false (vpeekc()): /// Just look whether there is a character available. /// Return NUL if not. /// /// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL and CSI may be escaped, need to get two more bytes then. -static int vgetorpeek(int advance) +static int vgetorpeek(bool advance) { int c, c1; int keylen; @@ -1721,7 +1721,7 @@ static int vgetorpeek(int advance) // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); // If inchar() returns TRUE (script file was active) or we - // are inside a mapping, get out of insert mode. + // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. // As a result typing CTRL-C in insert mode will // really insert a CTRL-C. @@ -2324,7 +2324,7 @@ static int vgetorpeek(int advance) } /* for (;;) */ } /* if (!character from stuffbuf) */ - /* if advance is FALSE don't loop on NULs */ + // if advance is false don't loop on NULs } while (c < 0 || (advance && c == NUL)); /* -- cgit From 3a3fb0860248b42e05de6591d4b7727453efdef2 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 3 Jan 2020 22:58:09 -0500 Subject: vim-patch:8.1.0851: feedkeys() with "L" does not work properly Problem: feedkeys() with "L" does not work properly. Solution: Do not set typebuf_was_filled when using "L". (Ozaki Kiichi, closes vim/vim#3885) https://github.com/vim/vim/commit/8d4ce56a19ed14d13332f94ad592fff2d9a715d5 --- src/nvim/api/vim.c | 8 ++++---- src/nvim/testdir/test_autocmd.vim | 2 +- src/nvim/testdir/test_mapping.vim | 2 +- src/nvim/testdir/test_timers.vim | 5 ++--- 4 files changed, 8 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a649eab97c..d35d04cdb3 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -241,15 +241,15 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) keys_esc = keys.data; } ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE), - insert ? 0 : typebuf.tb_len, !typed, false); + insert ? 0 : typebuf.tb_len, !typed, false); + if (vgetc_busy) { + typebuf_was_filled = true; + } if (escape_csi) { xfree(keys_esc); } - if (vgetc_busy) { - typebuf_was_filled = true; - } if (execute) { int save_msg_scroll = msg_scroll; diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 275b053bc9..e3547aea5b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -52,7 +52,7 @@ if has('timers') au CursorHoldI * let g:triggered += 1 set updatetime=500 call job_start(has('win32') ? 'cmd /c echo:' : 'echo', - \ {'exit_cb': {j, s -> timer_start(1000, 'ExitInsertMode')}}) + \ {'exit_cb': {-> timer_start(1000, 'ExitInsertMode')}}) call feedkeys('a', 'x!') call assert_equal(1, g:triggered) unlet g:triggered diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index 34e62e80e8..f14f292a92 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -287,7 +287,7 @@ func Test_map_timeout_with_timer_interrupt() set timeout timeoutlen=200 func ExitCb(job, status) - let g:timer = timer_start(1, {_ -> feedkeys("3\", 't')}) + let g:timer = timer_start(1, {-> feedkeys("3\", 't')}) endfunc call job_start([&shell, &shellcmdflag, 'echo'], {'exit_cb': 'ExitCb'}) diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 6c336e6fa6..c4d74222a4 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -254,15 +254,14 @@ func Test_peek_and_get_char() endfunc func Test_getchar_zero() - if has('win32') + if has('win32') && !has('gui_running') " Console: no low-level input - " GUI: somehow doesn't work return endif " Measure the elapsed time to avoid a hang when it fails. let start = reltime() - let id = timer_start(20, {id -> feedkeys('x', 'L')}) + let id = timer_start(20, {-> feedkeys('x', 'L')}) let c = 0 while c == 0 && reltimefloat(reltime(start)) < 0.2 let c = getchar(0) -- cgit From 1aacab49ea0e5cff94bd89595737b6af677f4490 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 5 Jan 2020 10:40:02 -0500 Subject: vim-patch:8.1.1579: dict and list could be GC'ed while displaying error Problem: Dict and list could be GC'ed while displaying error in a timer. (Yasuhiro Matsumoto) Solution: Block garbage collection when executing a timer. Add test_garbagecollect_soon(). Add "no_wait_return" to test_override(). (closes vim/vim#4571) https://github.com/vim/vim/commit/adc6714aac20f5462a0ecec50ab4806b2f3ab0db --- src/nvim/eval.c | 2 +- src/nvim/eval/typval.c | 2 +- src/nvim/getchar.c | 2 +- src/nvim/globals.h | 2 +- src/nvim/testdir/test_timers.vim | 26 ++++++++++++++++++++++++++ 5 files changed, 30 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cf4d3af232..5c19ce4047 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5920,7 +5920,7 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) if (**arg != '}') { EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg); failret: - if (evaluate) { + if (d != NULL) { tv_dict_free(d); } return FAIL; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 728e3a7fa3..da97eccc65 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1306,7 +1306,7 @@ void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) dict_T *tv_dict_alloc(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - dict_T *const d = xmalloc(sizeof(dict_T)); + dict_T *const d = xcalloc(1, sizeof(dict_T)); // Add the dict to the list of dicts for garbage collection. if (gc_first_dict != NULL) { diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index b82839103f..81666bf5d6 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1518,7 +1518,7 @@ int vgetc(void) * collection in the first next vgetc(). It's disabled after that to * avoid internally used Lists and Dicts to be freed. */ - may_garbage_collect = FALSE; + may_garbage_collect = false; return c; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 148ab0a02b..4741778c14 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -325,7 +325,7 @@ EXTERN except_T *caught_stack INIT(= NULL); /// we do garbage collection before waiting for a char at the toplevel. /// "garbage_collect_at_exit" indicates garbagecollect(1) was called. /// -EXTERN int may_garbage_collect INIT(= false); +EXTERN bool may_garbage_collect INIT(= false); EXTERN int want_garbage_collect INIT(= false); EXTERN int garbage_collect_at_exit INIT(= false); diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index c4d74222a4..3043103270 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -313,4 +313,30 @@ func Test_restore_count() call delete('Xtrctext') endfunc +" Test that the garbage collector isn't triggered if a timer callback invokes +" vgetc(). +func Test_nocatch_garbage_collect() + " skipped: Nvim does not support test_garbagecollect_soon(), test_override() + return + " 'uptimetime. must be bigger than the timer timeout + set ut=200 + call test_garbagecollect_soon() + call test_override('no_wait_return', 0) + func CauseAnError(id) + " This will show an error and wait for Enter. + let a = {'foo', 'bar'} + endfunc + func FeedChar(id) + call feedkeys('x', 't') + endfunc + call timer_start(300, 'FeedChar') + call timer_start(100, 'CauseAnError') + let x = getchar() + + set ut& + call test_override('no_wait_return', 1) + delfunc CauseAnError + delfunc FeedChar +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From 3c764aabb5d4be71a5ea0d50bf8dae0a7285677f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 5 Jan 2020 22:51:49 -0500 Subject: vim-patch:8.1.1308: the Normal highlight is not defined when compiled with GUI Problem: The Normal highlight is not defined when compiled with GUI. Solution: Always define Normal. (Christian Brabandt, closes vim/vim#4072) https://github.com/vim/vim/commit/f90b6e03a983b62b66564fc449e32724d6456769 --- src/nvim/syntax.c | 3 +-- src/nvim/testdir/test_highlight.vim | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 61ee225eba..bcf133afda 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5959,6 +5959,7 @@ static const char *highlight_init_both[] = { "IncSearch cterm=reverse gui=reverse", "ModeMsg cterm=bold gui=bold", "NonText ctermfg=Blue gui=bold guifg=Blue", + "Normal cterm=NONE gui=NONE", "PmenuSbar ctermbg=Grey guibg=Grey", "StatusLine cterm=reverse,bold gui=reverse,bold", "StatusLineNC cterm=reverse gui=reverse", @@ -6010,7 +6011,6 @@ static const char *highlight_init_light[] = { "Title ctermfg=DarkMagenta gui=bold guifg=Magenta", "Visual guibg=LightGrey", "WarningMsg ctermfg=DarkRed guifg=Red", - "Normal gui=NONE", NULL }; @@ -6044,7 +6044,6 @@ static const char *highlight_init_dark[] = { "Title ctermfg=LightMagenta gui=bold guifg=Magenta", "Visual guibg=DarkGrey", "WarningMsg ctermfg=LightRed guifg=Red", - "Normal gui=NONE", NULL }; diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index d94eb7c3a2..b9aeff0907 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -591,3 +591,10 @@ func Test_cursorline_with_visualmode() call StopVimInTerminal(buf) call delete('Xtest_cursorline_with_visualmode') endfunc + +func Test_1_highlight_Normalgroup_exists() + " This test must come before the Test_cursorline test, as it appears this + " defines the Normal highlighting group anyway. + let hlNormal = HighlightArgs('Normal') + call assert_match('hi Normal\s*clear', hlNormal) +endfunc -- cgit From 83083dd47b0c2880e4e1401f1c751013c533d57f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 5 Jan 2020 23:03:51 -0500 Subject: vim-patch:8.1.1309: test for Normal highlight fails on MS-Windows GUI Problem: Test for Normal highlight fails on MS-Windows GUI. Solution: Skip the test for MS-Windows GUI. https://github.com/vim/vim/commit/6b528fa062a5ac6bb5d8bd3abc26f32c65691d00 --- src/nvim/testdir/test_highlight.vim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index b9aeff0907..a320e8edc8 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -592,9 +592,12 @@ func Test_cursorline_with_visualmode() call delete('Xtest_cursorline_with_visualmode') endfunc +" This test must come before the Test_cursorline test, as it appears this +" defines the Normal highlighting group anyway. func Test_1_highlight_Normalgroup_exists() - " This test must come before the Test_cursorline test, as it appears this - " defines the Normal highlighting group anyway. - let hlNormal = HighlightArgs('Normal') - call assert_match('hi Normal\s*clear', hlNormal) + " MS-Windows GUI sets the font + if !has('win32') || !has('gui_running') + let hlNormal = HighlightArgs('Normal') + call assert_match('hi Normal\s*clear', hlNormal) + endif endfunc -- cgit From 013436c9973dfeb76f073a597256bb9e9288d262 Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Tue, 7 Jan 2020 08:03:15 +0200 Subject: ui_grid_resize: fix resize logic for floating window #11655 --- src/nvim/ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 79fa8b8223..0f841760d6 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -505,7 +505,7 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) } if (wp->w_floating) { - if (width != wp->w_width && height != wp->w_height) { + if (width != wp->w_width || height != wp->w_height) { wp->w_float_config.width = width; wp->w_float_config.height = height; win_config_float(wp, wp->w_float_config); -- cgit From 3d6cca5a9d7cc9c13af631d2c6d652096203ce13 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 7 Jan 2020 23:20:35 -0500 Subject: vim-patch:8.2.0099: use of NULL pointer when out of memory Problem: Use of NULL pointer when out of memory. Solution: Check for NULL pointer. (Dominique Pelle, closes vim/vim#5449) https://github.com/vim/vim/commit/8b7aa2f9b238df916c161cdacda032c25d72a0ae --- src/nvim/ex_getln.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c09a3c08ef..b2dc90f3fb 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4715,8 +4715,7 @@ ExpandFromContext ( int free_pat = FALSE; int i; - /* for ":set path=" and ":set tags=" halve backslashes for escaped - * space */ + // for ":set path=" and ":set tags=" halve backslashes for escaped space if (xp->xp_backslash != XP_BS_NONE) { free_pat = TRUE; pat = vim_strsave(pat); @@ -4984,8 +4983,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, int ret; bool did_curdir = false; - /* for ":set path=" and ":set tags=" halve backslashes for escaped - * space */ + // for ":set path=" and ":set tags=" halve backslashes for escaped space pat = vim_strsave(filepat); for (i = 0; pat[i]; ++i) if (pat[i] == '\\' && pat[i + 1] == ' ') -- cgit From 45759e44f9cff9deef1538933fad3e42f94d5930 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 8 Jan 2020 01:08:55 -0500 Subject: Remove (void) hacks, Mark unused attrs --- src/nvim/ex_docmd.c | 4 +--- src/nvim/ex_getln.c | 2 +- src/nvim/normal.c | 5 +---- src/nvim/tui/tui.c | 3 +-- 4 files changed, 4 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index afe2660cdf..30e7ecd434 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -10397,8 +10397,6 @@ bool cmd_can_preview(char_u *cmd) Dictionary commands_array(buf_T *buf) { Dictionary rv = ARRAY_DICT_INIT; - Object obj = NIL; - (void)obj; // Avoid "dead assignment" warning. char str[20]; garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds; @@ -10429,7 +10427,7 @@ Dictionary commands_array(buf_T *buf) PUT(d, "complete_arg", cmd->uc_compl_arg == NULL ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg))); - obj = NIL; + Object obj = NIL; if (cmd->uc_argt & COUNT) { if (cmd->uc_def >= 0) { snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index b2dc90f3fb..2578d1837a 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2007,7 +2007,7 @@ getcmdline ( int firstc, long count, // only used for incremental search int indent, // indent for inside conditionals - bool do_concat // unused + bool do_concat FUNC_ATTR_UNUSED ) { // Be prepared for situations where cmdline can be invoked recursively. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d0b9fd4589..3aff3cef84 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3760,7 +3760,6 @@ find_decl ( bool retval = true; bool incll; int searchflags = flags_arg; - bool valid; pat = xmalloc(len + 7); @@ -3795,8 +3794,6 @@ find_decl ( /* Search forward for the identifier, ignore comment lines. */ clearpos(&found_pos); for (;; ) { - valid = false; - (void)valid; // Avoid "dead assignment" warning. t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD, pat, 1L, searchflags, RE_LAST, NULL); if (curwin->w_cursor.lnum >= old_pos.lnum) { @@ -3831,7 +3828,7 @@ find_decl ( curwin->w_cursor.col = 0; continue; } - valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col); + bool valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col); // If the current position is not a valid identifier and a previous match is // present, favor that one instead. diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e168cf079a..e9276db484 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1073,9 +1073,8 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow, Integer startcol, Integer endcol, - Integer rows, Integer cols) + Integer rows, Integer cols FUNC_ATTR_UNUSED) { - (void)cols; // unused TUIData *data = ui->data; UGrid *grid = &data->grid; int top = (int)startrow, bot = (int)endrow-1; -- cgit From a91ea028303ea28a4667072d5db1ae10e8af80c9 Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Wed, 8 Jan 2020 07:38:10 +0100 Subject: version.c: update [ci skip] #11636 vim-patch:8.0.0902: cannot specify directory or environment for a job vim-patch:8.0.1127: Test_peek_and_get_char fails on 32 bit system vim-patch:8.1.0661: clipboard regexp might be used recursively vim-patch:8.1.0834: GUI may wait too long before dealing with messages vim-patch:8.2.0040: timers test is still flaky on Travis for Mac vim-patch:8.2.0053: windowsversion() does not always return the right value vim-patch:8.2.0055: cannot use ":gui" in vimrc with VIMDLL enabled vim-patch:8.2.0057: cannot build with small features vim-patch:8.2.0059: compiler warnings for unused variables in small build vim-patch:8.2.0060: message test only runs with one encoding vim-patch:8.2.0065: Amiga and alikes: autoopen only used on Amiga OS4 vim-patch:8.2.0080: globals using INIT4() are not in the tags file vim-patch:8.2.0081: MS-Windows also need the change to support INIT4() vim-patch:8.2.0086: build error for small version vim-patch:8.2.0094: MS-Windows: cannot build with Strawberry Perl 5.30 vim-patch:8.2.0100: macros for Ruby are too complicated --- src/nvim/version.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 4360b3776b..8bfd2f5021 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -101,7 +101,7 @@ static const int included_patches[] = { 1820, 1819, 1818, - // 1817, + 1817, 1816, 1815, 1814, @@ -132,7 +132,7 @@ static const int included_patches[] = { // 1789, 1788, 1787, - // 1786, + 1786, 1785, 1784, // 1783, @@ -378,9 +378,9 @@ static const int included_patches[] = { // 1543, 1542, 1541, - // 1540, + 1540, 1539, - // 1538, + 1538, 1537, 1536, 1535, @@ -396,7 +396,7 @@ static const int included_patches[] = { // 1525, 1524, 1523, - // 1522, + 1522, 1521, // 1520, 1519, @@ -423,11 +423,11 @@ static const int included_patches[] = { 1498, 1497, 1496, - // 1495, + 1495, 1494, 1493, 1492, - // 1491, + 1491, 1490, 1489, 1488, @@ -562,7 +562,7 @@ static const int included_patches[] = { 1359, // 1358, 1357, - // 1356, + 1356, // 1355, // 1354, 1353, -- cgit From 831fa45ad84e2f41730db3350289e660006139d6 Mon Sep 17 00:00:00 2001 From: kevinhwang91 Date: Wed, 8 Jan 2020 22:19:23 +0800 Subject: API: nvim_get_hl_by_id: omit hl instead of returning -1 #11685 Problem: When Normal highlight group defines ctermfg/bg, but other highlight group lacks ctermfg/bg, nvim_get_hl_by_id(hl_id, v:false) returns -1 for the missing ctermfg/bg instead of just omitting it. Solution: checking for -1 in hlattrs2dict() fix #11680 --- src/nvim/highlight.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index c96f07ed89..c0cae54572 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -642,11 +642,11 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) PUT(hl, "special", INTEGER_OBJ(ae.rgb_sp_color)); } } else { - if (cterm_normal_fg_color != ae.cterm_fg_color) { + if (cterm_normal_fg_color != ae.cterm_fg_color && ae.cterm_fg_color != 0) { PUT(hl, "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1)); } - if (cterm_normal_bg_color != ae.cterm_bg_color) { + if (cterm_normal_bg_color != ae.cterm_bg_color && ae.cterm_bg_color != 0) { PUT(hl, "background", INTEGER_OBJ(ae.cterm_bg_color - 1)); } } -- cgit From 29b1a4761a9fadfa9de05cfd9c8aad281fc29791 Mon Sep 17 00:00:00 2001 From: butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Thu, 9 Jan 2020 07:31:16 -0800 Subject: tabpage: disallow go-to-previous in cmdline-win #11692 After cbc8d72fde4b19176028490934ff7a447afe523c when editing the command in the command editing window (q:, q/, q?) it was possible to switch to the previous tab. Doing so put Nvim in a bad state. Moreover, switching tabs via the other available mechanisms (gt, gT, gt, gT) is not possible when in the command editing window. Here, the behavior is prevented. It is no longer possible to switch to the previous tab when editing the command in the command editing window. The solution is to share code between gt, gT, and g. Specifically, goto_tabpage_lastused now calls through goto_tabpage rather than directly calling goto_tabpage_tp. Doing so works well because all the validation enjoyed by gt and gT is present in goto_tabpage. --- src/nvim/window.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/window.c b/src/nvim/window.c index 8feedf2de6..e913d33de0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4113,8 +4113,9 @@ void goto_tabpage_tp(tabpage_T *tp, int trigger_enter_autocmds, int trigger_leav // Go to the last accessed tab page, if there is one. void goto_tabpage_lastused(void) { - if (valid_tabpage(lastused_tabpage)) { - goto_tabpage_tp(lastused_tabpage, true, true); + int index = tabpage_index(lastused_tabpage); + if (index < tabpage_index(NULL)) { + goto_tabpage(index); } } -- cgit From 462ee281b779b0f2189132a75dccebeb38a62042 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 11 Jan 2020 16:21:21 -0500 Subject: vim-patch:8.0.1769: refactor save/restore of viewstate #11701 Problem: Repeated saving and restoring viewstate for 'incsearch'. Solution: Use a structure. https://github.com/vim/vim/commit/9b25af36204c0511eab08d621688f0f2008fc68e --- src/nvim/ex_getln.c | 111 +++++++++++++++++++++++----------------------------- 1 file changed, 50 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 2578d1837a..551482dab3 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -136,6 +136,15 @@ struct cmdline_info { /// Last value of prompt_id, incremented when doing new prompt static unsigned last_prompt_id = 0; +typedef struct { + colnr_T vs_curswant; + colnr_T vs_leftcol; + linenr_T vs_topline; + int vs_topfill; + linenr_T vs_botline; + int vs_empty_rows; +} viewstate_T; + typedef struct command_line_state { VimState state; int firstc; @@ -151,18 +160,8 @@ typedef struct command_line_state { int histype; // history type to be used pos_T search_start; // where 'incsearch' starts searching pos_T save_cursor; - colnr_T old_curswant; - colnr_T init_curswant; - colnr_T old_leftcol; - colnr_T init_leftcol; - linenr_T old_topline; - linenr_T init_topline; - int old_topfill; - int init_topfill; - linenr_T old_botline; - linenr_T init_botline; - int old_empty_rows; - int init_empty_rows; + viewstate_T init_viewstate; + viewstate_T old_viewstate; pos_T match_start; pos_T match_end; int did_incsearch; @@ -230,6 +229,28 @@ static int compl_selected; static int cmd_hkmap = 0; // Hebrew mapping during command line +static void save_viewstate(viewstate_T *vs) + FUNC_ATTR_NONNULL_ALL +{ + vs->vs_curswant = curwin->w_curswant; + vs->vs_leftcol = curwin->w_leftcol; + vs->vs_topline = curwin->w_topline; + vs->vs_topfill = curwin->w_topfill; + vs->vs_botline = curwin->w_botline; + vs->vs_empty_rows = curwin->w_empty_rows; +} + +static void restore_viewstate(viewstate_T *vs) + FUNC_ATTR_NONNULL_ALL +{ + curwin->w_curswant = vs->vs_curswant; + curwin->w_leftcol = vs->vs_leftcol; + curwin->w_topline = vs->vs_topline; + curwin->w_topfill = vs->vs_topfill; + curwin->w_botline = vs->vs_botline; + curwin->w_empty_rows = vs->vs_empty_rows; +} + /// Internal entry point for cmdline mode. /// /// caller must use save_cmdline and restore_cmdline. Best is to use @@ -240,22 +261,18 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) static int cmdline_level = 0; cmdline_level++; - CommandLineState state, *s = &state; - memset(s, 0, sizeof(CommandLineState)); - s->firstc = firstc; - s->count = count; - s->indent = indent; - s->save_msg_scroll = msg_scroll; - s->save_State = State; + CommandLineState state = { + .firstc = firstc, + .count = count, + .indent = indent, + .save_msg_scroll = msg_scroll, + .save_State = State, + .ignore_drag_release = true, + .match_start = curwin->w_cursor, + }; + CommandLineState *s = &state; s->save_p_icm = vim_strsave(p_icm); - s->ignore_drag_release = true; - s->match_start = curwin->w_cursor; - s->init_curswant = curwin->w_curswant; - s->init_leftcol = curwin->w_leftcol; - s->init_topline = curwin->w_topline; - s->init_topfill = curwin->w_topfill; - s->init_botline = curwin->w_botline; - s->init_empty_rows = curwin->w_empty_rows; + save_viewstate(&state.init_viewstate); if (s->firstc == -1) { s->firstc = NUL; @@ -273,12 +290,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later s->search_start = curwin->w_cursor; - s->old_curswant = curwin->w_curswant; - s->old_leftcol = curwin->w_leftcol; - s->old_topline = curwin->w_topline; - s->old_topfill = curwin->w_topfill; - s->old_botline = curwin->w_botline; - s->old_empty_rows = curwin->w_empty_rows; + save_viewstate(&state.old_viewstate); assert(indent >= 0); @@ -448,12 +460,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } curwin->w_cursor = s->search_start; // -V519 } - curwin->w_curswant = s->old_curswant; - curwin->w_leftcol = s->old_leftcol; - curwin->w_topline = s->old_topline; - curwin->w_topfill = s->old_topfill; - curwin->w_botline = s->old_botline; - curwin->w_empty_rows = s->old_empty_rows; + restore_viewstate(&s->old_viewstate); highlight_match = false; validate_cursor(); // needed for TAB redraw_all_later(SOME_VALID); @@ -1118,12 +1125,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) update_topline(); validate_cursor(); highlight_match = true; - s->old_curswant = curwin->w_curswant; - s->old_leftcol = curwin->w_leftcol; - s->old_topline = curwin->w_topline; - s->old_topfill = curwin->w_topfill; - s->old_botline = curwin->w_botline; - s->old_empty_rows = curwin->w_empty_rows; + save_viewstate(&s->old_viewstate); update_screen(NOT_VALID); redrawcmdline(); } else { @@ -1244,12 +1246,7 @@ static int command_line_handle_key(CommandLineState *s) s->search_start = s->save_cursor; // save view settings, so that the screen won't be restored at the // wrong position - s->old_curswant = s->init_curswant; - s->old_leftcol = s->init_leftcol; - s->old_topline = s->init_topline; - s->old_topfill = s->init_topfill; - s->old_botline = s->init_botline; - s->old_empty_rows = s->init_empty_rows; + s->old_viewstate = s->init_viewstate; } redrawcmd(); } else if (ccline.cmdlen == 0 && s->c != Ctrl_W @@ -1879,11 +1876,7 @@ static int command_line_changed(CommandLineState *s) // first restore the old curwin values, so the screen is // positioned in the same way as the actual search command - curwin->w_leftcol = s->old_leftcol; - curwin->w_topline = s->old_topline; - curwin->w_topfill = s->old_topfill; - curwin->w_botline = s->old_botline; - curwin->w_empty_rows = s->old_empty_rows; + restore_viewstate(&s->old_viewstate); changed_cline_bef_curs(); update_topline(); @@ -1944,11 +1937,7 @@ static int command_line_changed(CommandLineState *s) // Restore the window "view". curwin->w_cursor = s->save_cursor; - curwin->w_curswant = s->old_curswant; - curwin->w_leftcol = s->old_leftcol; - curwin->w_topline = s->old_topline; - curwin->w_topfill = s->old_topfill; - curwin->w_botline = s->old_botline; + restore_viewstate(&s->old_viewstate); update_topline(); redrawcmdline(); -- cgit From 1cbe8d6d78a6d9fcef36daab5231bb74e58f5c7f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 11 Jan 2020 19:31:05 -0500 Subject: vim-patch:8.0.1593: :qall never exits active :terminal #11704 Problem: :qall never exits with an active terminal window. Solution: Add a way to kill a job in a terminal window. https://github.com/vim/vim/commit/25cdd9c33b21ddbd31321c075873bb225450d2d2 --- src/nvim/ex_cmds2.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 496aecfb27..a3d49c682e 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1414,6 +1414,7 @@ bool check_changed_any(bool hidden, bool unload) size_t bufcount = 0; int *bufnrs; + // Make a list of all buffers, with the most important ones first. FOR_ALL_BUFFERS(buf) { bufcount++; } @@ -1426,14 +1427,15 @@ bool check_changed_any(bool hidden, bool unload) // curbuf bufnrs[bufnum++] = curbuf->b_fnum; - // buf in curtab + + // buffers in current tab FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer != curbuf) { add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); } } - // buf in other tab + // buffers in other tabs FOR_ALL_TABS(tp) { if (tp != curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, tp) { @@ -1442,7 +1444,7 @@ bool check_changed_any(bool hidden, bool unload) } } - // any other buf + // any other buffer FOR_ALL_BUFFERS(buf) { add_bufnum(bufnrs, &bufnum, buf->b_fnum); } @@ -1471,6 +1473,7 @@ bool check_changed_any(bool hidden, bool unload) goto theend; } + // Get here if "buf" cannot be abandoned. ret = true; exiting = false; // When ":confirm" used, don't give an error message. -- cgit From 05ea3c1997a5f8c1c28192cb67a1306485c11b9a Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Sun, 12 Jan 2020 01:36:11 +0100 Subject: defaults: set fillchars "foldsep" to box line #11702 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "│" U+2502 BOX DRAWINGS LIGHT VERTICAL Fallback to old default | if 'ambiwidth' is set. --- src/nvim/option.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index a8a4ad6484..9d3e02949e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3544,7 +3544,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, - { &wp->w_p_fcs_chars.foldsep, "foldsep", '|' }, + { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ { &wp->w_p_fcs_chars.diff, "diff", '-' }, { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, { &wp->w_p_fcs_chars.eob, "eob", '~' }, @@ -3576,9 +3576,11 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is // forbidden (TUI limitation?). Set old defaults. fcs_tab[2].def = '|'; + fcs_tab[6].def = '|'; fcs_tab[3].def = '-'; } else { fcs_tab[2].def = 9474; // │ + fcs_tab[6].def = 9474; // │ fcs_tab[3].def = 183; // · } } -- cgit From dfb676fe0d64c708c0c334b09c947db1bae4736d Mon Sep 17 00:00:00 2001 From: Matthew Malcomson Date: Mon, 13 Jan 2020 01:09:39 +0000 Subject: edit.c: Ensure undo sync when emulating x #11706 After PR #8226 an unmapped META key in insert mode behaves like ESC- (:help i_META). The behaviour does not fully match, since if - is pressed manually then since it were pressed manually `gotchars` would be called on the second after insert-mode had already been left. This would mean that `may_sync_undo` (called from `gotchars`) would call `u_sync(FALSE)` on the second key (since we would be in normal mode). This overall means that behaves differently with respect to undo than [something] when the [something] makes a change. As an example, under `nvim -u NONE`: ihellou leaves the buffer empty, while ihello.u leaves the buffer with one instance of `hello`. - Fix by calling u_sync() manually in the new clause under `normalchar:` in `insert_handle_key`. - Update test in tui_spec.lua that accidentally relied on the old behaviour. --- src/nvim/edit.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index cb5c7023d7..d59924cd08 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1234,6 +1234,7 @@ normalchar: // Unmapped ALT/META chord behaves like ESC+c. #8213 stuffcharReadbuff(ESC); stuffcharReadbuff(s->c); + u_sync(false); break; } -- cgit From 3d1531aee5d92375b69098de8f8c788ea407b066 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 14 Jan 2020 09:21:10 +0100 Subject: API: include invalid buffer/window/tabpage in error message (#11712) --- src/nvim/api/private/helpers.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index cdd1f42ae1..36331eee6e 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -631,7 +631,7 @@ buf_T *find_buffer_by_handle(Buffer buffer, Error *err) buf_T *rv = handle_get_buffer(buffer); if (!rv) { - api_set_error(err, kErrorTypeValidation, "Invalid buffer id"); + api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer); } return rv; @@ -646,7 +646,7 @@ win_T *find_window_by_handle(Window window, Error *err) win_T *rv = handle_get_window(window); if (!rv) { - api_set_error(err, kErrorTypeValidation, "Invalid window id"); + api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window); } return rv; @@ -661,7 +661,7 @@ tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err) tabpage_T *rv = handle_get_tabpage(tabpage); if (!rv) { - api_set_error(err, kErrorTypeValidation, "Invalid tabpage id"); + api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage); } return rv; -- cgit From 55677ddc4637664c8ef034e5c91f79fae8a97396 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 15 Nov 2019 18:21:45 +0100 Subject: Add new marktree data structure for storing marks This is inspired by Atom's "marker index" data structure to efficiently adjust marks to text insertions deletions, but uses a wide B-tree (derived from kbtree) to keep the nesting level down. --- src/nvim/map.h | 2 + src/nvim/marktree.c | 1188 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/marktree.h | 76 ++++ 3 files changed, 1266 insertions(+) create mode 100644 src/nvim/marktree.c create mode 100644 src/nvim/marktree.h (limited to 'src') diff --git a/src/nvim/map.h b/src/nvim/map.h index 75ab64cca4..fec91ac0c2 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -53,6 +53,8 @@ MAP_DECLS(String, handle_T) #define map_del(T, U) map_##T##_##U##_del #define map_clear(T, U) map_##T##_##U##_clear +#define map_size(map) ((map)->table->size) + #define pmap_new(T) map_new(T, ptr_t) #define pmap_free(T) map_free(T, ptr_t) #define pmap_get(T) map_get(T, ptr_t) diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c new file mode 100644 index 0000000000..6ad283f5dc --- /dev/null +++ b/src/nvim/marktree.c @@ -0,0 +1,1188 @@ +// 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 + +// Tree data structure for storing marks at (row, col) positions and updating +// them to arbitrary text changes. Derivative work of kbtree in klib, whose +// copyright notice is reproduced below. Also inspired by the design of the +// marker tree data structure of the Atom editor, regarding efficient updates +// to text changes. +// +// Marks are inserted using marktree_put. Text changes are processed using +// marktree_splice. All read and delete operations use the iterator. +// use marktree_itr_get to put an iterator at a given position or +// marktree_lookup to lookup a mark by its id (iterator optional in this case). +// Use marktree_itr_current and marktree_itr_next/prev to read marks in a loop. +// marktree_del_itr deletes the current mark of the iterator and implicitly +// moves the iterator to the next mark. +// +// Work is ongoing to fully support ranges (mark pairs). + +// Copyright notice for kbtree (included in heavily modified form): +// +// Copyright 1997-1999, 2001, John-Mark Gurney. +// 2008-2009, Attractive Chaos +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. +// +// Changes done by by the neovim project follow the Apache v2 license available +// at the repo root. + +#include + +#include "nvim/marktree.h" +#include "nvim/lib/kvec.h" +#include "nvim/garray.h" + +#define T MT_BRANCH_FACTOR +#define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *)) +#define key_t SKRAPET + +#define RIGHT_GRAVITY (((uint64_t)1) << 63) +#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1)) +#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY) + +#define PAIRED MARKTREE_PAIRED_FLAG +#define END_FLAG MARKTREE_END_FLAG +#define ID_INCR (((uint64_t)1) << 2) + +#define PROP_MASK (RIGHT_GRAVITY|PAIRED|END_FLAG) + +#define rawkey(itr) (itr->node->key[itr->i]) + +static bool pos_leq(mtpos_t a, mtpos_t b) +{ + return a.row < b.row || (a.row == b.row && a.col <= b.col); +} + +static void relative(mtpos_t base, mtpos_t *val) +{ + assert(pos_leq(base, *val)); + if (val->row == base.row) { + val->row = 0; + val->col -= base.col; + } else { + val->row -= base.row; + } +} + +static void unrelative(mtpos_t base, mtpos_t *val) +{ + if (val->row == 0) { + val->row = base.row; + val->col += base.col; + } else { + val->row += base.row; + } +} + +static void compose(mtpos_t *base, mtpos_t val) +{ + if (val.row == 0) { + base->col += val.col; + } else { + base->row += val.row; + base->col = val.col; + } +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "marktree.c.generated.h" +#endif + +#define mt_generic_cmp(a, b) (((b) < (a)) - ((a) < (b))) +static int key_cmp(mtkey_t a, mtkey_t b) +{ + int cmp = mt_generic_cmp(a.pos.row, b.pos.row); + if (cmp != 0) { + return cmp; + } + cmp = mt_generic_cmp(a.pos.col, b.pos.col); + if (cmp != 0) { + return cmp; + } + // NB: keeping the events at the same pos sorted by id is actually not + // necessary only make sure that START is before END etc. + return mt_generic_cmp(a.id, b.id); +} + +static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) +{ + int tr, *rr, begin = 0, end = x->n; + if (x->n == 0) { + return -1; + } + rr = r? r : &tr; + while (begin < end) { + int mid = (begin + end) >> 1; + if (key_cmp(x->key[mid], k) < 0) { + begin = mid + 1; + } else { + end = mid; + } + } + if (begin == x->n) { *rr = 1; return x->n - 1; } + if ((*rr = key_cmp(k, x->key[begin])) < 0) { + begin--; + } + return begin; +} + +static inline void refkey(MarkTree *b, mtnode_t *x, int i) +{ + pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x); +} + +// put functions + +// x must be an internal node, which is not full +// x->ptr[i] should be a full node, i e x->ptr[i]->n == 2*T-1 +static inline void split_node(MarkTree *b, mtnode_t *x, const int i) +{ + mtnode_t *y = x->ptr[i]; + mtnode_t *z; + z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t)); + b->n_nodes++; + z->level = y->level; + z->n = T - 1; + memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (T - 1)); + for (int j = 0; j < T-1; j++) { + refkey(b, z, j); + } + if (y->level) { + memcpy(z->ptr, &y->ptr[T], sizeof(mtnode_t *) * T); + for (int j = 0; j < T; j++) { + z->ptr[j]->parent = z; + } + } + y->n = T - 1; + memmove(&x->ptr[i + 2], &x->ptr[i + 1], + sizeof(mtnode_t *) * (size_t)(x->n - i)); + x->ptr[i + 1] = z; + z->parent = x; // == y->parent + memmove(&x->key[i + 1], &x->key[i], sizeof(mtkey_t) * (size_t)(x->n - i)); + + // move key to internal layer: + x->key[i] = y->key[T - 1]; + refkey(b, x, i); + x->n++; + + for (int j = 0; j < T-1; j++) { + relative(x->key[i].pos, &z->key[j].pos); + } + if (i > 0) { + unrelative(x->key[i-1].pos, &x->key[i].pos); + } +} + +// x must not be a full node (even if there might be internal space) +static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) +{ + int i = x->n - 1; + if (x->level == 0) { + i = marktree_getp_aux(x, k, 0); + if (i != x->n - 1) { + memmove(&x->key[i + 2], &x->key[i + 1], + (size_t)(x->n - i - 1) * sizeof(mtkey_t)); + } + x->key[i + 1] = k; + refkey(b, x, i+1); + x->n++; + } else { + i = marktree_getp_aux(x, k, 0) + 1; + if (x->ptr[i]->n == 2 * T - 1) { + split_node(b, x, i); + if (key_cmp(k, x->key[i]) > 0) { + i++; + } + } + if (i > 0) { + relative(x->key[i-1].pos, &k.pos); + } + marktree_putp_aux(b, x->ptr[i], k); + } +} + +uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity) +{ + uint64_t id = (b->next_id+=ID_INCR); + uint64_t keyid = id; + if (right_gravity) { + // order all right gravity keys after the left ones, for effortless + // insertion (but not deletion!) + keyid |= RIGHT_GRAVITY; + } + marktree_put_key(b, row, col, keyid); + return id; +} + +uint64_t marktree_put_pair(MarkTree *b, + int start_row, int start_col, bool start_right, + int end_row, int end_col, bool end_right) +{ + uint64_t id = (b->next_id+=ID_INCR)|PAIRED; + uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0); + uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0); + marktree_put_key(b, start_row, start_col, start_id); + marktree_put_key(b, end_row, end_col, end_id); + return id; +} + +void marktree_put_key(MarkTree *b, int row, int col, uint64_t id) +{ + mtkey_t k = { .pos = { .row = row, .col = col }, .id = id }; + + if (!b->root) { + b->root = (mtnode_t *)xcalloc(1, ILEN); + b->id2node = pmap_new(uint64_t)(); + b->n_nodes++; + } + mtnode_t *r, *s; + b->n_keys++; + r = b->root; + if (r->n == 2 * T - 1) { + b->n_nodes++; + s = (mtnode_t *)xcalloc(1, ILEN); + b->root = s; s->level = r->level+1; s->n = 0; + s->ptr[0] = r; + r->parent = s; + split_node(b, s, 0); + r = s; + } + marktree_putp_aux(b, r, k); +} + +/// INITIATING DELETION PROTOCOL: +/// +/// 1. Construct a valid iterator to the node to delete (argument) +/// 2. If an "internal" key. Iterate one step to the left or right, +/// which gives an internal key "auxiliary key". +/// 3. Now delete this internal key (intended or auxiliary). +/// The leaf node X might become undersized. +/// 4. If step two was done: now replace the key that _should_ be +/// deleted with the auxiliary key. Adjust relative +/// 5. Now "repair" the tree as needed. We always start at a leaf node X. +/// - if the node is big enough, terminate +/// - if we can steal from the left, steal +/// - if we can steal from the right, steal +/// - otherwise merge this node with a neighbour. This might make our +/// parent undersized. So repeat 5 for the parent. +/// 6. If 4 went all the way to the root node. The root node +/// might have ended up with size 0. Delete it then. +/// +/// NB: ideally keeps the iterator valid. Like point to the key after this +/// if present. +/// +/// @param rev should be true if we plan to iterate _backwards_ and delete +/// stuff before this key. Most of the time this is false (the +/// recommended strategy is to always iterate forward) +void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) +{ + int adjustment = 0; + + mtnode_t *cur = itr->node; + int curi = itr->i; + uint64_t id = cur->key[curi].id; + // fprintf(stderr, "\nDELET %lu\n", id); + + if (itr->node->level) { + if (rev) { + abort(); + } else { + // fprintf(stderr, "INTERNAL %d\n", cur->level); + // steal previous node + marktree_itr_prev(b, itr); + adjustment = -1; + } + } + + // 3. + mtnode_t *x = itr->node; + assert(x->level == 0); + mtkey_t intkey = x->key[itr->i]; + if (x->n > itr->i+1) { + memmove(&x->key[itr->i], &x->key[itr->i+1], + sizeof(mtkey_t) * (size_t)(x->n - itr->i-1)); + } + x->n--; + + // 4. + if (adjustment) { + if (adjustment == 1) { + abort(); + } else { // adjustment == -1 + int ilvl = itr->lvl-1; + mtnode_t *lnode = x; + do { + mtnode_t *p = lnode->parent; + if (ilvl < 0) { + abort(); + } + int i = itr->s[ilvl].i; + assert(p->ptr[i] == lnode); + if (i > 0) { + unrelative(p->key[i-1].pos, &intkey.pos); + } + lnode = p; + ilvl--; + } while (lnode != cur); + + mtkey_t deleted = cur->key[curi]; + cur->key[curi] = intkey; + refkey(b, cur, curi); + relative(intkey.pos, &deleted.pos); + mtnode_t *y = cur->ptr[curi+1]; + if (deleted.pos.row || deleted.pos.col) { + while (y) { + for (int k = 0; k < y->n; k++) { + unrelative(deleted.pos, &y->key[k].pos); + } + y = y->level ? y->ptr[0] : NULL; + } + } + } + } + + b->n_keys--; + pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id)); + + // 5. + bool itr_dirty = false; + int rlvl = itr->lvl-1; + int *lasti = &itr->i; + while (x != b->root) { + assert(rlvl >= 0); + mtnode_t *p = x->parent; + if (x->n >= T-1) { + // we are done, if this node is fine the rest of the tree will be + break; + } + int pi = itr->s[rlvl].i; + assert(p->ptr[pi] == x); + if (pi > 0 && p->ptr[pi-1]->n > T-1) { + *lasti += 1; + itr_dirty = true; + // steal one key from the left neighbour + pivot_right(b, p, pi-1); + break; + } else if (pi < p->n && p->ptr[pi+1]->n > T-1) { + // steal one key from right neighbour + pivot_left(b, p, pi); + break; + } else if (pi > 0) { + // fprintf(stderr, "LEFT "); + assert(p->ptr[pi-1]->n == T-1); + // merge with left neighbour + *lasti += T; + x = merge_node(b, p, pi-1); + if (lasti == &itr->i) { + // TRICKY: we merged the node the iterator was on + itr->node = x; + } + itr->s[rlvl].i--; + itr_dirty = true; + } else { + // fprintf(stderr, "RIGHT "); + assert(pi < p->n && p->ptr[pi+1]->n == T-1); + merge_node(b, p, pi); + // no iter adjustment needed + } + lasti = &itr->s[rlvl].i; + rlvl--; + x = p; + } + + // 6. + if (b->root->n == 0) { + if (itr->lvl > 0) { + memmove(itr->s, itr->s+1, (size_t)(itr->lvl-1) * sizeof(*itr->s)); + itr->lvl--; + } + if (b->root->level) { + mtnode_t *oldroot = b->root; + b->root = b->root->ptr[0]; + b->root->parent = NULL; + xfree(oldroot); + } else { + // no items, nothing for iterator to point to + // not strictly needed, should handle delete right-most mark anyway + itr->node = NULL; + } + } + + if (itr->node && itr_dirty) { + marktree_itr_fix_pos(b, itr); + } + + // BONUS STEP: fix the iterator, so that it points to the key afterwards + // TODO(bfredl): with "rev" should point before + if (adjustment == 1) { + abort(); + } else if (adjustment == -1) { + // tricky: we stand at the deleted space in the previous leaf node. + // But the inner key is now the previous key we stole, so we need + // to skip that one as well. + marktree_itr_next(b, itr); + marktree_itr_next(b, itr); + } else { + if (itr->node && itr->i >= itr->node->n) { + // we deleted the last key of a leaf node + // go to the inner key after that. + assert(itr->node->level == 0); + marktree_itr_next(b, itr); + } + } +} + +static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i) +{ + mtnode_t *x = p->ptr[i], *y = p->ptr[i+1]; + + x->key[x->n] = p->key[i]; + refkey(b, x, x->n); + if (i > 0) { + relative(p->key[i-1].pos, &x->key[x->n].pos); + } + + memmove(&x->key[x->n+1], y->key, (size_t)y->n * sizeof(mtkey_t)); + for (int k = 0; k < y->n; k++) { + refkey(b, x, x->n+1+k); + unrelative(x->key[x->n].pos, &x->key[x->n+1+k].pos); + } + if (x->level) { + memmove(&x->ptr[x->n+1], y->ptr, (size_t)(y->n + 1) * sizeof(mtnode_t *)); + for (int k = 0; k < y->n+1; k++) { + x->ptr[x->n+k+1]->parent = x; + } + } + x->n += y->n+1; + memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t)); + memmove(&p->ptr[i + 1], &p->ptr[i + 2], + (size_t)(p->n - i - 1) * sizeof(mtkey_t *)); + p->n--; + xfree(y); + b->n_nodes--; + return x; +} + +// TODO(bfredl): as a potential "micro" optimization, pivoting should balance +// the two nodes instead of stealing just one key +static void pivot_right(MarkTree *b, mtnode_t *p, int i) +{ + mtnode_t *x = p->ptr[i], *y = p->ptr[i+1]; + memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t)); + if (y->level) { + memmove(&y->ptr[1], y->ptr, (size_t)(y->n + 1) * sizeof(mtnode_t *)); + } + y->key[0] = p->key[i]; + refkey(b, y, 0); + p->key[i] = x->key[x->n - 1]; + refkey(b, p, i); + if (x->level) { + y->ptr[0] = x->ptr[x->n]; + y->ptr[0]->parent = y; + } + x->n--; + y->n++; + if (i > 0) { + unrelative(p->key[i-1].pos, &p->key[i].pos); + } + relative(p->key[i].pos, &y->key[0].pos); + for (int k = 1; k < y->n; k++) { + unrelative(y->key[0].pos, &y->key[k].pos); + } +} + +static void pivot_left(MarkTree *b, mtnode_t *p, int i) +{ + mtnode_t *x = p->ptr[i], *y = p->ptr[i+1]; + + // reverse from how we "always" do it. but pivot_left + // is just the inverse of pivot_right, so reverse it literally. + for (int k = 1; k < y->n; k++) { + relative(y->key[0].pos, &y->key[k].pos); + } + unrelative(p->key[i].pos, &y->key[0].pos); + if (i > 0) { + relative(p->key[i-1].pos, &p->key[i].pos); + } + + x->key[x->n] = p->key[i]; + refkey(b, x, x->n); + p->key[i] = y->key[0]; + refkey(b, p, i); + if (x->level) { + x->ptr[x->n+1] = y->ptr[0]; + x->ptr[x->n+1]->parent = x; + } + memmove(y->key, &y->key[1], (size_t)(y->n-1) * sizeof(mtkey_t)); + if (y->level) { + memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *)); + } + x->n++; + y->n--; +} + +/// frees all mem, resets tree to valid empty state +void marktree_clear(MarkTree *b) +{ + if (b->root) { + marktree_free_node(b->root); + b->root = NULL; + } + if (b->id2node) { + pmap_free(uint64_t)(b->id2node); + b->id2node = NULL; + } + b->n_keys = 0; + b->n_nodes = 0; +} + +void marktree_free_node(mtnode_t *x) +{ + if (x->level) { + for (int i = 0; i < x->n+1; i++) { + marktree_free_node(x->ptr[i]); + } + } + xfree(x); +} + +/// NB: caller must check not pair! +uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr) +{ + uint64_t old_id = rawkey(itr).id; + pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id)); + uint64_t new_id = (b->next_id += ID_INCR); + rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id); + refkey(b, itr->node, itr->i); + return new_id; +} + +void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) +{ + uint64_t old_id = rawkey(itr).id; + // TODO(bfredl): optimize when moving a mark within a leaf without moving it + // across neighbours! + marktree_del_itr(b, itr, false); + marktree_put_key(b, row, col, old_id); + itr->node = NULL; // itr might become invalid by put +} + +// itr functions + +// TODO(bfredl): static inline? +bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr) +{ + return marktree_itr_get_ext(b, (mtpos_t){ row, col }, + itr, false, false, NULL); +} + +bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, + bool last, bool gravity, mtpos_t *oldbase) +{ + mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 }; + if (last && !gravity) { + k.id = UINT64_MAX; + } + if (b->n_keys == 0) { + itr->node = NULL; + return false; + } + itr->pos = (mtpos_t){ 0, 0 }; + itr->node = b->root; + itr->lvl = 0; + if (oldbase) { + oldbase[itr->lvl] = itr->pos; + } + while (true) { + itr->i = marktree_getp_aux(itr->node, k, 0)+1; + + if (itr->node->level == 0) { + break; + } + + itr->s[itr->lvl].i = itr->i; + itr->s[itr->lvl].oldcol = itr->pos.col; + + if (itr->i > 0) { + compose(&itr->pos, itr->node->key[itr->i-1].pos); + relative(itr->node->key[itr->i-1].pos, &k.pos); + } + itr->node = itr->node->ptr[itr->i]; + itr->lvl++; + if (oldbase) { + oldbase[itr->lvl] = itr->pos; + } + } + + if (last) { + return marktree_itr_prev(b, itr); + } else if (itr->i >= itr->node->n) { + return marktree_itr_next(b, itr); + } + return true; +} + +bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr) +{ + itr->node = b->root; + if (b->n_keys == 0) { + return false; + } + + itr->i = 0; + itr->lvl = 0; + itr->pos = (mtpos_t){ 0, 0 }; + while (itr->node->level > 0) { + itr->s[itr->lvl].i = 0; + itr->s[itr->lvl].oldcol = 0; + itr->lvl++; + itr->node = itr->node->ptr[0]; + } + return true; +} + +// gives the first key that is greater or equal to p +int marktree_itr_last(MarkTree *b, MarkTreeIter *itr) +{ + if (b->n_keys == 0) { + itr->node = NULL; + return false; + } + itr->pos = (mtpos_t){ 0, 0 }; + itr->node = b->root; + itr->lvl = 0; + while (true) { + itr->i = itr->node->n; + + if (itr->node->level == 0) { + break; + } + + itr->s[itr->lvl].i = itr->i; + itr->s[itr->lvl].oldcol = itr->pos.col; + + assert(itr->i > 0); + compose(&itr->pos, itr->node->key[itr->i-1].pos); + + itr->node = itr->node->ptr[itr->i]; + itr->lvl++; + } + itr->i--; + return true; +} + +// TODO(bfredl): static inline +bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr) +{ + return marktree_itr_next_skip(b, itr, false, NULL); +} + +static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, + mtpos_t oldbase[]) +{ + if (!itr->node) { + return false; + } + itr->i++; + if (itr->node->level == 0 || skip) { + if (itr->i < itr->node->n) { + // TODO(bfredl): this is the common case, + // and could be handled by inline wrapper + return true; + } + // we ran out of non-internal keys. Go up until we find an internal key + while (itr->i >= itr->node->n) { + itr->node = itr->node->parent; + if (itr->node == NULL) { + return false; + } + itr->lvl--; + itr->i = itr->s[itr->lvl].i; + if (itr->i > 0) { + itr->pos.row -= itr->node->key[itr->i-1].pos.row; + itr->pos.col = itr->s[itr->lvl].oldcol; + } + } + } else { + // we stood at an "internal" key. Go down to the first non-internal + // key after it. + while (itr->node->level > 0) { + // internal key, there is always a child after + if (itr->i > 0) { + itr->s[itr->lvl].oldcol = itr->pos.col; + compose(&itr->pos, itr->node->key[itr->i-1].pos); + } + if (oldbase && itr->i == 0) { + oldbase[itr->lvl+1] = oldbase[itr->lvl]; + } + itr->s[itr->lvl].i = itr->i; + assert(itr->node->ptr[itr->i]->parent == itr->node); + itr->node = itr->node->ptr[itr->i]; + itr->i = 0; + itr->lvl++; + } + } + return true; +} + +bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr) +{ + if (!itr->node) { + return false; + } + if (itr->node->level == 0) { + itr->i--; + if (itr->i >= 0) { + // TODO(bfredl): this is the common case, + // and could be handled by inline wrapper + return true; + } + // we ran out of non-internal keys. Go up until we find a non-internal key + while (itr->i < 0) { + itr->node = itr->node->parent; + if (itr->node == NULL) { + return false; + } + itr->lvl--; + itr->i = itr->s[itr->lvl].i-1; + if (itr->i >= 0) { + itr->pos.row -= itr->node->key[itr->i].pos.row; + itr->pos.col = itr->s[itr->lvl].oldcol; + } + } + } else { + // we stood at an "internal" key. Go down to the last non-internal + // key before it. + while (itr->node->level > 0) { + // internal key, there is always a child before + if (itr->i > 0) { + itr->s[itr->lvl].oldcol = itr->pos.col; + compose(&itr->pos, itr->node->key[itr->i-1].pos); + } + itr->s[itr->lvl].i = itr->i; + assert(itr->node->ptr[itr->i]->parent == itr->node); + itr->node = itr->node->ptr[itr->i]; + itr->i = itr->node->n; + itr->lvl++; + } + itr->i--; + } + return true; +} + +void marktree_itr_rewind(MarkTree *b, MarkTreeIter *itr) +{ + if (!itr->node) { + return; + } + if (itr->node->level) { + marktree_itr_prev(b, itr); + } + itr->i = 0; +} + +bool marktree_itr_node_done(MarkTreeIter *itr) +{ + return !itr->node || itr->i == itr->node->n-1; +} + + +mtpos_t marktree_itr_pos(MarkTreeIter *itr) +{ + mtpos_t pos = rawkey(itr).pos; + unrelative(itr->pos, &pos); + return pos; +} + +mtmark_t marktree_itr_current(MarkTreeIter *itr) +{ + if (itr->node) { + uint64_t keyid = rawkey(itr).id; + mtpos_t pos = marktree_itr_pos(itr); + mtmark_t mark = { .row = pos.row, + .col = pos.col, + .id = ANTIGRAVITY(keyid), + .right_gravity = keyid & RIGHT_GRAVITY }; + return mark; + } + return (mtmark_t){ -1, -1, 0, false }; +} + +static void swap_id(uint64_t *id1, uint64_t *id2) +{ + uint64_t temp = *id1; + *id1 = *id2; + *id2 = temp; +} + +bool marktree_splice(MarkTree *b, + int start_line, int start_col, + int old_extent_line, int old_extent_col, + int new_extent_line, int new_extent_col) +{ + mtpos_t start = { start_line, start_col }; + mtpos_t old_extent = { (int)old_extent_line, old_extent_col }; + mtpos_t new_extent = { (int)new_extent_line, new_extent_col }; + + bool may_delete = (old_extent.row != 0 || old_extent.col != 0); + bool same_line = old_extent.row == 0 && new_extent.row == 0; + unrelative(start, &old_extent); + unrelative(start, &new_extent); + MarkTreeIter itr[1], enditr[1]; + + mtpos_t oldbase[MT_MAX_DEPTH]; + + marktree_itr_get_ext(b, start, itr, false, true, oldbase); + if (!itr->node) { + // den e FÄRDIG + return false; + } + mtpos_t delta = { new_extent.row - old_extent.row, + new_extent.col-old_extent.col }; + + if (may_delete) { + mtpos_t ipos = marktree_itr_pos(itr); + if (!pos_leq(old_extent, ipos) + || (old_extent.row == ipos.row && old_extent.col == ipos.col + && !IS_RIGHT(rawkey(itr).id))) { + marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL); + assert(enditr->node); + // "assert" (itr <= enditr) + } else { + may_delete = false; + } + } + + bool past_right = false; + bool moved = false; + + // Follow the general strategy of messing things up and fix them later + // "oldbase" carries the information needed to calculate old position of + // children. + if (may_delete) { + while (itr->node && !past_right) { + mtpos_t loc_start = start; + mtpos_t loc_old = old_extent; + relative(itr->pos, &loc_start); + + relative(oldbase[itr->lvl], &loc_old); + +continue_same_node: + // NB: strictly should be less than the right gravity of loc_old, but + // the iter comparison below will already break on that. + if (!pos_leq(rawkey(itr).pos, loc_old)) { + break; + } + + if (IS_RIGHT(rawkey(itr).id)) { + while (rawkey(itr).id != rawkey(enditr).id + && IS_RIGHT(rawkey(enditr).id)) { + marktree_itr_prev(b, enditr); + } + if (!IS_RIGHT(rawkey(enditr).id)) { + swap_id(&rawkey(itr).id, &rawkey(enditr).id); + refkey(b, itr->node, itr->i); + refkey(b, enditr->node, enditr->i); + } else { + past_right = true; + break; + } + } + + if (rawkey(itr).id == rawkey(enditr).id) { + // actually, will be past_right after this key + past_right = true; + } + + moved = true; + if (itr->node->level) { + oldbase[itr->lvl+1] = rawkey(itr).pos; + unrelative(oldbase[itr->lvl], &oldbase[itr->lvl+1]); + rawkey(itr).pos = loc_start; + marktree_itr_next_skip(b, itr, false, oldbase); + } else { + rawkey(itr).pos = loc_start; + if (itr->i < itr->node->n-1) { + itr->i++; + if (!past_right) { + goto continue_same_node; + } + } else { + marktree_itr_next(b, itr); + } + } + } + while (itr->node) { + mtpos_t loc_new = new_extent; + relative(itr->pos, &loc_new); + mtpos_t limit = old_extent; + + relative(oldbase[itr->lvl], &limit); + +past_continue_same_node: + + if (pos_leq(limit, rawkey(itr).pos)) { + break; + } + + mtpos_t oldpos = rawkey(itr).pos; + rawkey(itr).pos = loc_new; + moved = true; + if (itr->node->level) { + oldbase[itr->lvl+1] = oldpos; + unrelative(oldbase[itr->lvl], &oldbase[itr->lvl+1]); + + marktree_itr_next_skip(b, itr, false, oldbase); + } else { + if (itr->i < itr->node->n-1) { + itr->i++; + goto past_continue_same_node; + } else { + marktree_itr_next(b, itr); + } + } + } + } + + + while (itr->node) { + unrelative(oldbase[itr->lvl], &rawkey(itr).pos); + int realrow = rawkey(itr).pos.row; + assert(realrow >= old_extent.row); + bool done = false; + if (realrow == old_extent.row) { + if (delta.col) { + rawkey(itr).pos.col += delta.col; + moved = true; + } + } else { + if (same_line) { + // optimization: column only adjustment can skip remaining rows + done = true; + } + } + if (delta.row) { + rawkey(itr).pos.row += delta.row; + moved = true; + } + relative(itr->pos, &rawkey(itr).pos); + if (done) { + break; + } + marktree_itr_next_skip(b, itr, true, NULL); + } + return moved; +} + +void marktree_move_region(MarkTree *b, + int start_row, colnr_T start_col, + int extent_row, colnr_T extent_col, + int new_row, colnr_T new_col) +{ + mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col }; + mtpos_t end = size; + unrelative(start, &end); + MarkTreeIter itr[1]; + marktree_itr_get_ext(b, start, itr, false, true, NULL); + kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; + while (itr->node) { + mtpos_t pos = marktree_itr_pos(itr); + if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col + && rawkey(itr).id & RIGHT_GRAVITY)) { + break; + } + relative(start, &pos); + kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id })); + marktree_del_itr(b, itr, false); + } + + marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0); + mtpos_t new = { new_row, new_col }; + marktree_splice(b, new.row, new.col, + 0, 0, size.row, size.col); + + for (size_t i = 0; i < kv_size(saved); i++) { + mtkey_t item = kv_A(saved, i); + unrelative(new, &item.pos); + marktree_put_key(b, item.pos.row, item.pos.col, item.id); + } + kv_destroy(saved); +} + +/// @param itr OPTIONAL. set itr to pos. +mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) +{ + mtnode_t *n = pmap_get(uint64_t)(b->id2node, id); + if (n == NULL) { + if (itr) { + itr->node = NULL; + } + return (mtpos_t){ -1, -1 }; + } + int i = 0; + for (i = 0; i < n->n; i++) { + if (ANTIGRAVITY(n->key[i].id) == id) { + goto found; + } + } + abort(); +found: {} + mtpos_t pos = n->key[i].pos; + if (itr) { + itr->i = i; + itr->node = n; + itr->lvl = b->root->level - n->level; + } + while (n->parent != NULL) { + mtnode_t *p = n->parent; + for (i = 0; i < p->n+1; i++) { + if (p->ptr[i] == n) { + goto found_node; + } + } + abort(); +found_node: + if (itr) { + itr->s[b->root->level-p->level].i = i; + } + if (i > 0) { + unrelative(p->key[i-1].pos, &pos); + } + n = p; + } + if (itr) { + marktree_itr_fix_pos(b, itr); + } + return pos; +} + +static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) +{ + itr->pos = (mtpos_t){ 0, 0 }; + mtnode_t *x = b->root; + for (int lvl = 0; lvl < itr->lvl; lvl++) { + itr->s[lvl].oldcol = itr->pos.col; + int i = itr->s[lvl].i; + if (i > 0) { + compose(&itr->pos, x->key[i-1].pos); + } + assert(x->level); + x = x->ptr[i]; + } + assert(x == itr->node); +} + +void marktree_check(MarkTree *b) +{ + if (b->root == NULL) { + assert(b->n_keys == 0); + assert(b->n_nodes == 0); + assert(b->id2node == NULL || map_size(b->id2node) == 0); + return; + } + + mtpos_t dummy; + bool last_right = false; + size_t nkeys = check_node(b, b->root, &dummy, &last_right); + assert(b->n_keys == nkeys); + assert(b->n_keys == map_size(b->id2node)); +} + +size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right) +{ + assert(x->n <= 2 * T - 1); + // TODO(bfredl): too strict if checking "in repair" post-delete tree. + assert(x->n >= (x != b->root ? T-1 : 0)); + size_t n_keys = (size_t)x->n; + + for (int i = 0; i < x->n; i++) { + if (x->level) { + n_keys += check_node(b, x->ptr[i], last, last_right); + } else { + *last = (mtpos_t) { 0, 0 }; + } + if (i > 0) { + unrelative(x->key[i-1].pos, last); + } + if (x->level) { + } + assert(pos_leq(*last, x->key[i].pos)); + if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) { + assert(!*last_right || IS_RIGHT(x->key[i].id)); + } + *last_right = IS_RIGHT(x->key[i].id); + assert(x->key[i].pos.col >= 0); + assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x); + } + + if (x->level) { + n_keys += check_node(b, x->ptr[x->n], last, last_right); + unrelative(x->key[x->n-1].pos, last); + + for (int i = 0; i < x->n+1; i++) { + assert(x->ptr[i]->parent == x); + assert(x->ptr[i]->level == x->level-1); + // PARANOIA: check no double node ref + for (int j = 0; j < i; j++) { + assert(x->ptr[i] != x->ptr[j]); + } + } + } else { + *last = x->key[x->n-1].pos; + } + return n_keys; +} + +char *mt_inspect_rec(MarkTree *b) +{ + garray_T ga; + ga_init(&ga, (int)sizeof(char), 80); + mtpos_t p = { 0, 0 }; + mt_inspect_node(b, &ga, b->root, p); + return ga.ga_data; +} + +void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off) +{ + static char buf[1024]; +#define GA_PUT(x) ga_concat(ga, (char_u *)(x)) + GA_PUT("["); + if (n->level) { + mt_inspect_node(b, ga, n->ptr[0], off); + } + for (int i = 0; i < n->n; i++) { + mtpos_t p = n->key[i].pos; + unrelative(off, &p); + snprintf((char *)buf, sizeof(buf), "%d/%d", p.row, p.col); + GA_PUT(buf); + if (n->level) { + mt_inspect_node(b, ga, n->ptr[i+1], p); + } else { + GA_PUT(","); + } + } + GA_PUT("]"); +#undef GA_PUT +} + diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h new file mode 100644 index 0000000000..0c73e75b2e --- /dev/null +++ b/src/nvim/marktree.h @@ -0,0 +1,76 @@ +#ifndef NVIM_MARKTREE_H +#define NVIM_MARKTREE_H + +#include +#include "nvim/map.h" +#include "nvim/garray.h" + +#define MT_MAX_DEPTH 20 +#define MT_BRANCH_FACTOR 10 + +typedef struct { + int32_t row; + int32_t col; +} mtpos_t; + +typedef struct { + int32_t row; + int32_t col; + uint64_t id; + bool right_gravity; +} mtmark_t; + +typedef struct mtnode_s mtnode_t; +typedef struct { + int oldcol; + int i; +} iterstate_t; + +typedef struct { + mtpos_t pos; + int lvl; + mtnode_t *node; + int i; + iterstate_t s[MT_MAX_DEPTH]; +} MarkTreeIter; + + +// Internal storage +// +// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for +// "space before (row,col)" +typedef struct { + mtpos_t pos; + uint64_t id; +} mtkey_t; + +struct mtnode_s { + int32_t n; + int32_t level; + // TODO(bfredl): we could consider having a only-sometimes-valid + // index into parent for faster "chached" lookup. + mtnode_t *parent; + mtkey_t key[2 * MT_BRANCH_FACTOR - 1]; + mtnode_t *ptr[]; +}; + +// TODO(bfredl): the iterator is pretty much everpresent, make it part of the +// tree struct itself? +typedef struct { + mtnode_t *root; + size_t n_keys, n_nodes; + uint64_t next_id; + // TODO(bfredl): the pointer to node could be part of the larger + // Map(uint64_t, ExtmarkItem) essentially; + PMap(uint64_t) *id2node; +} MarkTree; + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "marktree.h.generated.h" +#endif + +#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1) +#define MARKTREE_END_FLAG (((uint64_t)1) << 0) + +#endif // NVIM_MARKTREE_H -- cgit From 53473b3b71def9cdb7753523345c2171701b6889 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Tue, 14 Jan 2020 22:51:11 +0100 Subject: fillchars: fix display on closed fold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rollback of the last written symbol was not thorough, hence confusing the code later on and causing a buggy display. To reproduce, use `set fillchars+=foldopen:▾,foldsep:│` and close a fold. Foldcolumn should display a glitch. --- src/nvim/screen.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 0612575e67..34b775ba12 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2123,11 +2123,11 @@ fill_foldcolumn( if (closed) { if (symbol != 0) { - // rollback length + // rollback previous write char_counter -= len; + memset(&p[char_counter], ' ', len); } - symbol = wp->w_p_fcs_chars.foldclosed; - len = utf_char2bytes(symbol, &p[char_counter]); + len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); char_counter += len; } -- cgit From f86acd213b63e2d4b3ed87ad3a7998fa2d7a43ad Mon Sep 17 00:00:00 2001 From: Marvim the Paranoid Android Date: Wed, 15 Jan 2020 08:59:48 +0100 Subject: version.c: update [ci skip] #11689 vim-patch:8.0.1789: BufWinEnter does not work well for a terminal window vim-patch:8.2.0105: Vim license not easy to find on github vim-patch:8.2.0106: printf formats are not exactly right vim-patch:8.2.0107: hgignore is out of sync from gitignore --- src/nvim/version.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/version.c b/src/nvim/version.c index 8bfd2f5021..c67fd9175f 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -149,7 +149,7 @@ static const int included_patches[] = { 1772, 1771, 1770, - // 1769, + 1769, 1768, 1767, 1766, @@ -325,7 +325,7 @@ static const int included_patches[] = { 1596, 1595, 1594, - // 1593, + 1593, // 1592, // 1591, 1590, @@ -791,7 +791,7 @@ static const int included_patches[] = { 1130, // 1129, 1128, - // 1127, + 1127, 1126, // 1125, 1124, @@ -1016,7 +1016,7 @@ static const int included_patches[] = { 905, 904, 903, - // 902, + 902, 901, 900, 899, -- cgit From 8ba3354d74a8f90ded0997100bdbe845a8c5382f Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 15 Jan 2020 09:08:22 +0100 Subject: api_set_error: include expression with "Failed to evaluate expression" (#11713) --- src/nvim/api/vim.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d35d04cdb3..baa0387fd8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -443,7 +443,8 @@ Object nvim_eval(String expr, Error *err) if (!try_end(err)) { if (ok == FAIL) { // Should never happen, try_end() should get the error. #8371 - api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); + api_set_error(err, kErrorTypeException, + "Failed to evaluate expression: '%.*s'", 256, expr.data); } else { rv = vim_to_object(&rettv); } -- cgit From ca1a00edd6d6345b848a28d077d6a192528f811e Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 14 Jan 2020 12:45:09 +0100 Subject: extmarks/bufhl: reimplement using new marktree data structure Add new "splice" interface for tracking buffer changes at the byte level. This will later be reused for byte-resolution buffer updates. (Implementation has been started, but using undocumented "_on_bytes" option now as interface hasn't been finalized). Use this interface to improve many edge cases of extmark adjustment. Changed tests indicate previously incorrect behavior. Adding tests for more edge cases will be follow-up work (overlaps on_bytes tests) Don't consider creation/deletion of marks an undoable event by itself. This behavior was never documented, and imposes complexity for little gain. Add nvim__buf_add_decoration temporary API for direct access to the new implementation. This should be refactored into a proper API for decorations, probably involving a huge dict. fixes #11598 --- src/nvim/api/buffer.c | 253 +++++-- src/nvim/api/private/helpers.c | 83 +-- src/nvim/buffer.c | 431 ----------- src/nvim/buffer_defs.h | 23 +- src/nvim/buffer_updates.c | 48 ++ src/nvim/bufhl_defs.h | 41 -- src/nvim/change.c | 39 +- src/nvim/diff.c | 2 +- src/nvim/edit.c | 18 +- src/nvim/ex_cmds.c | 315 ++------ src/nvim/fold.c | 12 + src/nvim/indent.c | 12 + src/nvim/indent.h | 9 +- src/nvim/map.c | 11 +- src/nvim/map.h | 14 +- src/nvim/mark.c | 22 +- src/nvim/mark.h | 1 + src/nvim/mark_extended.c | 1593 +++++++++++++++++----------------------- src/nvim/mark_extended.h | 293 ++------ src/nvim/mark_extended_defs.h | 59 +- src/nvim/marktree.c | 10 +- src/nvim/ops.c | 161 ++-- src/nvim/screen.c | 59 +- src/nvim/undo.c | 2 +- 24 files changed, 1273 insertions(+), 2238 deletions(-) delete mode 100644 src/nvim/bufhl_defs.h (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index e6f8f73b9d..3106011fe2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -170,6 +170,14 @@ Boolean nvim_buf_attach(uint64_t channel_id, } cb.on_lines = v->data.luaref; v->data.luaref = LUA_NOREF; + } else if (is_lua && strequal("_on_bytes", k.data)) { + // NB: undocumented, untested and incomplete interface! + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, "callback is not a function"); + goto error; + } + cb.on_bytes = v->data.luaref; + v->data.luaref = LUA_NOREF; } else if (is_lua && strequal("on_changedtick", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); @@ -201,6 +209,7 @@ Boolean nvim_buf_attach(uint64_t channel_id, error: // TODO(bfredl): ASAN build should check that the ref table is empty? executor_free_luaref(cb.on_lines); + executor_free_luaref(cb.on_bytes); executor_free_luaref(cb.on_changedtick); executor_free_luaref(cb.on_detach); return false; @@ -639,7 +648,6 @@ void nvim_buf_set_lines(uint64_t channel_id, (linenr_T)(end - 1), MAXLNUM, (long)extra, - false, kExtmarkUndo); changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); @@ -1119,12 +1127,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } - Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); - if (!extmark) { + ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + if (extmark.row < 0) { return rv; } - ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1)); - ADD(rv, INTEGER_OBJ((Integer)extmark->col-1)); + ADD(rv, INTEGER_OBJ((Integer)extmark.row)); + ADD(rv, INTEGER_OBJ((Integer)extmark.col)); return rv; } @@ -1204,43 +1212,39 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, if (limit == 0) { return rv; + } else if (limit < 0) { + limit = INT64_MAX; } bool reverse = false; - linenr_T l_lnum; + int l_row; colnr_T l_col; - if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { + if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) { return rv; } - linenr_T u_lnum; + int u_row; colnr_T u_col; - if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { + if (!extmark_get_index_from_obj(buf, ns_id, end, &u_row, &u_col, err)) { return rv; } - if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) { + if (l_row > u_row || (l_row == u_row && l_col > u_col)) { reverse = true; - linenr_T tmp_lnum = l_lnum; - l_lnum = u_lnum; - u_lnum = tmp_lnum; - colnr_T tmp_col = l_col; - l_col = u_col; - u_col = tmp_col; } - ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, u_lnum, + ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row, u_col, (int64_t)limit, reverse); for (size_t i = 0; i < kv_size(marks); i++) { Array mark = ARRAY_DICT_INIT; - Extmark *extmark = kv_A(marks, i); - ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id)); - ADD(mark, INTEGER_OBJ(extmark->line->lnum-1)); - ADD(mark, INTEGER_OBJ(extmark->col-1)); + ExtmarkInfo extmark = kv_A(marks, i); + ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id)); + ADD(mark, INTEGER_OBJ(extmark.row)); + ADD(mark, INTEGER_OBJ(extmark.col)); ADD(rv, ARRAY_OBJ(mark)); } @@ -1301,17 +1305,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, } uint64_t id_num; - if (id == 0) { - id_num = extmark_free_id_get(buf, (uint64_t)ns_id); - } else if (id > 0) { + if (id >= 0) { id_num = (uint64_t)id; } else { api_set_error(err, kErrorTypeValidation, _("Invalid mark id")); return 0; } - extmark_set(buf, (uint64_t)ns_id, id_num, - (linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo); + id_num = extmark_set(buf, (uint64_t)ns_id, id_num, + (int)line, (colnr_T)col, kExtmarkUndo); return (Integer)id_num; } @@ -1339,7 +1341,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, return false; } - return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo); + return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id); } /// Adds a highlight to buffer. @@ -1373,7 +1375,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer, /// @param[out] err Error details, if any /// @return The ns_id that was used Integer nvim_buf_add_highlight(Buffer buffer, - Integer ns_id, + Integer src_id, String hl_group, Integer line, Integer col_start, @@ -1398,14 +1400,31 @@ Integer nvim_buf_add_highlight(Buffer buffer, col_end = MAXCOL; } + uint64_t ns_id = src2ns(&src_id); + + if (!(0 <= line && line < buf->b_ml.ml_line_count)) { + // safety check, we can't add marks outside the range + return src_id; + } + int hlg_id = 0; if (hl_group.size > 0) { hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + } else { + return src_id; + } + + int end_line = (int)line; + if (col_end == MAXCOL) { + col_end = 0; + end_line++; } - ns_id = bufhl_add_hl(buf, (int)ns_id, hlg_id, (linenr_T)line+1, - (colnr_T)col_start+1, (colnr_T)col_end); - return ns_id; + ns_id = extmark_add_decoration(buf, ns_id, hlg_id, + (int)line, (colnr_T)col_start, + end_line, (colnr_T)col_end, + VIRTTEXT_EMPTY); + return src_id; } /// Clears namespaced objects (highlights, extmarks, virtual text) from @@ -1439,12 +1458,9 @@ void nvim_buf_clear_namespace(Buffer buffer, if (line_end < 0 || line_end > MAXLNUM) { line_end = MAXLNUM; } - - bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end); - extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id, - (linenr_T)line_start+1, - (linenr_T)line_end, - kExtmarkUndo); + extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id), + (int)line_start, 0, + (int)line_end-1, MAXCOL); } /// Clears highlights and virtual text from namespace and range of lines @@ -1467,6 +1483,43 @@ void nvim_buf_clear_highlight(Buffer buffer, nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err); } +static VirtText parse_virt_text(Array chunks, Error *err) +{ + VirtText virt_text = KV_INITIAL_VALUE; + for (size_t i = 0; i < chunks.size; i++) { + if (chunks.items[i].type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); + goto free_exit; + } + Array chunk = chunks.items[i].data.array; + if (chunk.size == 0 || chunk.size > 2 + || chunk.items[0].type != kObjectTypeString + || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { + api_set_error(err, kErrorTypeValidation, + "Chunk is not an array with one or two strings"); + goto free_exit; + } + + String str = chunk.items[0].data.string; + char *text = transstr(str.size > 0 ? str.data : ""); // allocates + + int hl_id = 0; + if (chunk.size == 2) { + String hl = chunk.items[1].data.string; + if (hl.size > 0) { + hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + } + } + kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); + } + + return virt_text; + +free_exit: + clear_virttext(&virt_text); + return virt_text; +} + /// Set the virtual text (annotation) for a buffer line. /// @@ -1496,7 +1549,7 @@ void nvim_buf_clear_highlight(Buffer buffer, /// @param[out] err Error details, if any /// @return The ns_id that was used Integer nvim_buf_set_virtual_text(Buffer buffer, - Integer ns_id, + Integer src_id, Integer line, Array chunks, Dictionary opts, @@ -1518,41 +1571,26 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, return 0; } - VirtText virt_text = KV_INITIAL_VALUE; - for (size_t i = 0; i < chunks.size; i++) { - if (chunks.items[i].type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); - goto free_exit; - } - Array chunk = chunks.items[i].data.array; - if (chunk.size == 0 || chunk.size > 2 - || chunk.items[0].type != kObjectTypeString - || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { - api_set_error(err, kErrorTypeValidation, - "Chunk is not an array with one or two strings"); - goto free_exit; - } - - String str = chunk.items[0].data.string; - char *text = transstr(str.size > 0 ? str.data : ""); // allocates + uint64_t ns_id = src2ns(&src_id); - int hl_id = 0; - if (chunk.size == 2) { - String hl = chunk.items[1].data.string; - if (hl.size > 0) { - hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); - } - } - kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); + VirtText virt_text = parse_virt_text(chunks, err); + if (ERROR_SET(err)) { + return 0; } - ns_id = bufhl_add_virt_text(buf, (int)ns_id, (linenr_T)line+1, - virt_text); - return ns_id; -free_exit: - kv_destroy(virt_text); - return 0; + VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id); + + if (existing) { + clear_virttext(existing); + *existing = virt_text; + return src_id; + } + + extmark_add_decoration(buf, ns_id, 0, + (int)line, 0, -1, -1, + virt_text); + return src_id; } /// Get the virtual text (annotation) for a buffer line. @@ -1570,7 +1608,7 @@ free_exit: /// @param line Line to get the virtual text from (zero-indexed) /// @param[out] err Error details, if any /// @return List of virtual text chunks -Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) +Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err) FUNC_API_SINCE(7) { Array chunks = ARRAY_DICT_INIT; @@ -1580,20 +1618,20 @@ Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) return chunks; } - if (lnum < 0 || lnum >= MAXLNUM) { + if (line < 0 || line >= MAXLNUM) { api_set_error(err, kErrorTypeValidation, "Line number outside range"); return chunks; } - BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, (linenr_T)(lnum + 1), - false); - if (!lineinfo) { + VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0); + + if (!virt_text) { return chunks; } - for (size_t i = 0; i < lineinfo->virt_text.size; i++) { + for (size_t i = 0; i < virt_text->size; i++) { Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &lineinfo->virt_text.items[i]; + VirtTextChunk *vtc = &virt_text->items[i]; ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); if (vtc->hl_id > 0) { ADD(chunk, STRING_OBJ(cstr_to_string( @@ -1605,6 +1643,59 @@ Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) return chunks; } +Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group, + Integer start_row, Integer start_col, + Integer end_row, Integer end_col, + Array virt_text, + Error *err) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + return 0; + } + + + if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) { + api_set_error(err, kErrorTypeValidation, "Line number outside range"); + return 0; + } + + if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) { + api_set_error(err, kErrorTypeValidation, "Column value outside range"); + return 0; + } + if (end_row < 0 || end_col < 0) { + end_row = -1; + end_col = -1; + } + + if (start_row >= buf->b_ml.ml_line_count + || end_row >= buf->b_ml.ml_line_count) { + // safety check, we can't add marks outside the range + return 0; + } + + int hlg_id = 0; + if (hl_group.size > 0) { + hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + } + + VirtText vt = parse_virt_text(virt_text, err); + if (ERROR_SET(err)) { + return 0; + } + + uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id, + (int)start_row, (colnr_T)start_col, + (int)end_row, (colnr_T)end_col, vt); + return (Integer)mark_id; +} + Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; @@ -1626,6 +1717,16 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err) // this exists to debug issues PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); + u_header_T *uhp = NULL; + if (buf->b_u_curhead != NULL) { + uhp = buf->b_u_curhead; + } else if (buf->b_u_newhead) { + uhp = buf->b_u_newhead; + } + if (uhp) { + PUT(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark))); + } + return rv; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 36331eee6e..37e31e0807 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1511,61 +1511,6 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) return mappings; } -// Returns an extmark given an id or a positional index -// If throw == true then an error will be raised if nothing -// was found -// Returns NULL if something went wrong -Extmark *extmark_from_id_or_pos(Buffer buffer, Integer ns, Object id, - Error *err, bool throw) -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - - if (!buf) { - return NULL; - } - - Extmark *extmark = NULL; - if (id.type == kObjectTypeArray) { - if (id.data.array.size != 2) { - api_set_error(err, kErrorTypeValidation, - _("Position must have 2 elements")); - return NULL; - } - linenr_T row = (linenr_T)id.data.array.items[0].data.integer; - colnr_T col = (colnr_T)id.data.array.items[1].data.integer; - if (row < 1 || col < 1) { - if (throw) { - api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0")); - } - return NULL; - } - extmark = extmark_from_pos(buf, (uint64_t)ns, row, col); - } else if (id.type != kObjectTypeInteger) { - if (throw) { - api_set_error(err, kErrorTypeValidation, - _("Mark id must be an int or [row, col]")); - } - return NULL; - } else if (id.data.integer < 0) { - if (throw) { - api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); - } - return NULL; - } else { - extmark = extmark_from_id(buf, - (uint64_t)ns, - (uint64_t)id.data.integer); - } - - if (!extmark) { - if (throw) { - api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist")); - } - return NULL; - } - return extmark; -} - // Is the Namespace in use? bool ns_initialized(uint64_t ns) { @@ -1584,29 +1529,29 @@ bool ns_initialized(uint64_t ns) /// @param[out] colnr extmark column /// /// @return true if the extmark was found, else false -bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T - *lnum, colnr_T *colnr, Error *err) +bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int + *row, colnr_T *col, Error *err) { // Check if it is mark id if (obj.type == kObjectTypeInteger) { Integer id = obj.data.integer; if (id == 0) { - *lnum = 1; - *colnr = 1; + *row = 0; + *col = 0; return true; } else if (id == -1) { - *lnum = MAXLNUM; - *colnr = MAXCOL; + *row = MAXLNUM; + *col = MAXCOL; return true; } else if (id < 0) { api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); return false; } - Extmark *extmark = extmark_from_id(buf, (uint64_t)ns, (uint64_t)id); - if (extmark) { - *lnum = extmark->line->lnum; - *colnr = extmark->col; + ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + if (extmark.row >= 0) { + *row = extmark.row; + *col = extmark.col; return true; } else { api_set_error(err, kErrorTypeValidation, _("No mark with requested id")); @@ -1623,10 +1568,10 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T _("Position must have 2 integer elements")); return false; } - Integer line = pos.items[0].data.integer; - Integer col = pos.items[1].data.integer; - *lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM); - *colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL); + Integer pos_row = pos.items[0].data.integer; + Integer pos_col = pos.items[1].data.integer; + *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM); + *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL); return true; } else { api_set_error(err, kErrorTypeValidation, diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 33ffff39f6..837fcb5cc1 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -81,12 +81,6 @@ #include "nvim/os/input.h" #include "nvim/buffer_updates.h" -typedef enum { - kBLSUnchanged = 0, - kBLSChanged = 1, - kBLSDeleted = 2, -} BufhlLineStatus; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer.c.generated.h" #endif @@ -818,7 +812,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) uc_clear(&buf->b_ucmds); // clear local user commands buf_delete_signs(buf, (char_u *)"*"); // delete any signs extmark_free_all(buf); // delete any extmarks - bufhl_clear_all(buf); // delete any highligts map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs XFREE_CLEAR(buf->b_start_fenc); @@ -5342,430 +5335,6 @@ int buf_signcols(buf_T *buf) return buf->b_signcols; } -// bufhl: plugin highlights associated with a buffer - -/// Get reference to line in kbtree_t -/// -/// @param b the three -/// @param line the linenumber to lookup -/// @param put if true, put a new line when not found -/// if false, return NULL when not found -BufhlLine *bufhl_tree_ref(BufhlInfo *b, linenr_T line, bool put) -{ - BufhlLine t = BUFHLLINE_INIT(line); - - // kp_put() only works if key is absent, try get first - BufhlLine **pp = kb_get(bufhl, b, &t); - if (pp) { - return *pp; - } else if (!put) { - return NULL; - } - - BufhlLine *p = xmalloc(sizeof(*p)); - *p = (BufhlLine)BUFHLLINE_INIT(line); - kb_put(bufhl, b, p); - return p; -} - -/// Adds a highlight to buffer. -/// -/// Unlike matchaddpos() highlights follow changes to line numbering (as lines -/// are inserted/removed above the highlighted line), like signs and marks do. -/// -/// When called with "src_id" set to 0, a unique source id is generated and -/// returned. Succesive calls can pass it in as "src_id" to add new highlights -/// to the same source group. All highlights in the same group can be cleared -/// at once. If the highlight never will be manually deleted pass in -1 for -/// "src_id" -/// -/// if "hl_id" or "lnum" is invalid no highlight is added, but a new src_id -/// is still returned. -/// -/// @param buf The buffer to add highlights to -/// @param src_id src_id to use or 0 to use a new src_id group, -/// or -1 for ungrouped highlight. -/// @param hl_id Id of the highlight group to use -/// @param lnum The line to highlight -/// @param col_start First column to highlight -/// @param col_end The last column to highlight, -/// or -1 to highlight to end of line -/// @return The src_id that was used -int bufhl_add_hl(buf_T *buf, - int src_id, - int hl_id, - linenr_T lnum, - colnr_T col_start, - colnr_T col_end) -{ - if (src_id == 0) { - src_id = (int)nvim_create_namespace((String)STRING_INIT); - } - if (hl_id <= 0) { - // no highlight group or invalid line, just return src_id - return src_id; - } - - BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true); - - BufhlItem *hlentry = kv_pushp(lineinfo->items); - hlentry->src_id = src_id; - hlentry->hl_id = hl_id; - hlentry->start = col_start; - hlentry->stop = col_end; - - if (0 < lnum && lnum <= buf->b_ml.ml_line_count) { - redraw_buf_line_later(buf, lnum); - } - return src_id; -} - -/// Add highlighting to a buffer, bounded by two cursor positions, -/// with an offset. -/// -/// @param buf Buffer to add highlights to -/// @param src_id src_id to use or 0 to use a new src_id group, -/// or -1 for ungrouped highlight. -/// @param hl_id Highlight group id -/// @param pos_start Cursor position to start the hightlighting at -/// @param pos_end Cursor position to end the highlighting at -/// @param offset Move the whole highlighting this many columns to the right -void bufhl_add_hl_pos_offset(buf_T *buf, - int src_id, - int hl_id, - lpos_T pos_start, - lpos_T pos_end, - colnr_T offset) -{ - colnr_T hl_start = 0; - colnr_T hl_end = 0; - - for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { - if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - hl_start = offset; - hl_end = MAXCOL; - } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { - hl_start = pos_start.col + offset + 1; - hl_end = MAXCOL; - } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = offset; - hl_end = pos_end.col + offset; - } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { - hl_start = pos_start.col + offset + 1; - hl_end = pos_end.col + offset; - } - (void)bufhl_add_hl(buf, src_id, hl_id, lnum, hl_start, hl_end); - } -} - -int bufhl_add_virt_text(buf_T *buf, - int src_id, - linenr_T lnum, - VirtText virt_text) -{ - if (src_id == 0) { - src_id = (int)nvim_create_namespace((String)STRING_INIT); - } - - BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, true); - - bufhl_clear_virttext(&lineinfo->virt_text); - if (kv_size(virt_text) > 0) { - lineinfo->virt_text_src = src_id; - lineinfo->virt_text = virt_text; - } else { - lineinfo->virt_text_src = 0; - // currently not needed, but allow a future caller with - // 0 size and non-zero capacity - kv_destroy(virt_text); - } - - if (0 < lnum && lnum <= buf->b_ml.ml_line_count) { - redraw_buf_line_later(buf, lnum); - } - return src_id; -} - -static void bufhl_clear_virttext(VirtText *text) -{ - for (size_t i = 0; i < kv_size(*text); i++) { - xfree(kv_A(*text, i).text); - } - kv_destroy(*text); - *text = (VirtText)KV_INITIAL_VALUE; -} - -/// Clear bufhl highlights from a given source group and range of lines. -/// -/// @param buf The buffer to remove highlights from -/// @param src_id highlight source group to clear, or -1 to clear all groups. -/// @param line_start first line to clear -/// @param line_end last line to clear or MAXLNUM to clear to end of file. -void bufhl_clear_line_range(buf_T *buf, - int src_id, - linenr_T line_start, - linenr_T line_end) -{ - // TODO(bfredl): implement kb_itr_interval to jump directly to the first line - kbitr_t(bufhl) itr; - BufhlLine *l, t = BUFHLLINE_INIT(line_start); - if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) { - kb_itr_next(bufhl, &buf->b_bufhl_info, &itr); - } - for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) { - l = kb_itr_key(&itr); - linenr_T line = l->line; - if (line > line_end) { - break; - } - if (line_start <= line) { - BufhlLineStatus status = bufhl_clear_line(l, src_id, line); - if (status != kBLSUnchanged) { - redraw_buf_line_later(buf, line); - } - if (status == kBLSDeleted) { - kb_del_itr(bufhl, &buf->b_bufhl_info, &itr); - xfree(l); - } - } - } -} - -/// Clear bufhl highlights from a given source group and given line -/// -/// @param bufhl_info The highlight info for the buffer -/// @param src_id Highlight source group to clear, or -1 to clear all groups. -/// @param lnum Linenr where the highlight should be cleared -static BufhlLineStatus bufhl_clear_line(BufhlLine *lineinfo, int src_id, - linenr_T lnum) -{ - BufhlLineStatus changed = kBLSUnchanged; - size_t oldsize = kv_size(lineinfo->items); - if (src_id < 0) { - kv_size(lineinfo->items) = 0; - } else { - size_t newidx = 0; - for (size_t i = 0; i < kv_size(lineinfo->items); i++) { - if (kv_A(lineinfo->items, i).src_id != src_id) { - if (i != newidx) { - kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i); - } - newidx++; - } - } - kv_size(lineinfo->items) = newidx; - } - if (kv_size(lineinfo->items) != oldsize) { - changed = kBLSChanged; - } - - if (kv_size(lineinfo->virt_text) != 0 - && (src_id < 0 || src_id == lineinfo->virt_text_src)) { - bufhl_clear_virttext(&lineinfo->virt_text); - lineinfo->virt_text_src = 0; - changed = kBLSChanged; - } - - if (kv_size(lineinfo->items) == 0 && kv_size(lineinfo->virt_text) == 0) { - kv_destroy(lineinfo->items); - return kBLSDeleted; - } - return changed; -} - - -/// Remove all highlights and free the highlight data -void bufhl_clear_all(buf_T *buf) -{ - bufhl_clear_line_range(buf, -1, 1, MAXLNUM); - kb_destroy(bufhl, (&buf->b_bufhl_info)); - kb_init(&buf->b_bufhl_info); - kv_destroy(buf->b_bufhl_move_space); - kv_init(buf->b_bufhl_move_space); -} - -/// Adjust a placed highlight for inserted/deleted lines. -void bufhl_mark_adjust(buf_T* buf, - linenr_T line1, - linenr_T line2, - long amount, - long amount_after, - bool end_temp) -{ - kbitr_t(bufhl) itr; - BufhlLine *l, t = BUFHLLINE_INIT(line1); - if (end_temp && amount < 0) { - // Move all items from b_bufhl_move_space to the btree. - for (size_t i = 0; i < kv_size(buf->b_bufhl_move_space); i++) { - l = kv_A(buf->b_bufhl_move_space, i); - l->line += amount; - kb_put(bufhl, &buf->b_bufhl_info, l); - } - kv_size(buf->b_bufhl_move_space) = 0; - return; - } - - if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) { - kb_itr_next(bufhl, &buf->b_bufhl_info, &itr); - } - for (; kb_itr_valid(&itr); kb_itr_next(bufhl, &buf->b_bufhl_info, &itr)) { - l = kb_itr_key(&itr); - if (l->line >= line1 && l->line <= line2) { - if (end_temp && amount > 0) { - kb_del_itr(bufhl, &buf->b_bufhl_info, &itr); - kv_push(buf->b_bufhl_move_space, l); - } - if (amount == MAXLNUM) { - if (bufhl_clear_line(l, -1, l->line) == kBLSDeleted) { - kb_del_itr(bufhl, &buf->b_bufhl_info, &itr); - xfree(l); - } else { - assert(false); - } - } else { - l->line += amount; - } - } else if (l->line > line2) { - if (amount_after == 0) { - break; - } - l->line += amount_after; - } - } -} - -/// Adjust a placed highlight for column changes and joined/broken lines -bool bufhl_mark_col_adjust(buf_T *buf, - linenr_T lnum, - colnr_T mincol, - long lnum_amount, - long col_amount) -{ - bool moved = false; - BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false); - if (!lineinfo) { - // Old line empty, nothing to do - return false; - } - // Create the new line below only if needed - BufhlLine *lineinfo2 = NULL; - - colnr_T delcol = MAXCOL; - if (lnum_amount == 0 && col_amount < 0) { - delcol = mincol+(int)col_amount; - } - - size_t newidx = 0; - for (size_t i = 0; i < kv_size(lineinfo->items); i++) { - BufhlItem *item = &kv_A(lineinfo->items, i); - bool delete = false; - if (item->start >= mincol) { - moved = true; - item->start += (int)col_amount; - if (item->stop < MAXCOL) { - item->stop += (int)col_amount; - } - if (lnum_amount != 0) { - if (lineinfo2 == NULL) { - lineinfo2 = bufhl_tree_ref(&buf->b_bufhl_info, - lnum+lnum_amount, true); - } - kv_push(lineinfo2->items, *item); - delete = true; - } - } else { - if (item->start >= delcol) { - moved = true; - item->start = delcol; - } - if (item->stop == MAXCOL || item->stop+1 >= mincol) { - if (item->stop == MAXCOL) { - if (delcol < MAXCOL - && delcol > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { - delete = true; - } - } else { - moved = true; - item->stop += (int)col_amount; - } - assert(lnum_amount >= 0); - if (lnum_amount > 0) { - item->stop = MAXCOL; - } - } else if (item->stop+1 >= delcol) { - moved = true; - item->stop = delcol-1; - } - // we covered the entire range with a visual delete or something - if (item->stop < item->start) { - delete = true; - } - } - - if (!delete) { - if (i != newidx) { - kv_A(lineinfo->items, newidx) = kv_A(lineinfo->items, i); - } - newidx++; - } - } - kv_size(lineinfo->items) = newidx; - - return moved; -} - - -/// Get highlights to display at a specific line -/// -/// @param buf The buffer handle -/// @param lnum The line number -/// @param[out] info The highligts for the line -/// @return true if there was highlights to display -bool bufhl_start_line(buf_T *buf, linenr_T lnum, BufhlLineInfo *info) -{ - BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, lnum, false); - if (!lineinfo) { - return false; - } - info->valid_to = -1; - info->line = lineinfo; - return true; -} - -/// get highlighting at column col -/// -/// It is is assumed this will be called with -/// non-decreasing column nrs, so that it is -/// possible to only recalculate highlights -/// at endpoints. -/// -/// @param info The info returned by bufhl_start_line -/// @param col The column to get the attr for -/// @return The highilight attr to display at the column -int bufhl_get_attr(BufhlLineInfo *info, colnr_T col) -{ - if (col <= info->valid_to) { - return info->current; - } - int attr = 0; - info->valid_to = MAXCOL; - for (size_t i = 0; i < kv_size(info->line->items); i++) { - BufhlItem entry = kv_A(info->line->items, i); - if (entry.start <= col && col <= entry.stop) { - int entry_attr = syn_id2attr(entry.hl_id); - attr = hl_combine_attr(attr, entry_attr); - if (entry.stop < info->valid_to) { - info->valid_to = entry.stop; - } - } else if (col < entry.start && entry.start-1 < info->valid_to) { - info->valid_to = entry.start-1; - } - } - info->current = attr; - return attr; -} - - /* * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. */ diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 265fc05f67..a0379740b6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -42,6 +42,8 @@ typedef struct { #include "nvim/map.h" // for kvec #include "nvim/lib/kvec.h" +// for marktree +#include "nvim/marktree.h" #define GETFILE_SUCCESS(x) ((x) <= 0) #define MODIFIABLE(buf) (buf->b_p_ma) @@ -109,15 +111,10 @@ typedef uint16_t disptick_T; // display tick type #include "nvim/syntax_defs.h" // for signlist_T #include "nvim/sign_defs.h" -// for bufhl_*_T -#include "nvim/bufhl_defs.h" #include "nvim/os/fs_defs.h" // for FileID #include "nvim/terminal.h" // for Terminal -#include "nvim/lib/kbtree.h" -#include "nvim/mark_extended.h" - /* * The taggy struct is used to store the information about a :tag command. */ @@ -461,11 +458,15 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem; typedef struct { LuaRef on_lines; + LuaRef on_bytes; LuaRef on_changedtick; LuaRef on_detach; bool utf_sizes; } BufUpdateCallbacks; -#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false } +#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, false } + +EXTERN int curbuf_splice_pending INIT(= 0); #define BUF_HAS_QF_ENTRY 1 #define BUF_HAS_LL_ENTRY 2 @@ -804,13 +805,9 @@ struct file_buffer { int b_mapped_ctrl_c; // modes where CTRL-C is mapped - BufhlInfo b_bufhl_info; // buffer stored highlights - - kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights - - PMap(uint64_t) *b_extmark_ns; // extmark namespaces - kbtree_t(extmarklines) b_extlines; // extmarks - kvec_t(ExtmarkLine *) b_extmark_move_space; // temp space for extmarks + MarkTree b_marktree[1]; + Map(uint64_t, ExtmarkItem) *b_extmark_index; + Map(uint64_t, ExtmarkNs) *b_extmark_ns; // extmark namespaces // array of channel_id:s which have asked to receive updates for this // buffer. diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index d12527d6ac..80780a3aa3 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -281,6 +281,54 @@ void buf_updates_send_changes(buf_T *buf, kv_size(buf->update_callbacks) = j; } +void buf_updates_send_splice(buf_T *buf, + linenr_T start_line, colnr_T start_col, + linenr_T oldextent_line, colnr_T oldextent_col, + linenr_T newextent_line, colnr_T newextent_col) +{ + if (!buf_updates_active(buf)) { + return; + } + + // notify each of the active callbakcs + size_t j = 0; + for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { + BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); + bool keep = true; + if (cb.on_bytes != LUA_NOREF) { + Array args = ARRAY_DICT_INIT; + Object items[8]; + args.size = 8; + args.items = items; + + // the first argument is always the buffer handle + args.items[0] = BUFFER_OBJ(buf->handle); + + // next argument is b:changedtick + args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); + + args.items[2] = INTEGER_OBJ(start_line); + args.items[3] = INTEGER_OBJ(start_col); + args.items[4] = INTEGER_OBJ(oldextent_line); + args.items[5] = INTEGER_OBJ(oldextent_col); + args.items[6] = INTEGER_OBJ(newextent_line); + args.items[7] = INTEGER_OBJ(newextent_col); + + textlock++; + Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL); + textlock--; + + if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + free_update_callbacks(cb); + keep = false; + } + } + if (keep) { + kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); + } + } + kv_size(buf->update_callbacks) = j; +} void buf_updates_changedtick(buf_T *buf) { // notify each of the active channels diff --git a/src/nvim/bufhl_defs.h b/src/nvim/bufhl_defs.h deleted file mode 100644 index d0fb40ab88..0000000000 --- a/src/nvim/bufhl_defs.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef NVIM_BUFHL_DEFS_H -#define NVIM_BUFHL_DEFS_H - -#include "nvim/pos.h" -#include "nvim/lib/kvec.h" -#include "nvim/lib/kbtree.h" - -// bufhl: buffer specific highlighting - -typedef struct { - int src_id; - int hl_id; // highlight group - colnr_T start; // first column to highlight - colnr_T stop; // last column to highlight -} BufhlItem; - -typedef struct { - char *text; - int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; - -typedef struct { - linenr_T line; - kvec_t(BufhlItem) items; - int virt_text_src; - VirtText virt_text; -} BufhlLine; -#define BUFHLLINE_INIT(l) { l, KV_INITIAL_VALUE, 0, KV_INITIAL_VALUE } - -typedef struct { - BufhlLine *line; - int current; - colnr_T valid_to; -} BufhlLineInfo; - -#define BUFHL_CMP(a, b) ((int)(((a)->line - (b)->line))) -KBTREE_INIT(bufhl, BufhlLine *, BUFHL_CMP, 10) // -V512 -typedef kbtree_t(bufhl) BufhlInfo; -#endif // NVIM_BUFHL_DEFS_H diff --git a/src/nvim/change.c b/src/nvim/change.c index 05cacaf2c2..7eb6ea7328 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -363,15 +363,10 @@ void changed_bytes(linenr_T lnum, colnr_T col) /// /// Like changed_bytes() but also adjust extmark for "added" bytes. /// When "added" is negative text was deleted. -static void inserted_bytes(linenr_T lnum, colnr_T col, int added) +static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new) { - if (added > 0) { - extmark_col_adjust(curbuf, lnum, col+1, 0, added, kExtmarkUndo); - } else if (added < 0) { - // TODO(bfredl): next revision of extmarks should handle both these - // with the same entry point. Also with more sane params.. - extmark_col_adjust_delete(curbuf, lnum, col+2, - col+(-added)+1, kExtmarkUndo, 0); + if (curbuf_splice_pending == 0) { + extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo); } changed_bytes(lnum, col); @@ -391,7 +386,10 @@ void appended_lines_mark(linenr_T lnum, long count) // Skip mark_adjust when adding a line after the last one, there can't // be marks there. But it's still needed in diff mode. if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, kExtmarkUndo); + } else { + extmark_adjust(curbuf, lnum + 1, (linenr_T)MAXLNUM, count, 0L, + kExtmarkUndo); } changed_lines(lnum + 1, 0, lnum + 1, count, true); } @@ -409,7 +407,7 @@ void deleted_lines(linenr_T lnum, long count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false, + mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, kExtmarkUndo); changed_lines(lnum, 0, lnum + count, -count, true); } @@ -648,7 +646,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying - inserted_bytes(lnum, (colnr_T)col, (int)(newlen - oldlen)); + inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen); // If we're in Insert or Replace mode and 'showmatch' is set, then briefly // show the match for right parens and braces. @@ -694,7 +692,7 @@ void ins_str(char_u *s) assert(bytes >= 0); memmove(newp + col + newlen, oldp + col, (size_t)bytes); ml_replace(lnum, newp, false); - inserted_bytes(lnum, col, newlen); + inserted_bytes(lnum, col, 0, newlen); curwin->w_cursor.col += newlen; } @@ -815,7 +813,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } // mark the buffer as changed and prepare for displaying - inserted_bytes(lnum, col, -count); + inserted_bytes(lnum, col, count, 0); return OK; } @@ -1583,6 +1581,7 @@ int open_line( end_comment_pending = NUL; // turns out there was no leader } + curbuf_splice_pending++; old_cursor = curwin->w_cursor; if (dir == BACKWARD) { curwin->w_cursor.lnum--; @@ -1597,7 +1596,7 @@ int open_line( // be marks there. But still needed in diff mode. if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false, + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkUndo); } did_append = true; @@ -1638,7 +1637,7 @@ int open_line( // it. It gets restored at the function end. curbuf->b_p_pi = true; } else { - (void)set_indent(newindent, SIN_INSERT); + (void)set_indent(newindent, SIN_INSERT|SIN_NOMARK); } less_cols -= curwin->w_cursor.col; @@ -1687,12 +1686,13 @@ int open_line( if (flags & OPENLINE_MARKFIX) { mark_col_adjust(curwin->w_cursor.lnum, curwin->w_cursor.col + less_cols_off, - 1L, (long)-less_cols, 0, kExtmarkNOOP); + 1L, (long)-less_cols, 0); } // Always move extmarks - Here we move only the line where the // cursor is, the previous mark_adjust takes care of the lines after - extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols, - kExtmarkUndo); + int cols_added = mincol-1+less_cols_off-less_cols; + extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off, + 1, cols_added, kExtmarkUndo); } else { changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); } @@ -1704,7 +1704,10 @@ int open_line( } if (did_append) { changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); + extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, + 0, 0, 0, 1, 0, kExtmarkUndo); } + curbuf_splice_pending--; curwin->w_cursor.col = newcol; curwin->w_cursor.coladd = 0; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index dccde01d29..c31adc01fd 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2711,7 +2711,7 @@ void ex_diffgetput(exarg_T *eap) // Adjust marks. This will change the following entries! if (added != 0) { - mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false, + mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, kExtmarkUndo); if (curwin->w_cursor.lnum >= lnum) { // Adjust the cursor position if it's in/after the changed diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d59924cd08..cdb4b127da 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1826,11 +1826,14 @@ change_indent ( /* We only put back the new line up to the cursor */ new_line[curwin->w_cursor.col] = NUL; + int new_col = curwin->w_cursor.col; // Put back original line ml_replace(curwin->w_cursor.lnum, orig_line, false); curwin->w_cursor.col = orig_col; + curbuf_splice_pending++; + /* Backspace from cursor to start of line */ backspace_until_column(0); @@ -1838,13 +1841,16 @@ change_indent ( ins_bytes(new_line); xfree(new_line); - } - // change_indent seems to bec called twice, this combination only triggers - // once for both calls - if (new_cursor_col - vcol != 0) { - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount, - kExtmarkUndo); + curbuf_splice_pending--; + + // TODO(bfredl): test for crazy edge cases, like we stand on a TAB or + // something? does this even do the right text change then? + int delta = orig_col - new_col; + extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col, + 0, delta < 0 ? -delta : 0, + 0, delta > 0 ? delta : 0, + kExtmarkUndo); } } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 85048427b1..53caaa6a67 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -14,6 +14,7 @@ #include #include "nvim/api/private/defs.h" +#include "nvim/api/vim.h" #include "nvim/api/buffer.h" #include "nvim/log.h" #include "nvim/vim.h" @@ -659,10 +660,10 @@ void ex_sort(exarg_T *eap) deleted = (long)(count - (lnum - eap->line2)); if (deleted > 0) { mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted, - false, kExtmarkUndo); + kExtmarkUndo); msgmore(-deleted); } else if (deleted < 0) { - mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo); + mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, kExtmarkUndo); } if (change_occurred || deleted != 0) { changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true); @@ -875,12 +876,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) * their final destination at the new text position -- webb */ last_line = curbuf->b_ml.ml_line_count; - mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo); - extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo, - true); + mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP); changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false); if (dest >= line2) { - mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo); + mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { foldMoveRange(&win->w_folds, line1, line2, dest); @@ -889,8 +888,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) curbuf->b_op_start.lnum = dest - num_lines + 1; curbuf->b_op_end.lnum = dest; } else { - mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false, - kExtmarkNoUndo); + mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); @@ -901,9 +899,15 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) } curbuf->b_op_start.col = curbuf->b_op_end.col = 0; mark_adjust_nofold(last_line - num_lines + 1, last_line, - -(last_line - dest - extra), 0L, true, kExtmarkNoUndo); + -(last_line - dest - extra), 0L, kExtmarkNOOP); + + // extmarks are handled separately + int size = line2-line1+1; + int off = dest >= line2 ? -size : 0; + extmark_move_region(curbuf, line1-1, 0, + line2-line1+1, 0, + dest+off, 0, kExtmarkUndo); - u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra); changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); // send update regarding the new lines that were added @@ -1285,16 +1289,19 @@ static void do_filter( if (do_in) { if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) { + // TODO(bfredl): Currently not active for extmarks. What would we + // do if columns don't match, assume added/deleted bytes at the + // end of each line? if (read_linecount >= linecount) { // move all marks from old lines to new lines - mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo); + mark_adjust(line1, line2, linecount, 0L, kExtmarkNOOP); } else { // move marks from old lines to new lines, delete marks // that are in deleted lines - mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false, - kExtmarkUndo); - mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false, - kExtmarkUndo); + mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, + kExtmarkNOOP); + mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, + kExtmarkNOOP); } } @@ -3222,186 +3229,6 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, return cmd; } -static void extmark_move_regmatch_single(lpos_T startpos, - lpos_T endpos, - linenr_T lnum, - int sublen) -{ - colnr_T mincol; - colnr_T endcol; - colnr_T col_amount; - - mincol = startpos.col + 1; - endcol = endpos.col + 1; - - // There are cases such as :s/^/x/ where this happens - // a delete is simply not required. - if (mincol + 1 <= endcol) { - extmark_col_adjust_delete(curbuf, - lnum, mincol + 1, endcol, kExtmarkUndo, 0); - } - - // Insert, sublen seems to be the value we need but + 1... - col_amount = sublen - 1; - extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo); -} - -static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i) -{ - colnr_T mincol; - mincol = s.startpos.col + 1; - - linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; - colnr_T n_after_newline_in_pat = s.endpos.col; - colnr_T n_before_newline_in_pat = mincol - s.cm_start.col; - long n_after_newline_in_sub; - if (!s.newline_in_sub) { - n_after_newline_in_sub = s.cm_end.col - s.cm_start.col; - } else { - n_after_newline_in_sub = s.cm_end.col; - } - - if (s.newline_in_pat && !s.newline_in_sub) { - // -- Delete Pattern -- - // 1. Move marks in the pattern - mincol = s.startpos.col + 1; - linenr_T u_lnum = n_u_lnum; - assert(n_u_lnum == u_lnum); - extmark_copy_and_place(curbuf, - s.lnum, mincol, - u_lnum, n_after_newline_in_pat, - s.lnum, mincol, - kExtmarkUndo, true, NULL); - // 2. Move marks on last newline - mincol = mincol - n_before_newline_in_pat; - extmark_col_adjust(curbuf, - u_lnum, - n_after_newline_in_pat + 1, - -s.newline_in_pat, - mincol - n_after_newline_in_pat, - kExtmarkUndo); - // Take care of the lines after - extmark_adjust(curbuf, - u_lnum, - u_lnum, - MAXLNUM, - -s.newline_in_pat, - kExtmarkUndo, - false); - // 1. first insert the text in the substitutaion - extmark_col_adjust(curbuf, - s.lnum, - mincol + 1, - s.newline_in_sub, - n_after_newline_in_sub, - kExtmarkUndo); - - } else { - // The data in sub_obj is as if the substituons above had already taken - // place. For our extmarks they haven't as we work from the bottom of the - // buffer up. Readjust the data. - n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; - n_u_lnum = n_u_lnum - s.lnum_added; - - // adjusted = L - (i-1)N - // where L = lnum value, N= lnum_added and i = iteration - linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added); - linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum; - assert(s.startpos.lnum == 0); - - mincol = s.startpos.col + 1; - - if (!s.newline_in_pat && s.newline_in_sub) { - // -- Delete Pattern -- - // 1. Move marks in the pattern - extmark_col_adjust_delete(curbuf, - a_l_lnum, - mincol + 1, - s.endpos.col + 1, - kExtmarkUndo, - s.eol); - - extmark_adjust(curbuf, - a_u_lnum + 1, - MAXLNUM, - (long)s.newline_in_sub, - 0, - kExtmarkUndo, - false); - // 3. Insert - extmark_col_adjust(curbuf, - a_l_lnum, - mincol, - s.newline_in_sub, - (long)-mincol + 1 + n_after_newline_in_sub, - kExtmarkUndo); - } else if (s.newline_in_pat && s.newline_in_sub) { - if (s.lnum_added >= 0) { - linenr_T u_col = n_after_newline_in_pat == 0 - ? 1 : n_after_newline_in_pat; - extmark_copy_and_place(curbuf, - a_l_lnum, mincol, - a_u_lnum, u_col, - a_l_lnum, mincol, - kExtmarkUndo, true, NULL); - // 2. Move marks on last newline - mincol = mincol - (colnr_T)n_before_newline_in_pat; - extmark_col_adjust(curbuf, - a_u_lnum, - (colnr_T)(n_after_newline_in_pat + 1), - -s.newline_in_pat, - mincol - n_after_newline_in_pat, - kExtmarkUndo); - // TODO(timeyyy): nothing to do here if lnum_added = 0 - extmark_adjust(curbuf, - a_u_lnum + 1, - MAXLNUM, - (long)s.lnum_added, - 0, - kExtmarkUndo, - false); - - extmark_col_adjust(curbuf, - a_l_lnum, - mincol + 1, - s.newline_in_sub, - (long)-mincol + n_after_newline_in_sub, - kExtmarkUndo); - } else { - mincol = s.startpos.col + 1; - a_l_lnum = s.startpos.lnum + 1; - a_u_lnum = s.endpos.lnum + 1; - extmark_copy_and_place(curbuf, - a_l_lnum, mincol, - a_u_lnum, n_after_newline_in_pat, - a_l_lnum, mincol, - kExtmarkUndo, true, NULL); - // 2. Move marks on last newline - mincol = mincol - (colnr_T)n_before_newline_in_pat; - extmark_col_adjust(curbuf, - a_u_lnum, - (colnr_T)(n_after_newline_in_pat + 1), - -s.newline_in_pat, - mincol - n_after_newline_in_pat, - kExtmarkUndo); - extmark_adjust(curbuf, - a_u_lnum, - a_u_lnum, - MAXLNUM, - s.lnum_added, - kExtmarkUndo, - false); - // 3. Insert - extmark_col_adjust(curbuf, - a_l_lnum, - mincol + 1, - s.newline_in_sub, - (long)-mincol + n_after_newline_in_sub, - kExtmarkUndo); - } - } - } -} /// Perform a substitution from line eap->line1 to line eap->line2 using the /// command pointed to by eap->arg which should be of the form: @@ -3449,11 +3276,6 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int save_ma = 0; int save_b_changed = curbuf->b_changed; bool preview = (State & CMDPREVIEW); - extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE; - extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE; - linenr_T no_of_lines_changed = 0; - linenr_T newline_in_pat = 0; - linenr_T newline_in_sub = 0; // inccommand tests fail without this check if (!preview) { @@ -4010,9 +3832,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, goto skip; } + // 3. Substitute the string. During 'inccommand' preview only do this if // there is a replace pattern. if (!preview || has_second_delim) { + long lnum_start = lnum; // save the start lnum save_ma = curbuf->b_p_ma; if (subflags.do_count) { // prevent accidentally changing the buffer by a function @@ -4060,7 +3884,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, // Finally, at this point we can know where the match actually will // start in the new text - current_match.start.col = new_end - new_start; + int start_col = new_end - new_start; + current_match.start.col = start_col; (void)vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, @@ -4092,8 +3917,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, *p1 = NUL; // truncate up to the CR ml_append(lnum - 1, new_start, (colnr_T)(p1 - new_start + 1), false); - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false, - kExtmarkNOOP); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP); if (subflags.do_ask) { appended_lines(lnum - 1, 1L); @@ -4117,45 +3941,21 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, p1 += (*mb_ptr2len)(p1) - 1; } } - current_match.end.col = STRLEN(new_start); + size_t new_endcol = STRLEN(new_start); + current_match.end.col = new_endcol; current_match.end.lnum = lnum; - } - - // Adjust extmarks, by delete and then insert - if (!preview) { - newline_in_pat = (regmatch.endpos[0].lnum - - regmatch.startpos[0].lnum); - newline_in_sub = current_match.end.lnum - current_match.start.lnum; - if (newline_in_pat || newline_in_sub) { - ExtmarkSubMulti sub_multi; - no_of_lines_changed = newline_in_sub - newline_in_pat; - - sub_multi.newline_in_pat = newline_in_pat; - sub_multi.newline_in_sub = newline_in_sub; - sub_multi.lnum = lnum; - sub_multi.lnum_added = no_of_lines_changed; - sub_multi.cm_start = current_match.start; - sub_multi.cm_end = current_match.end; - - sub_multi.startpos = regmatch.startpos[0]; - sub_multi.endpos = regmatch.endpos[0]; - sub_multi.eol = extmark_eol_col(curbuf, lnum); - - kv_push(extmark_sub_multi, sub_multi); - // Collect information required for moving extmarks WITHOUT \n, \r - } else { - no_of_lines_changed = 0; - - if (regmatch.startpos[0].col != -1) { - ExtmarkSubSingle sub_single; - sub_single.sublen = sublen; - sub_single.lnum = lnum; - sub_single.startpos = regmatch.startpos[0]; - sub_single.endpos = regmatch.endpos[0]; - kv_push(extmark_sub_single, sub_single); + // TODO(bfredl): adjust in preview, because decorations? + // this has some robustness issues, will look into later. + if (!preview) { + lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0]; + int matchcols = end.col - ((end.lnum == start.lnum) + ? start.col : 0); + int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0); + extmark_splice(curbuf, lnum_start-1, start_col, + end.lnum-start.lnum, matchcols, + lnum-lnum_start, subcols, kExtmarkUndo); } - } } @@ -4225,7 +4025,7 @@ skip: ml_delete(lnum, false); } mark_adjust(lnum, lnum + nmatch_tl - 1, - (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP); + (long)MAXLNUM, -nmatch_tl, kExtmarkNOOP); if (subflags.do_ask) { deleted_lines(lnum, nmatch_tl); } @@ -4387,7 +4187,7 @@ skip: } else if (*p_icm != NUL && pat != NULL) { if (pre_src_id == 0) { // Get a unique new src_id, saved in a static - pre_src_id = bufhl_add_hl(NULL, 0, -1, 0, 0, 0); + pre_src_id = (int)nvim_create_namespace((String)STRING_INIT); } if (pre_hl_id == 0) { pre_hl_id = syn_check_group((char_u *)S_LEN("Substitute")); @@ -4396,40 +4196,11 @@ skip: preview_buf = show_sub(eap, old_cursor, &preview_lines, pre_hl_id, pre_src_id); if (subsize > 0) { - bufhl_clear_line_range(orig_buf, pre_src_id, eap->line1, - kv_last(preview_lines.subresults).end.lnum); + extmark_clear(orig_buf, pre_src_id, eap->line1-1, 0, + kv_last(preview_lines.subresults).end.lnum-1, MAXCOL); } } } - if (newline_in_pat || newline_in_sub) { - long n = (long)kv_size(extmark_sub_multi); - ExtmarkSubMulti sub_multi; - if (no_of_lines_changed < 0) { - for (i = 0; i < n; i++) { - sub_multi = kv_A(extmark_sub_multi, i); - extmark_move_regmatch_multi(sub_multi, i); - } - } else { - // Move extmarks in reverse order to avoid moving marks we just moved... - for (i = 0; i < n; i++) { - sub_multi = kv_Z(extmark_sub_multi, i); - extmark_move_regmatch_multi(sub_multi, n - i); - } - } - kv_destroy(extmark_sub_multi); - } else { - long n = (long)kv_size(extmark_sub_single); - ExtmarkSubSingle sub_single; - for (i = 0; i < n; i++) { - sub_single = kv_Z(extmark_sub_single, i); - extmark_move_regmatch_single(sub_single.startpos, - sub_single.endpos, - sub_single.lnum, - sub_single.sublen); - } - - kv_destroy(extmark_sub_single); - } kv_destroy(preview_lines.subresults); diff --git a/src/nvim/fold.c b/src/nvim/fold.c index b193b4005c..addfab8f08 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -22,6 +22,7 @@ #include "nvim/func_attr.h" #include "nvim/indent.h" #include "nvim/buffer_updates.h" +#include "nvim/mark_extended.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -1610,6 +1611,7 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen) // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end line = ml_get(lnum); size_t line_len = STRLEN(line); + size_t added = 0; if (u_save(lnum - 1, lnum + 1) == OK) { // Check if the line ends with an unclosed comment @@ -1619,12 +1621,19 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen) // Append the marker to the end of the line if (p == NULL || line_is_comment) { STRLCPY(newline + line_len, marker, markerlen + 1); + added = markerlen; } else { STRCPY(newline + line_len, cms); memcpy(newline + line_len + (p - cms), marker, markerlen); STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); + added = markerlen + STRLEN(cms)-2; } ml_replace(lnum, newline, false); + if (added) { + extmark_splice(curbuf, (int)lnum-1, (int)line_len, + 0, 0, + 0, (int)added, kExtmarkUndo); + } } } @@ -1692,6 +1701,9 @@ static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen) memcpy(newline, line, (size_t)(p - line)); STRCPY(newline + (p - line), p + len); ml_replace(lnum, newline, false); + extmark_splice(curbuf, (int)lnum-1, (int)(p - line), + 0, (int)len, + 0, 0, kExtmarkUndo); } break; } diff --git a/src/nvim/indent.c b/src/nvim/indent.c index efbfea33aa..2c5fdd8ea6 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -13,6 +13,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/misc1.h" @@ -88,6 +89,7 @@ int get_indent_str(char_u *ptr, int ts, int list) // SIN_CHANGED: call changed_bytes() if the line was changed. // SIN_INSERT: insert the indent in front of the line. // SIN_UNDO: save line for undo before changing it. +// SIN_NOMARK: don't move extmarks (because just after ml_append or something) // @param size measured in spaces // Returns true if the line was changed. int set_indent(int size, int flags) @@ -205,6 +207,7 @@ int set_indent(int size, int flags) // If 'preserveindent' and 'expandtab' are both set keep the original // characters and allocate accordingly. We will fill the rest with spaces // after the if (!curbuf->b_p_et) below. + int skipcols = 0; // number of columns (in bytes) that were presved if (orig_char_len != -1) { int newline_size; // = orig_char_len + size - ind_done + line_len STRICT_ADD(orig_char_len, size, &newline_size, int); @@ -219,6 +222,7 @@ int set_indent(int size, int flags) ind_len = orig_char_len + todo; p = oldline; s = newline; + skipcols = orig_char_len; while (orig_char_len > 0) { *s++ = *p++; @@ -263,6 +267,7 @@ int set_indent(int size, int flags) ind_done++; } *s++ = *p++; + skipcols++; } // Fill to next tabstop with a tab, if possible. @@ -290,6 +295,13 @@ int set_indent(int size, int flags) // Replace the line (unless undo fails). if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) { ml_replace(curwin->w_cursor.lnum, newline, false); + if (!(flags & SIN_NOMARK)) { + extmark_splice(curbuf, + (int)curwin->w_cursor.lnum-1, skipcols, + 0, (int)(p-oldline) - skipcols, + 0, (int)(s-newline) - skipcols, + kExtmarkUndo); + } if (flags & SIN_CHANGED) { changed_bytes(curwin->w_cursor.lnum, 0); diff --git a/src/nvim/indent.h b/src/nvim/indent.h index 6552157d20..f96732bf1c 100644 --- a/src/nvim/indent.h +++ b/src/nvim/indent.h @@ -3,10 +3,11 @@ #include "nvim/vim.h" -/* flags for set_indent() */ -#define SIN_CHANGED 1 /* call changed_bytes() when line changed */ -#define SIN_INSERT 2 /* insert indent before existing text */ -#define SIN_UNDO 4 /* save line for undo before changing it */ +// flags for set_indent() +#define SIN_CHANGED 1 // call changed_bytes() when line changed +#define SIN_INSERT 2 // insert indent before existing text +#define SIN_UNDO 4 // save line for undo before changing it +#define SIN_NOMARK 8 // don't adjust extmarks #ifdef INCLUDE_GENERATED_DECLARATIONS # include "indent.h.generated.h" diff --git a/src/nvim/map.c b/src/nvim/map.c index cdade5ee71..cba39f24b3 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -44,7 +44,8 @@ #define INITIALIZER(T, U) T##_##U##_initializer #define INITIALIZER_DECLARE(T, U, ...) const U INITIALIZER(T, U) = __VA_ARGS__ -#define DEFAULT_INITIALIZER {0} +#define DEFAULT_INITIALIZER { 0 } +#define SSIZE_INITIALIZER { -1 } #define MAP_IMPL(T, U, ...) \ INITIALIZER_DECLARE(T, U, __VA_ARGS__); \ @@ -178,10 +179,16 @@ MAP_IMPL(int, int, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) +MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER) +MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER) +#define EXTMARK_NS_INITIALIZER { 0, 0 } +MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER) +#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } +#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER } +MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER) MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false } MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) -#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) diff --git a/src/nvim/map.h b/src/nvim/map.h index fec91ac0c2..761938776d 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -4,9 +4,9 @@ #include #include "nvim/map_defs.h" +#include "nvim/mark_extended_defs.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" -#include "nvim/bufhl_defs.h" #include "nvim/highlight_defs.h" #if defined(__NetBSD__) @@ -38,6 +38,18 @@ MAP_DECLS(int, int) MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) +MAP_DECLS(uint64_t, ssize_t) +MAP_DECLS(uint64_t, uint64_t) + +// NB: this is the only way to define a struct both containing and contained +// in a map... +typedef struct ExtmarkNs { // For namespacing extmarks + Map(uint64_t, uint64_t) *map; // For fast lookup + uint64_t free_id; // For automatically assigning id's +} ExtmarkNs; + +MAP_DECLS(uint64_t, ExtmarkNs) +MAP_DECLS(uint64_t, ExtmarkItem) MAP_DECLS(handle_T, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 8b2f342142..4a7452493a 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -20,6 +20,7 @@ #include "nvim/ex_cmds.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -915,10 +916,9 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after, - bool end_temp, ExtmarkOp op) { - mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op); + mark_adjust_internal(line1, line2, amount, amount_after, true, op); } // mark_adjust_nofold() does the same as mark_adjust() but without adjusting @@ -927,15 +927,15 @@ void mark_adjust(linenr_T line1, // calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, // for an example of why this may be necessary, see do_move(). void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount, - long amount_after, bool end_temp, + long amount_after, ExtmarkOp op) { - mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op); + mark_adjust_internal(line1, line2, amount, amount_after, false, op); } static void mark_adjust_internal(linenr_T line1, linenr_T line2, long amount, long amount_after, - bool adjust_folds, bool end_temp, + bool adjust_folds, ExtmarkOp op) { int i; @@ -991,9 +991,8 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, } sign_mark_adjust(line1, line2, amount, amount_after); - bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp); if (op != kExtmarkNOOP) { - extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp); + extmark_adjust(curbuf, line1, line2, amount, amount_after, op); } } @@ -1106,7 +1105,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, // cursor is inside them. void mark_col_adjust( linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount, - int spaces_removed, ExtmarkOp op) + int spaces_removed) { int i; int fnum = curbuf->b_fnum; @@ -1126,13 +1125,6 @@ void mark_col_adjust( col_adjust(&(namedfm[i].fmark.mark)); } - // Extmarks - if (op != kExtmarkNOOP) { - // TODO(timeyyy): consider spaces_removed? (behave like a delete) - extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount, - kExtmarkUndo); - } - /* last Insert position */ col_adjust(&(curbuf->b_last_insert.mark)); diff --git a/src/nvim/mark.h b/src/nvim/mark.h index ed4e47907b..d8370c367a 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -6,6 +6,7 @@ #include "nvim/buffer_defs.h" #include "nvim/func_attr.h" #include "nvim/mark_defs.h" +#include "nvim/mark_extended_defs.h" #include "nvim/memory.h" #include "nvim/pos.h" #include "nvim/os/time.h" diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c index 17776d438a..b60d847676 100644 --- a/src/nvim/mark_extended.c +++ b/src/nvim/mark_extended.c @@ -7,14 +7,13 @@ // The btree provides efficient range lookups. // A map of pointers to the marks is used for fast lookup by mark id. // -// Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or -// extmark_col_adjust_delete which are based on col_adjust and mark_adjust from -// mark.c +// Marks are moved by calls to extmark_splice. Additionally mark_adjust +// might adjust extmarks to line inserts/deletes. // // Undo/Redo of marks is implemented by storing the call arguments to -// extmark_col_adjust or extmark_adjust. The list of arguments -// is applied in extmark_apply_undo. The only case where we have to -// copy extmarks is for the area being effected by a delete. +// extmark_splice. The list of arguments is applied in extmark_apply_undo. +// The only case where we have to copy extmarks is for the area being effected +// by a delete. // // Marks live in namespaces that allow plugins/users to segregate marks // from other users. @@ -30,9 +29,11 @@ // leave it in same position unless it is on the EOL of a line. #include +#include "nvim/api/vim.h" #include "nvim/vim.h" -#include "charset.h" +#include "nvim/charset.h" #include "nvim/mark_extended.h" +#include "nvim/buffer_updates.h" #include "nvim/memline.h" #include "nvim/pos.h" #include "nvim/globals.h" @@ -40,93 +41,227 @@ #include "nvim/lib/kbtree.h" #include "nvim/undo.h" #include "nvim/buffer.h" +#include "nvim/syntax.h" +#include "nvim/highlight.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark_extended.c.generated.h" #endif +static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) { + if (!buf->b_extmark_ns) { + if (!put) { + return NULL; + } + buf->b_extmark_ns = map_new(uint64_t, ExtmarkNs)(); + buf->b_extmark_index = map_new(uint64_t, ExtmarkItem)(); + } + + ExtmarkNs *ns = map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put); + if (put && ns->map == NULL) { + ns->map = map_new(uint64_t, uint64_t)(); + ns->free_id = 1; + } + return ns; +} + /// Create or update an extmark /// /// must not be used during iteration! -/// @returns whether a new mark was created -int extmark_set(buf_T *buf, uint64_t ns, uint64_t id, - linenr_T lnum, colnr_T col, ExtmarkOp op) +/// @returns the mark id +uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, + int row, colnr_T col, ExtmarkOp op) { - Extmark *extmark = extmark_from_id(buf, ns, id); - if (!extmark) { - extmark_create(buf, ns, id, lnum, col, op); - return true; + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); + mtpos_t old_pos; + uint64_t mark = 0; + + if (id == 0) { + id = ns->free_id++; } else { - ExtmarkLine *extmarkline = extmark->line; - extmark_update(extmark, buf, ns, id, lnum, col, op, NULL); - if (kb_size(&extmarkline->items) == 0) { - kb_del(extmarklines, &buf->b_extlines, extmarkline); - extmarkline_free(extmarkline); + uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id); + if (old_mark) { + if (old_mark & MARKTREE_PAIRED_FLAG) { + extmark_del(buf, ns_id, id); + } else { + // TODO(bfredl): we need to do more if "revising" a decoration mark. + MarkTreeIter itr[1]; + old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); + assert(itr->node); + if (old_pos.row == row && old_pos.col == col) { + map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark); + mark = marktree_revise(buf->b_marktree, itr); + goto revised; + } + marktree_del_itr(buf->b_marktree, itr, false); + } + } else { + ns->free_id = MAX(ns->free_id, id+1); } + } + + mark = marktree_put(buf->b_marktree, row, col, true); +revised: + map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, + (ExtmarkItem){ ns_id, id, 0, + KV_INITIAL_VALUE }); + map_put(uint64_t, uint64_t)(ns->map, id, mark); + + if (op != kExtmarkNoUndo) { + // TODO(bfredl): this doesn't cover all the cases and probably shouldn't + // be done "prematurely". Any movement in undo history might necessitate + // adding new marks to old undo headers. + u_extmark_set(buf, mark, row, col); + } + return id; +} + +static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) +{ + MarkTreeIter itr[1]; + mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); + if (pos.row == -1) { return false; } + + if (pos.row == row && pos.col == col) { + return true; + } + + marktree_move(buf->b_marktree, itr, row, col); + return true; } // Remove an extmark // Returns 0 on missing id -int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op) +bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) { - Extmark *extmark = extmark_from_id(buf, ns, id); - if (!extmark) { - return 0; + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); + if (!ns) { + return false; + } + + uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); + if (!mark) { + return false; } - return extmark_delete(extmark, buf, ns, id, op); + + MarkTreeIter itr[1]; + mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); + assert(pos.row >= 0); + marktree_del_itr(buf->b_marktree, itr, false); + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); + + if (mark & MARKTREE_PAIRED_FLAG) { + mtpos_t pos2 = marktree_lookup(buf->b_marktree, + mark|MARKTREE_END_FLAG, itr); + assert(pos2.row >= 0); + marktree_del_itr(buf->b_marktree, itr, false); + if (item.hl_id && pos2.row >= pos.row) { + redraw_buf_range_later(buf, pos.row+1, pos2.row+1); + } + } + + if (kv_size(item.virt_text)) { + redraw_buf_line_later(buf, pos.row+1); + } + clear_virttext(&item.virt_text); + + map_del(uint64_t, uint64_t)(ns->map, id); + map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); + + // TODO(bfredl): delete it from current undo header, opportunistically? + + return true; } // Free extmarks in a ns between lines // if ns = 0, it means clear all namespaces -void extmark_clear(buf_T *buf, uint64_t ns, - linenr_T l_lnum, linenr_T u_lnum, ExtmarkOp undo) +bool extmark_clear(buf_T *buf, uint64_t ns_id, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col) { if (!buf->b_extmark_ns) { - return; + return false; } bool marks_cleared = false; - if (undo == kExtmarkUndo) { - // Copy marks that would be effected by clear - u_extmark_copy(buf, ns, l_lnum, 0, u_lnum, MAXCOL); - } - bool all_ns = ns == 0 ? true : false; - ExtmarkNs *ns_obj; + bool all_ns = (ns_id == 0); + ExtmarkNs *ns = NULL; if (!all_ns) { - ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - if (!ns_obj) { + ns = buf_ns_ref(buf, ns_id, false); + if (!ns) { // nothing to do - return; + return false; } + + // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes + // it could be faster to iterate over the map instead } - FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { - FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, { - if (extmark->ns_id == ns || all_ns) { - marks_cleared = true; - if (all_ns) { - ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, extmark->ns_id); - } else { - ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - } - pmap_del(uint64_t)(ns_obj->map, extmark->mark_id); - kb_del_itr(markitems, &extmarkline->items, &mitr); - } - }); - if (kb_size(&extmarkline->items) == 0) { - kb_del_itr(extmarklines, &buf->b_extlines, &itr); - extmarkline_free(extmarkline); + // the value is either zero or the lnum (row+1) if highlight was present. + static Map(uint64_t, uint64_t) *delete_set = NULL; + if (delete_set == NULL) { + delete_set = map_new(uint64_t, uint64_t)(); + } + + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, l_row, l_col, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 + || mark.row > u_row + || (mark.row == u_row && mark.col > u_col)) { + break; } - }); + uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id, + false); + if (del_status) { + marktree_del_itr(buf->b_marktree, itr, false); + map_del(uint64_t, uint64_t)(delete_set, mark.id); + if (*del_status > 0) { + redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1); + } + continue; + } + + uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, + start_id); - // Record the undo for the actual move - if (marks_cleared && undo == kExtmarkUndo) { - u_extmark_clear(buf, ns, l_lnum, u_lnum); + assert(item.ns_id > 0 && item.mark_id > 0); + if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) { + if (kv_size(item.virt_text)) { + redraw_buf_line_later(buf, mark.row+1); + } + clear_virttext(&item.virt_text); + marks_cleared = true; + if (mark.id & MARKTREE_PAIRED_FLAG) { + uint64_t other = mark.id ^ MARKTREE_END_FLAG; + uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0; + map_put(uint64_t, uint64_t)(delete_set, other, status); + } + ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns; + map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id); + map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id); + marktree_del_itr(buf->b_marktree, itr, false); + } else { + marktree_itr_next(buf->b_marktree, itr); + } } + uint64_t id, status; + map_foreach(delete_set, id, status, { + mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr); + assert(itr->node); + marktree_del_itr(buf->b_marktree, itr, false); + if (status > 0) { + redraw_buf_range_later(buf, (linenr_T)status, pos.row+1); + } + }); + map_clear(uint64_t, uint64_t)(delete_set); + return marks_cleared; } // Returns the position of marks between a range, @@ -135,190 +270,65 @@ void extmark_clear(buf_T *buf, uint64_t ns, // will be searched to the start, or end // dir can be set to control the order of the array // amount = amount of marks to find or -1 for all -ExtmarkArray extmark_get(buf_T *buf, uint64_t ns, - linenr_T l_lnum, colnr_T l_col, - linenr_T u_lnum, colnr_T u_col, +ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col, int64_t amount, bool reverse) { ExtmarkArray array = KV_INITIAL_VALUE; + MarkTreeIter itr[1]; // Find all the marks - if (!reverse) { - FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, { - if (extmark->ns_id == ns) { - kv_push(array, extmark); - if (kv_size(array) == (size_t)amount) { - return array; - } - } - }) - } else { - FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, { - if (extmark->ns_id == ns) { - kv_push(array, extmark); - if (kv_size(array) == (size_t)amount) { - return array; - } - } - }) - } - return array; -} - -static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id, - linenr_T lnum, colnr_T col, ExtmarkOp op) -{ - if (!buf->b_extmark_ns) { - buf->b_extmark_ns = pmap_new(uint64_t)(); - } - ExtmarkNs *ns_obj = NULL; - ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - // Initialize a new namespace for this buffer - if (!ns_obj) { - ns_obj = xmalloc(sizeof(ExtmarkNs)); - ns_obj->map = pmap_new(uint64_t)(); - pmap_put(uint64_t)(buf->b_extmark_ns, ns, ns_obj); - } - - // Create or get a line - ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, true); - // Create and put mark on the line - extmark_put(col, id, extmarkline, ns); - - // Marks do not have stable address so we have to look them up - // by using the line instead of the mark - pmap_put(uint64_t)(ns_obj->map, id, extmarkline); - if (op != kExtmarkNoUndo) { - u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet); - } - - // Set a free id so extmark_free_id_get works - extmark_free_id_set(ns_obj, id); -} - -// update the position of an extmark -// to update while iterating pass the markitems itr -static void extmark_update(Extmark *extmark, buf_T *buf, - uint64_t ns, uint64_t id, - linenr_T lnum, colnr_T col, - ExtmarkOp op, kbitr_t(markitems) *mitr) -{ - assert(op != kExtmarkNOOP); - if (op != kExtmarkNoUndo) { - u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col, - lnum, col); - } - ExtmarkLine *old_line = extmark->line; - // Move the mark to a new line and update column - if (old_line->lnum != lnum) { - ExtmarkLine *ref_line = extmarkline_ref(buf, lnum, true); - extmark_put(col, id, ref_line, ns); - // Update the hashmap - ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - pmap_put(uint64_t)(ns_obj->map, id, ref_line); - // Delete old mark - if (mitr != NULL) { - kb_del_itr(markitems, &(old_line->items), mitr); - } else { - kb_del(markitems, &old_line->items, *extmark); + marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col }, + itr, reverse, false, NULL); + int order = reverse ? -1 : 1; + while ((int64_t)kv_size(array) < amount) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 + || (mark.row - u_row) * order > 0 + || (mark.row == u_row && (mark.col - u_col) * order > 0)) { + break; } - // Just update the column - } else { - if (mitr != NULL) { - // The btree stays organized during iteration with kbitr_t - extmark->col = col; + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id); + if (item.ns_id == ns_id) { + kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id, + .mark_id = item.mark_id, + .row = mark.row, .col = mark.col })); + } + if (reverse) { + marktree_itr_prev(buf->b_marktree, itr); } else { - // Keep the btree in order - kb_del(markitems, &old_line->items, *extmark); - extmark_put(col, id, old_line, ns); + marktree_itr_next(buf->b_marktree, itr); } } -} - -static int extmark_delete(Extmark *extmark, - buf_T *buf, - uint64_t ns, - uint64_t id, - ExtmarkOp op) -{ - if (op != kExtmarkNoUndo) { - u_extmark_set(buf, ns, id, extmark->line->lnum, extmark->col, - kExtmarkDel); - } - - // Remove our key from the namespace - ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - pmap_del(uint64_t)(ns_obj->map, id); - - // Remove the mark mark from the line - ExtmarkLine *extmarkline = extmark->line; - kb_del(markitems, &extmarkline->items, *extmark); - // Remove the line if there are no more marks in the line - if (kb_size(&extmarkline->items) == 0) { - kb_del(extmarklines, &buf->b_extlines, extmarkline); - extmarkline_free(extmarkline); - } - return true; + return array; } // Lookup an extmark by id -Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) +ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) { - if (!buf->b_extmark_ns) { - return NULL; - } - ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - if (!ns_obj || !kh_size(ns_obj->map->table)) { - return NULL; + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); + ExtmarkInfo ret = { 0, 0, -1, -1 }; + if (!ns) { + return ret; } - ExtmarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id); - if (!extmarkline) { - return NULL; + + uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); + if (!mark) { + return ret; } - FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, { - if (extmark->ns_id == ns - && extmark->mark_id == id) { - return extmark; - } - }) - return NULL; -} + mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL); + assert(pos.row >= 0); -// Lookup an extmark by position -Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col) -{ - if (!buf->b_extmark_ns) { - return NULL; - } - FOR_ALL_EXTMARKS(buf, ns, lnum, col, lnum, col, { - if (extmark->ns_id == ns) { - if (extmark->col == col) { - return extmark; - } - } - }) - return NULL; -} + ret.ns_id = ns_id; + ret.mark_id = id; + ret.row = pos.row; + ret.col = pos.col; -// Returns an available id in a namespace -uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns) -{ - if (!buf->b_extmark_ns) { - return 1; - } - ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); - if (!ns_obj) { - return 1; - } - return ns_obj->free_id; + return ret; } -// Set the next free id in a namesapce -static void extmark_free_id_set(ExtmarkNs *ns_obj, uint64_t id) -{ - // Simply Heurstic, the largest id + 1 - ns_obj->free_id = id + 1; -} // free extmarks from the buffer void extmark_free_all(buf_T *buf) @@ -327,813 +337,574 @@ void extmark_free_all(buf_T *buf) return; } - uint64_t ns; - ExtmarkNs *ns_obj; + uint64_t id; + ExtmarkNs ns; + ExtmarkItem item; - FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, { - kb_del_itr(extmarklines, &buf->b_extlines, &itr); - extmarkline_free(extmarkline); - }) + marktree_clear(buf->b_marktree); - map_foreach(buf->b_extmark_ns, ns, ns_obj, { - (void)ns; - pmap_free(uint64_t)(ns_obj->map); - xfree(ns_obj); + map_foreach(buf->b_extmark_ns, id, ns, { + (void)id; + map_free(uint64_t, uint64_t)(ns.map); }); - - pmap_free(uint64_t)(buf->b_extmark_ns); + map_free(uint64_t, ExtmarkNs)(buf->b_extmark_ns); buf->b_extmark_ns = NULL; - // k?_init called to set pointers to NULL - kb_destroy(extmarklines, (&buf->b_extlines)); - kb_init(&buf->b_extlines); - - kv_destroy(buf->b_extmark_move_space); - kv_init(buf->b_extmark_move_space); + map_foreach(buf->b_extmark_index, id, item, { + (void)id; + clear_virttext(&item.virt_text); + }); + map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index); + buf->b_extmark_index = NULL; } // Save info for undo/redo of set marks -static void u_extmark_set(buf_T *buf, uint64_t ns, uint64_t id, - linenr_T lnum, colnr_T col, UndoObjectType undo_type) +static void u_extmark_set(buf_T *buf, uint64_t mark, + int row, colnr_T col) { u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } - ExtmarkSet set; - set.ns_id = ns; - set.mark_id = id; - set.lnum = lnum; - set.col = col; + ExtmarkSavePos pos; + pos.mark = mark; + pos.old_row = -1; + pos.old_col = -1; + pos.row = row; + pos.col = col; - ExtmarkUndoObject undo = { .type = undo_type, - .data.set = set }; + ExtmarkUndoObject undo = { .type = kExtmarkSavePos, + .data.savepos = pos }; kv_push(uhp->uh_extmark, undo); } -// Save info for undo/redo of deleted marks -static void u_extmark_update(buf_T *buf, uint64_t ns, uint64_t id, - linenr_T old_lnum, colnr_T old_col, - linenr_T lnum, colnr_T col) +/// copy extmarks data between range +/// +/// useful when we cannot simply reverse the operation. This will do nothing on +/// redo, enforces correct position when undo. +void u_extmark_copy(buf_T *buf, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col) { u_header_T *uhp = u_force_get_undo_header(buf); if (!uhp) { return; } - ExtmarkUpdate update; - update.ns_id = ns; - update.mark_id = id; - update.old_lnum = old_lnum; - update.old_col = old_col; - update.lnum = lnum; - update.col = col; + ExtmarkUndoObject undo; - ExtmarkUndoObject undo = { .type = kExtmarkUpdate, - .data.update = update }; - kv_push(uhp->uh_extmark, undo); -} + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, l_row, l_col, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 + || mark.row > u_row + || (mark.row == u_row && mark.col > u_col)) { + break; + } + ExtmarkSavePos pos; + pos.mark = mark.id; + pos.old_row = mark.row; + pos.old_col = mark.col; + pos.row = -1; + pos.col = -1; + + undo.data.savepos = pos; + undo.type = kExtmarkSavePos; + kv_push(uhp->uh_extmark, undo); -// Hueristic works only for when the user is typing in insert mode -// - Instead of 1 undo object for each char inserted, -// we create 1 undo objet for all text inserted before the user hits esc -// Return True if we compacted else False -static bool u_compact_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol, - long lnum_amount, long col_amount) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return false; + marktree_itr_next(buf->b_marktree, itr); } +} - if (kv_size(uhp->uh_extmark) < 1) { - return false; - } - // Check the last action - ExtmarkUndoObject object = kv_last(uhp->uh_extmark); +/// undo or redo an extmark operation +void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) +{ + // splice: any text operation changing position (except :move) + if (undo_info.type == kExtmarkSplice) { + // Undo + ExtmarkSplice splice = undo_info.data.splice; + if (undo) { + extmark_splice(curbuf, + splice.start_row, splice.start_col, + splice.newextent_row, splice.newextent_col, + splice.oldextent_row, splice.oldextent_col, + kExtmarkNoUndo); - if (object.type != kColAdjust) { - return false; + } else { + extmark_splice(curbuf, + splice.start_row, splice.start_col, + splice.oldextent_row, splice.oldextent_col, + splice.newextent_row, splice.newextent_col, + kExtmarkNoUndo); + } + // kExtmarkSavePos + } else if (undo_info.type == kExtmarkSavePos) { + ExtmarkSavePos pos = undo_info.data.savepos; + if (undo) { + if (pos.old_row >= 0) { + extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col); + } + // Redo + } else { + if (pos.row >= 0) { + extmark_setraw(curbuf, pos.mark, pos.row, pos.col); + } + } + } else if (undo_info.type == kExtmarkMove) { + ExtmarkMove move = undo_info.data.move; + if (undo) { + extmark_move_region(curbuf, + move.new_row, move.new_col, + move.extent_row, move.extent_col, + move.start_row, move.start_col, + kExtmarkNoUndo); + } else { + extmark_move_region(curbuf, + move.start_row, move.start_col, + move.extent_row, move.extent_col, + move.new_row, move.new_col, + kExtmarkNoUndo); + } } - ColAdjust undo = object.data.col_adjust; - bool compactable = false; +} - if (!undo.lnum_amount && !lnum_amount) { - if (undo.lnum == lnum) { - if ((undo.mincol + undo.col_amount) >= mincol) { - compactable = true; - } } } - if (!compactable) { - return false; +// Adjust extmark row for inserted/deleted rows (columns stay fixed). +void extmark_adjust(buf_T *buf, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after, + ExtmarkOp undo) +{ + if (!curbuf_splice_pending) { + int old_extent, new_extent; + if (amount == MAXLNUM) { + old_extent = (int)(line2 - line1+1); + new_extent = (int)(amount_after + old_extent); + } else { + // A region is either deleted (amount == MAXLNUM) or + // added (line2 == MAXLNUM). The only other case is :move + // which is handled by a separate entry point extmark_move_region. + assert(line2 == MAXLNUM); + old_extent = 0; + new_extent = (int)amount; + } + extmark_splice(buf, + (int)line1-1, 0, + old_extent, 0, + new_extent, 0, undo); } - - undo.col_amount = undo.col_amount + col_amount; - ExtmarkUndoObject new_undo = { .type = kColAdjust, - .data.col_adjust = undo }; - kv_last(uhp->uh_extmark) = new_undo; - return true; } -// Save col_adjust info so we can undo/redo -void u_extmark_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol, - long lnum_amount, long col_amount) +void extmark_splice(buf_T *buf, + int start_row, colnr_T start_col, + int oldextent_row, colnr_T oldextent_col, + int newextent_row, colnr_T newextent_col, + ExtmarkOp undo) { - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } - - if (!u_compact_col_adjust(buf, lnum, mincol, lnum_amount, col_amount)) { - ColAdjust col_adjust; - col_adjust.lnum = lnum; - col_adjust.mincol = mincol; - col_adjust.lnum_amount = lnum_amount; - col_adjust.col_amount = col_amount; + buf_updates_send_splice(buf, start_row, start_col, + oldextent_row, oldextent_col, + newextent_row, newextent_col); - ExtmarkUndoObject undo = { .type = kColAdjust, - .data.col_adjust = col_adjust }; - - kv_push(uhp->uh_extmark, undo); + if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) { + // Copy marks that would be effected by delete + // TODO(bfredl): Be "smart" about gravity here, left-gravity at the + // beginning and right-gravity at the end need not be preserved. + // Also be smart about marks that already have been saved (important for + // merge!) + int end_row = start_row + oldextent_row; + int end_col = (oldextent_row ? 0 : start_col) + oldextent_col; + u_extmark_copy(buf, start_row, start_col, end_row, end_col); } -} -// Save col_adjust_delete info so we can undo/redo -void u_extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, - colnr_T mincol, colnr_T endcol, int eol) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } - ColAdjustDelete col_adjust_delete; - col_adjust_delete.lnum = lnum; - col_adjust_delete.mincol = mincol; - col_adjust_delete.endcol = endcol; - col_adjust_delete.eol = eol; + marktree_splice(buf->b_marktree, start_row, start_col, + oldextent_row, oldextent_col, + newextent_row, newextent_col); - ExtmarkUndoObject undo = { .type = kColAdjustDelete, - .data.col_adjust_delete = col_adjust_delete }; + if (undo == kExtmarkUndo) { + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } - kv_push(uhp->uh_extmark, undo); -} + bool merged = false; + // TODO(bfredl): this is quite rudimentary. We merge small (within line) + // inserts with each other and small deletes with each other. Add full + // merge algorithm later. + if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) { + ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark, + kv_size(uhp->uh_extmark)-1); + if (item->type == kExtmarkSplice) { + ExtmarkSplice *splice = &item->data.splice; + if (splice->start_row == start_row && splice->oldextent_row == 0 + && splice->newextent_row == 0) { + if (oldextent_col == 0 && start_col >= splice->start_col + && start_col <= splice->start_col+splice->newextent_col) { + splice->newextent_col += newextent_col; + merged = true; + } else if (newextent_col == 0 + && start_col == splice->start_col+splice->newextent_col) { + splice->oldextent_col += oldextent_col; + merged = true; + } else if (newextent_col == 0 + && start_col + oldextent_col == splice->start_col) { + splice->start_col = start_col; + splice->oldextent_col += oldextent_col; + merged = true; + } + } + } + } -// Save adjust info so we can undo/redo -static void u_extmark_adjust(buf_T * buf, linenr_T line1, linenr_T line2, - long amount, long amount_after) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; + if (!merged) { + ExtmarkSplice splice; + splice.start_row = start_row; + splice.start_col = start_col; + splice.oldextent_row = oldextent_row; + splice.oldextent_col = oldextent_col; + splice.newextent_row = newextent_row; + splice.newextent_col = newextent_col; + + kv_push(uhp->uh_extmark, + ((ExtmarkUndoObject){ .type = kExtmarkSplice, + .data.splice = splice })); + } } - - Adjust adjust; - adjust.line1 = line1; - adjust.line2 = line2; - adjust.amount = amount; - adjust.amount_after = amount_after; - - ExtmarkUndoObject undo = { .type = kLineAdjust, - .data.adjust = adjust }; - - kv_push(uhp->uh_extmark, undo); } -// save info to undo/redo a :move -void u_extmark_move(buf_T *buf, linenr_T line1, linenr_T line2, - linenr_T last_line, linenr_T dest, linenr_T num_lines, - linenr_T extra) + +void extmark_move_region(buf_T *buf, + int start_row, colnr_T start_col, + int extent_row, colnr_T extent_col, + int new_row, colnr_T new_col, + ExtmarkOp undo) { - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } + // TODO(bfredl): this is not synced to the buffer state inside the callback. + // But unless we make the undo implementation smarter, this is not ensured + // anyway. + buf_updates_send_splice(buf, start_row, start_col, + extent_row, extent_col, + 0, 0); - AdjustMove move; - move.line1 = line1; - move.line2 = line2; - move.last_line = last_line; - move.dest = dest; - move.num_lines = num_lines; - move.extra = extra; + marktree_move_region(buf->b_marktree, start_row, start_col, + extent_row, extent_col, + new_row, new_col); - ExtmarkUndoObject undo = { .type = kAdjustMove, - .data.move = move }; + buf_updates_send_splice(buf, new_row, new_col, + 0, 0, + extent_row, extent_col); - kv_push(uhp->uh_extmark, undo); -} -// copy extmarks data between range, useful when we cannot simply reverse -// the operation. This will do nothing on redo, enforces correct position when -// undo. -// if ns = 0, it means copy all namespaces -void u_extmark_copy(buf_T *buf, uint64_t ns, - linenr_T l_lnum, colnr_T l_col, - linenr_T u_lnum, colnr_T u_col) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } + if (undo == kExtmarkUndo) { + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } - bool all_ns = ns == 0 ? true : false; + ExtmarkMove move; + move.start_row = start_row; + move.start_col = start_col; + move.extent_row = extent_row; + move.extent_col = extent_col; + move.new_row = new_row; + move.new_col = new_col; - ExtmarkCopy copy; - ExtmarkUndoObject undo; - FOR_ALL_EXTMARKS(buf, 1, l_lnum, l_col, u_lnum, u_col, { - if (all_ns || extmark->ns_id == ns) { - copy.ns_id = extmark->ns_id; - copy.mark_id = extmark->mark_id; - copy.lnum = extmark->line->lnum; - copy.col = extmark->col; - - undo.data.copy = copy; - undo.type = kExtmarkCopy; - kv_push(uhp->uh_extmark, undo); - } - }); + kv_push(uhp->uh_extmark, + ((ExtmarkUndoObject){ .type = kExtmarkMove, + .data.move = move })); + } } -void u_extmark_copy_place(buf_T *buf, - linenr_T l_lnum, colnr_T l_col, - linenr_T u_lnum, colnr_T u_col, - linenr_T p_lnum, colnr_T p_col) +uint64_t src2ns(Integer *src_id) { - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; + if (*src_id == 0) { + *src_id = (Integer)nvim_create_namespace((String)STRING_INIT); + } + if (*src_id < 0) { + return UINT64_MAX; + } else { + return (uint64_t)(*src_id); } +} - ExtmarkCopyPlace copy_place; - copy_place.l_lnum = l_lnum; - copy_place.l_col = l_col; - copy_place.u_lnum = u_lnum; - copy_place.u_col = u_col; - copy_place.p_lnum = p_lnum; - copy_place.p_col = p_col; +/// Adds a decoration to a buffer. +/// +/// Unlike matchaddpos() highlights, these follow changes to the the buffer +/// texts. Decorations are represented internally and in the API as extmarks. +/// +/// @param buf The buffer to add decorations to +/// @param ns_id A valid namespace id. +/// @param hl_id Id of the highlight group to use (or zero) +/// @param start_row The line to highlight +/// @param start_col First column to highlight +/// @param end_row The line to highlight +/// @param end_col The last column to highlight +/// @param virt_text Virtual text (currently placed at the EOL of start_row) +/// @return The extmark id inside the namespace +uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id, + int start_row, colnr_T start_col, + int end_row, colnr_T end_col, + VirtText virt_text) +{ + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); + ExtmarkItem item; + item.ns_id = ns_id; + item.mark_id = ns->free_id++; + item.hl_id = hl_id; + item.virt_text = virt_text; + + uint64_t mark; + + if (end_row > -1) { + mark = marktree_put_pair(buf->b_marktree, + start_row, start_col, true, + end_row, end_col, false); + } else { + mark = marktree_put(buf->b_marktree, start_row, start_col, true); + } - ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace, - .data.copy_place = copy_place }; + map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item); + map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark); - kv_push(uhp->uh_extmark, undo); + redraw_buf_range_later(buf, start_row+1, + (end_row >= 0 ? end_row : start_row) + 1); + return item.mark_id; } -// Save info for undo/redo of extmark_clear -static void u_extmark_clear(buf_T *buf, uint64_t ns, - linenr_T l_lnum, linenr_T u_lnum) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// @param buf Buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Highlight group id +/// @param pos_start Cursor position to start the hightlighting at +/// @param pos_end Cursor position to end the highlighting at +/// @param offset Move the whole highlighting this many columns to the right +void bufhl_add_hl_pos_offset(buf_T *buf, + int src_id, + int hl_id, + lpos_T pos_start, + lpos_T pos_end, + colnr_T offset) +{ + colnr_T hl_start = 0; + colnr_T hl_end = 0; + + // TODO(bfredl): if decoration had blocky mode, we could avoid this loop + for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + hl_start = offset-1; + hl_end = MAXCOL; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset; + hl_end = MAXCOL; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = offset-1; + hl_end = pos_end.col + offset; + } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { + hl_start = pos_start.col + offset; + hl_end = pos_end.col + offset; + } + (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id, + (int)lnum-1, hl_start, (int)lnum-1, hl_end, + VIRTTEXT_EMPTY); } - - ExtmarkClear clear; - clear.ns_id = ns; - clear.l_lnum = l_lnum; - clear.u_lnum = u_lnum; - - ExtmarkUndoObject undo = { .type = kExtmarkClear, - .data.clear = clear }; - kv_push(uhp->uh_extmark, undo); } -// undo or redo an extmark operation -void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) +void clear_virttext(VirtText *text) { - linenr_T lnum; - colnr_T mincol; - long lnum_amount; - long col_amount; - linenr_T line1; - linenr_T line2; - long amount; - long amount_after; - - // use extmark_col_adjust - if (undo_info.type == kColAdjust) { - // Undo - if (undo) { - lnum = (undo_info.data.col_adjust.lnum - + undo_info.data.col_adjust.lnum_amount); - lnum_amount = -undo_info.data.col_adjust.lnum_amount; - col_amount = -undo_info.data.col_adjust.col_amount; - mincol = (undo_info.data.col_adjust.mincol - + (colnr_T)undo_info.data.col_adjust.col_amount); - // Redo - } else { - lnum = undo_info.data.col_adjust.lnum; - col_amount = undo_info.data.col_adjust.col_amount; - lnum_amount = undo_info.data.col_adjust.lnum_amount; - mincol = undo_info.data.col_adjust.mincol; - } - extmark_col_adjust(curbuf, - lnum, mincol, lnum_amount, col_amount, kExtmarkNoUndo); - // use extmark_col_adjust_delete - } else if (undo_info.type == kColAdjustDelete) { - if (undo) { - mincol = undo_info.data.col_adjust_delete.mincol; - col_amount = (undo_info.data.col_adjust_delete.endcol - - undo_info.data.col_adjust_delete.mincol) + 1; - extmark_col_adjust(curbuf, - undo_info.data.col_adjust_delete.lnum, - mincol, - 0, - col_amount, - kExtmarkNoUndo); - // Redo - } else { - extmark_col_adjust_delete(curbuf, - undo_info.data.col_adjust_delete.lnum, - undo_info.data.col_adjust_delete.mincol, - undo_info.data.col_adjust_delete.endcol, - kExtmarkNoUndo, - undo_info.data.col_adjust_delete.eol); - } - // use extmark_adjust - } else if (undo_info.type == kLineAdjust) { - if (undo) { - // Undo - call signature type one - insert now - if (undo_info.data.adjust.amount == MAXLNUM) { - line1 = undo_info.data.adjust.line1; - line2 = MAXLNUM; - amount = -undo_info.data.adjust.amount_after; - amount_after = 0; - // Undo - call singature type two - delete now - } else if (undo_info.data.adjust.line2 == MAXLNUM) { - line1 = undo_info.data.adjust.line1; - line2 = undo_info.data.adjust.line2; - amount = -undo_info.data.adjust.amount; - amount_after = undo_info.data.adjust.amount_after; - // Undo - call signature three - move lines - } else { - line1 = (undo_info.data.adjust.line1 - + undo_info.data.adjust.amount); - line2 = (undo_info.data.adjust.line2 - + undo_info.data.adjust.amount); - amount = -undo_info.data.adjust.amount; - amount_after = -undo_info.data.adjust.amount_after; - } - // redo - } else { - line1 = undo_info.data.adjust.line1; - line2 = undo_info.data.adjust.line2; - amount = undo_info.data.adjust.amount; - amount_after = undo_info.data.adjust.amount_after; - } - extmark_adjust(curbuf, - line1, line2, amount, amount_after, kExtmarkNoUndo, false); - // kExtmarkCopy - } else if (undo_info.type == kExtmarkCopy) { - // Redo should be handled by kColAdjustDelete or kExtmarkCopyPlace - if (undo) { - extmark_set(curbuf, - undo_info.data.copy.ns_id, - undo_info.data.copy.mark_id, - undo_info.data.copy.lnum, - undo_info.data.copy.col, - kExtmarkNoUndo); - } - // uses extmark_copy_and_place - } else if (undo_info.type == kExtmarkCopyPlace) { - // Redo, undo is handle by kExtmarkCopy - if (!undo) { - extmark_copy_and_place(curbuf, - undo_info.data.copy_place.l_lnum, - undo_info.data.copy_place.l_col, - undo_info.data.copy_place.u_lnum, - undo_info.data.copy_place.u_col, - undo_info.data.copy_place.p_lnum, - undo_info.data.copy_place.p_col, - kExtmarkNoUndo, true, NULL); - } - // kExtmarkClear - } else if (undo_info.type == kExtmarkClear) { - // Redo, undo is handle by kExtmarkCopy - if (!undo) { - extmark_clear(curbuf, - undo_info.data.clear.ns_id, - undo_info.data.clear.l_lnum, - undo_info.data.clear.u_lnum, - kExtmarkNoUndo); - } - // kAdjustMove - } else if (undo_info.type == kAdjustMove) { - apply_undo_move(undo_info, undo); - // extmark_set - } else if (undo_info.type == kExtmarkSet) { - if (undo) { - extmark_del(curbuf, - undo_info.data.set.ns_id, - undo_info.data.set.mark_id, - kExtmarkNoUndo); - // Redo - } else { - extmark_set(curbuf, - undo_info.data.set.ns_id, - undo_info.data.set.mark_id, - undo_info.data.set.lnum, - undo_info.data.set.col, - kExtmarkNoUndo); - } - // extmark_set into update - } else if (undo_info.type == kExtmarkUpdate) { - if (undo) { - extmark_set(curbuf, - undo_info.data.update.ns_id, - undo_info.data.update.mark_id, - undo_info.data.update.old_lnum, - undo_info.data.update.old_col, - kExtmarkNoUndo); - // Redo - } else { - extmark_set(curbuf, - undo_info.data.update.ns_id, - undo_info.data.update.mark_id, - undo_info.data.update.lnum, - undo_info.data.update.col, - kExtmarkNoUndo); - } - // extmark_del - } else if (undo_info.type == kExtmarkDel) { - if (undo) { - extmark_set(curbuf, - undo_info.data.set.ns_id, - undo_info.data.set.mark_id, - undo_info.data.set.lnum, - undo_info.data.set.col, - kExtmarkNoUndo); - // Redo - } else { - extmark_del(curbuf, - undo_info.data.set.ns_id, - undo_info.data.set.mark_id, - kExtmarkNoUndo); - } + for (size_t i = 0; i < kv_size(*text); i++) { + xfree(kv_A(*text, i).text); } + kv_destroy(*text); + *text = (VirtText)KV_INITIAL_VALUE; } -// undo/redo an kExtmarkMove operation -static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo) +VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) { - // 3 calls are required , see comment in function do_move (ex_cmds.c) - linenr_T line1 = undo_info.data.move.line1; - linenr_T line2 = undo_info.data.move.line2; - linenr_T last_line = undo_info.data.move.last_line; - linenr_T dest = undo_info.data.move.dest; - linenr_T num_lines = undo_info.data.move.num_lines; - linenr_T extra = undo_info.data.move.extra; - - if (undo) { - if (dest >= line2) { - extmark_adjust(curbuf, dest - num_lines + 1, dest, - last_line - dest + num_lines - 1, 0L, kExtmarkNoUndo, - true); - extmark_adjust(curbuf, dest - line2, dest - line1, - dest - line2, 0L, kExtmarkNoUndo, false); - } else { - extmark_adjust(curbuf, line1-num_lines, line2-num_lines, - last_line - (line1-num_lines), 0L, kExtmarkNoUndo, true); - extmark_adjust(curbuf, (line1-num_lines) + 1, (line2-num_lines) + 1, - -num_lines, 0L, kExtmarkNoUndo, false); + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, row, 0, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 || mark.row > row) { + break; } - extmark_adjust(curbuf, last_line, last_line + num_lines - 1, - line1 - last_line, 0L, kExtmarkNoUndo, true); - // redo - } else { - extmark_adjust(curbuf, line1, line2, - last_line - line2, 0L, kExtmarkNoUndo, true); - if (dest >= line2) { - extmark_adjust(curbuf, line2 + 1, dest, - -num_lines, 0L, kExtmarkNoUndo, false); - } else { - extmark_adjust(curbuf, dest + 1, line1 - 1, - num_lines, 0L, kExtmarkNoUndo, false); + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (item && (ns_id == 0 || ns_id == item->ns_id) + && kv_size(item->virt_text)) { + return &item->virt_text; } - extmark_adjust(curbuf, last_line - num_lines + 1, last_line, - -(last_line - dest - extra), 0L, kExtmarkNoUndo, true); + marktree_itr_next(buf->b_marktree, itr); } + return NULL; } -/// Get the column position for EOL on a line -/// -/// If the lnum doesn't exist, returns 0 -colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum) +bool extmark_decorations_reset(buf_T *buf, DecorationState *state) { - if (lnum > buf->b_ml.ml_line_count) { - return 0; - } - return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1; + state->row = -1; + return buf->b_extmark_index; } -// Adjust columns and rows for extmarks -// based off mark_col_adjust in mark.c -// returns true if something was moved otherwise false -static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum, - colnr_T mincol, long lnum_amount, - bool for_delete, - long update_col) +bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state) { - bool marks_exist = false; - - ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false); - if (!extmarkline) { + kv_size(state->active) = 0; + state->top_row = top_row; + marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); + if (!state->itr->node) { return false; } + marktree_itr_rewind(buf->b_marktree, state->itr); + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0) { // || mark.row > end_row + break; + } + // TODO(bfredl): dedicated flag for being a decoration? + if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { + goto next_mark; + } + mtpos_t altpos = marktree_lookup(buf->b_marktree, + mark.id^MARKTREE_END_FLAG, NULL); + + uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + start_id, false); + if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row + && !kv_size(item->virt_text)) + || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + goto next_mark; + } - FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, mincol, MAXCOL, { - marks_exist = true; - - // Calculate desired col amount where the adjustment should take place - // (not taking) eol into account - long col_amount; - if (for_delete) { - if (extmark->col < update_col) { - // When mark inside range - colnr_T start_effected_range = mincol - 1; - col_amount = -(extmark->col - start_effected_range); + if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { + int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; + VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; + HlRange range; + if (mark.id&MARKTREE_END_FLAG) { + range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, + attr_id, vt }; } else { - // Mark outside of range - // -1 because a delete of width 0 should still move marks - col_amount = -(update_col - mincol) - 1; + range = (HlRange){ mark.row, mark.col, altpos.row, + altpos.col, attr_id, vt }; } - } else { - // for anything other than deletes - col_amount = update_col; + kv_push(state->active, range); } - - // No update required for this guy - if (col_amount == 0 && lnum_amount == 0) { - continue; - } - - // Set mark to start of line - if (col_amount < 0 - && extmark->col <= (colnr_T)-col_amount) { - extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, - extmarkline->lnum + lnum_amount, - 1, kExtmarkNoUndo, &mitr); - // Update the mark - } else { - // Note: The undo is handled by u_extmark_col_adjust, NoUndo here - extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, - extmarkline->lnum + lnum_amount, - extmark->col + (colnr_T)col_amount, kExtmarkNoUndo, &mitr); +next_mark: + if (marktree_itr_node_done(state->itr)) { + break; } - }) - - if (kb_size(&extmarkline->items) == 0) { - kb_del(extmarklines, &buf->b_extlines, extmarkline); - extmarkline_free(extmarkline); + marktree_itr_next(buf->b_marktree, state->itr); } - return marks_exist; + return true; // TODO(bfredl): check if available in the region } -// Adjust columns and rows for extmarks -// -// based off mark_col_adjust in mark.c -// use extmark_col_adjust_impl to move columns by inserting -// Doesn't take the eol into consideration (possible to put marks in invalid -// positions) -void extmark_col_adjust(buf_T *buf, linenr_T lnum, - colnr_T mincol, long lnum_amount, - long col_amount, ExtmarkOp undo) +bool extmark_decorations_line(buf_T *buf, int row, DecorationState *state) { - assert(col_amount > INT_MIN && col_amount <= INT_MAX); - - bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount, - false, col_amount); - - marks_moved |= bufhl_mark_col_adjust(buf, lnum, mincol, - lnum_amount, col_amount); - - if (undo == kExtmarkUndo && marks_moved) { - u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount); + if (state->row == -1) { + extmark_decorations_start(buf, row, state); } + state->row = row; + state->col_until = -1; + return true; // TODO(bfredl): be more precise } -// Adjust marks after a delete on a line -// -// Automatically readjusts to take the eol into account -// TODO(timeyyy): change mincol to be for the mark to be copied, not moved -// -// @param mincol First column that needs to be moved (start of delete range) + 1 -// @param endcol Last column which needs to be copied (end of delete range + 1) -void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, - colnr_T mincol, colnr_T endcol, - ExtmarkOp undo, int _eol) +int extmark_decorations_col(buf_T *buf, int col, DecorationState *state) { - colnr_T start_effected_range = mincol; - - bool marks_moved; - if (undo == kExtmarkUndo) { - // Copy marks that would be effected by delete - // -1 because we need to restore if a mark existed at the start pos - u_extmark_copy(buf, 0, lnum, start_effected_range, lnum, endcol); - } - - marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0, - true, (long)endcol); - - marks_moved |= bufhl_mark_col_adjust(buf, lnum, endcol, 0, mincol-(endcol+1)); - // Deletes at the end of the line have different behaviour than the normal - // case when deleted. - // Cleanup any marks that are floating beyond the end of line. - // we allow this to be passed in as well because the buffer may have already - // been mutated. - int eol = _eol; - if (!eol) { - eol = extmark_eol_col(buf, lnum); - } - FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, { - extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, - extmarkline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr); - }) - - // Record the undo for the actual move - if (marks_moved && undo == kExtmarkUndo) { - u_extmark_col_adjust_delete(buf, lnum, mincol, endcol, eol); + if (col <= state->col_until) { + return state->current; } -} + state->col_until = MAXCOL; + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0 || mark.row > state->row) { + break; + } else if (mark.row == state->row && mark.col > col) { + state->col_until = mark.col-1; + break; + } -// Adjust extmark row for inserted/deleted rows (columns stay fixed). -void extmark_adjust(buf_T *buf, - linenr_T line1, - linenr_T line2, - long amount, - long amount_after, - ExtmarkOp undo, - bool end_temp) -{ - ExtmarkLine *_extline; - - // btree needs to be kept ordered to work, so far only :move requires this - // 2nd call with end_temp = true unpack the lines from the temp position - if (end_temp && amount < 0) { - for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) { - _extline = kv_A(buf->b_extmark_move_space, i); - _extline->lnum += amount; - kb_put(extmarklines, &buf->b_extlines, _extline); + if ((mark.id&MARKTREE_END_FLAG)) { + // TODO(bfredl): check decorations flag + goto next_mark; } - kv_size(buf->b_extmark_move_space) = 0; - return; - } + mtpos_t endpos = marktree_lookup(buf->b_marktree, + mark.id|MARKTREE_END_FLAG, NULL); - bool marks_exist = false; - linenr_T *lp; - - linenr_T adj_start = line1; - if (amount == MAXLNUM) { - // Careful! marks from deleted region can end up on en extisting extmarkline - // that is goinig to be adjusted to the target position. - linenr_T join_num = line1 - amount_after; - ExtmarkLine *joinline = (join_num > line2 - ? extmarkline_ref(buf, join_num, false) : NULL); - - // extmark_adjust is already redoable, the copy should only be for undo - marks_exist = extmark_copy_and_place(curbuf, - line1, 1, - line2, MAXCOL, - line1, 1, - kExtmarkUndoNoRedo, true, joinline); - adj_start = line2+1; - } - FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, { - marks_exist = true; - lp = &(extmarkline->lnum); - if (*lp <= line2) { - // 1st call with end_temp = true, store the lines in a temp position - if (end_temp && amount > 0) { - kb_del_itr_extmarklines(&buf->b_extlines, &itr); - kv_push(buf->b_extmark_move_space, extmarkline); - } + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); - *lp += amount; - } else if (amount_after && *lp > line2) { - *lp += amount_after; + if (endpos.row < mark.row + || (endpos.row == mark.row && endpos.col <= mark.col)) { + if (!kv_size(item->virt_text)) { + goto next_mark; + } } - }) - if (undo == kExtmarkUndo && marks_exist) { - u_extmark_adjust(buf, line1, line2, amount, amount_after); - } -} - -/// Range points to copy -/// -/// if part of a larger iteration we can't delete, then the caller -/// must check for empty lines. -bool extmark_copy_and_place(buf_T *buf, - linenr_T l_lnum, colnr_T l_col, - linenr_T u_lnum, colnr_T u_col, - linenr_T p_lnum, colnr_T p_col, - ExtmarkOp undo, bool delete, - ExtmarkLine *destline) + if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { + int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; + VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; + kv_push(state->active, ((HlRange){ mark.row, mark.col, + endpos.row, endpos.col, + attr_id, vt })); + } -{ - bool marks_moved = false; - if (undo == kExtmarkUndo || undo == kExtmarkUndoNoRedo) { - // Copy marks that would be effected by delete - u_extmark_copy(buf, 0, l_lnum, l_col, u_lnum, u_col); +next_mark: + marktree_itr_next(buf->b_marktree, state->itr); } - // Move extmarks to their final position - // Careful: if we move items within the same line, we might change order of - // marks within the same extmarkline. Too keep it simple, first delete all - // items from the extmarkline and put them back in the right order. - FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { - kvec_t(Extmark) temp_space = KV_INITIAL_VALUE; - bool same_line = extmarkline == destline; - FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, - (extmarkline->lnum > l_lnum) ? 0 : l_col, - (extmarkline->lnum < u_lnum) ? MAXCOL : u_col, { - if (!destline) { - destline = extmarkline_ref(buf, p_lnum, true); - same_line = extmarkline == destline; + int attr = 0; + size_t j = 0; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + bool active = false, keep = true; + if (item.end_row < state->row + || (item.end_row == state->row && item.end_col <= col)) { + if (!(item.start_row >= state->row && item.virt_text)) { + keep = false; } - marks_moved = true; - if (!same_line) { - extmark_put(p_col, extmark->mark_id, destline, extmark->ns_id); - ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, - extmark->ns_id); - pmap_put(uint64_t)(ns_obj->map, extmark->mark_id, destline); + } else { + if (item.start_row < state->row + || (item.start_row == state->row && item.start_col <= col)) { + active = true; + if (item.end_row == state->row) { + state->col_until = MIN(state->col_until, item.end_col-1); + } } else { - kv_push(temp_space, *extmark); - } - // Delete old mark - kb_del_itr(markitems, &extmarkline->items, &mitr); - }) - if (same_line) { - for (size_t i = 0; i < kv_size(temp_space); i++) { - Extmark mark = kv_A(temp_space, i); - extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id); + if (item.start_row == state->row) { + state->col_until = MIN(state->col_until, item.start_col-1); + } } - kv_destroy(temp_space); - } else if (delete && kb_size(&extmarkline->items) == 0) { - kb_del_itr(extmarklines, &buf->b_extlines, &itr); - extmarkline_free(extmarkline); } - }) - - // Record the undo for the actual move - if (marks_moved && undo == kExtmarkUndo) { - u_extmark_copy_place(buf, l_lnum, l_col, u_lnum, u_col, p_lnum, p_col); + if (active && item.attr_id > 0) { + attr = hl_combine_attr(attr, item.attr_id); + } + if (keep) { + kv_A(state->active, j++) = kv_A(state->active, i); + } } - - return marks_moved; + kv_size(state->active) = j; + state->current = attr; + return attr; } -// Get reference to line in kbtree_t, allocating it if neccessary. -ExtmarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) +VirtText *extmark_decorations_virt_text(buf_T *buf, DecorationState *state) { - kbtree_t(extmarklines) *b = &buf->b_extlines; - ExtmarkLine t, **pp; - t.lnum = lnum; - - pp = kb_get(extmarklines, b, &t); - if (!pp) { - if (!put) { - return NULL; + extmark_decorations_col(buf, MAXCOL, state); + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + if (item.start_row == state->row && item.virt_text) { + return item.virt_text; } - ExtmarkLine *p = xcalloc(sizeof(ExtmarkLine), 1); - p->lnum = lnum; - // p->items zero initialized - kb_put(extmarklines, b, p); - return p; } - // Return existing - return *pp; -} - -void extmarkline_free(ExtmarkLine *extmarkline) -{ - kb_destroy(markitems, (&extmarkline->items)); - xfree(extmarkline); -} - -/// Put an extmark into a line, -/// -/// caller must ensure combination of id and ns_id isn't in use. -void extmark_put(colnr_T col, uint64_t id, - ExtmarkLine *extmarkline, uint64_t ns) -{ - Extmark t; - t.col = col; - t.mark_id = id; - t.line = extmarkline; - t.ns_id = ns; - - kbtree_t(markitems) *b = &(extmarkline->items); - // kb_put requries the key to not be there - assert(!kb_getp(markitems, b, &t)); - - kb_put(markitems, b, t); + return NULL; } - - diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h index ee1da26875..f809148d9b 100644 --- a/src/nvim/mark_extended.h +++ b/src/nvim/mark_extended.h @@ -1,236 +1,57 @@ #ifndef NVIM_MARK_EXTENDED_H #define NVIM_MARK_EXTENDED_H +#include "nvim/buffer_defs.h" #include "nvim/mark_extended_defs.h" -#include "nvim/buffer_defs.h" // for buf_T +#include "nvim/marktree.h" +EXTERN int extmark_splice_pending INIT(= 0); -// Macro Documentation: FOR_ALL_? -// Search exclusively using the range values given. -// Use MAXCOL/MAXLNUM for the start and end of the line/col. -// The ns parameter: Unless otherwise stated, this is only a starting point -// for the btree to searched in, the results being itterated over will -// still contain extmarks from other namespaces. - -// see FOR_ALL_? for documentation -#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\ - kbitr_t(extmarklines) itr;\ - ExtmarkLine t;\ - t.lnum = l_lnum;\ - if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ - kb_itr_next(extmarklines, &buf->b_extlines, &itr);\ - }\ - ExtmarkLine *extmarkline;\ - for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \ - &buf->b_extlines, &itr)) { \ - extmarkline = kb_itr_key(&itr);\ - if (extmarkline->lnum > u_lnum) { \ - break;\ - }\ - code;\ - } - -// see FOR_ALL_? for documentation -#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\ - kbitr_t(extmarklines) itr;\ - ExtmarkLine t;\ - t.lnum = u_lnum;\ - if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ - kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\ - }\ - ExtmarkLine *extmarkline;\ - for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \ - &buf->b_extlines, &itr)) { \ - extmarkline = kb_itr_key(&itr);\ - if (extmarkline->lnum < l_lnum) { \ - break;\ - }\ - code;\ - } - -// see FOR_ALL_? for documentation -#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ - kbitr_t(markitems) mitr;\ - Extmark mt;\ - mt.ns_id = ns;\ - mt.mark_id = 0;\ - mt.line = NULL;\ - FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \ - mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\ - if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ - kb_itr_next(markitems, &extmarkline->items, &mitr);\ - } \ - Extmark *extmark;\ - for (; \ - kb_itr_valid(&mitr); \ - kb_itr_next(markitems, &extmarkline->items, &mitr)) { \ - extmark = &kb_itr_key(&mitr);\ - if (extmark->line->lnum == u_lnum \ - && extmark->col > u_col) { \ - break;\ - }\ - code;\ - }\ - }) - - -// see FOR_ALL_? for documentation -#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ - kbitr_t(markitems) mitr;\ - Extmark mt;\ - mt.mark_id = sizeof(uint64_t);\ - mt.ns_id = ns;\ - FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \ - mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\ - if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ - kb_itr_prev(markitems, &extmarkline->items, &mitr);\ - } \ - Extmark *extmark;\ - for (; \ - kb_itr_valid(&mitr); \ - kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \ - extmark = &kb_itr_key(&mitr);\ - if (extmark->line->lnum == l_lnum \ - && extmark->col < l_col) { \ - break;\ - }\ - code;\ - }\ - }) - - -#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\ - kbitr_t(markitems) mitr;\ - Extmark mt;\ - mt.ns_id = 0;\ - mt.mark_id = 0;\ - mt.line = NULL;\ - mt.col = l_col;\ - colnr_T extmarkline_u_col = u_col;\ - if (!kb_itr_get(markitems, &items, mt, &mitr)) { \ - kb_itr_next(markitems, &items, &mitr);\ - } \ - Extmark *extmark;\ - for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \ - extmark = &kb_itr_key(&mitr);\ - if (extmark->col > extmarkline_u_col) { \ - break;\ - }\ - code;\ - } - - -typedef struct ExtmarkNs { // For namespacing extmarks - PMap(uint64_t) *map; // For fast lookup - uint64_t free_id; // For automatically assigning id's -} ExtmarkNs; - - -typedef kvec_t(Extmark *) ExtmarkArray; - - -// Undo/redo extmarks - -typedef enum { - kExtmarkNOOP, // Extmarks shouldn't be moved - kExtmarkUndo, // Operation should be reversable/undoable - kExtmarkNoUndo, // Operation should not be reversable - kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable -} ExtmarkOp; - +typedef struct +{ + uint64_t ns_id; + uint64_t mark_id; + int row; + colnr_T col; +} ExtmarkInfo; -// adjust line numbers only, corresponding to mark_adjust call -typedef struct { - linenr_T line1; - linenr_T line2; - long amount; - long amount_after; -} Adjust; +typedef kvec_t(ExtmarkInfo) ExtmarkArray; -// adjust columns after split/join line, like mark_col_adjust -typedef struct { - linenr_T lnum; - colnr_T mincol; - long col_amount; - long lnum_amount; -} ColAdjust; // delete the columns between mincol and endcol typedef struct { - linenr_T lnum; - colnr_T mincol; - colnr_T endcol; - int eol; -} ColAdjustDelete; - -// adjust linenumbers after :move operation + int start_row; + colnr_T start_col; + int oldextent_row; + colnr_T oldextent_col; + int newextent_row; + colnr_T newextent_col; +} ExtmarkSplice; + +// adjust marks after :move operation typedef struct { - linenr_T line1; - linenr_T line2; - linenr_T last_line; - linenr_T dest; - linenr_T num_lines; - linenr_T extra; -} AdjustMove; - -// TODO(bfredl): reconsider if we really should track mark creation/updating -// itself, these are not really "edit" operation. -// extmark was created -typedef struct { - uint64_t ns_id; - uint64_t mark_id; - linenr_T lnum; - colnr_T col; -} ExtmarkSet; + int start_row; + int start_col; + int extent_row; + int extent_col; + int new_row; + int new_col; +} ExtmarkMove; // extmark was updated typedef struct { - uint64_t ns_id; - uint64_t mark_id; - linenr_T old_lnum; + uint64_t mark; // raw mark id of the marktree + int old_row; colnr_T old_col; - linenr_T lnum; - colnr_T col; -} ExtmarkUpdate; - -// copied mark before deletion (as operation is destructive) -typedef struct { - uint64_t ns_id; - uint64_t mark_id; - linenr_T lnum; + int row; colnr_T col; -} ExtmarkCopy; - -// also used as part of :move operation? probably can be simplified to one -// event. -typedef struct { - linenr_T l_lnum; - colnr_T l_col; - linenr_T u_lnum; - colnr_T u_col; - linenr_T p_lnum; - colnr_T p_col; -} ExtmarkCopyPlace; - -// extmark was cleared. -// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate -typedef struct { - uint64_t ns_id; - linenr_T l_lnum; - linenr_T u_lnum; -} ExtmarkClear; - +} ExtmarkSavePos; typedef enum { - kLineAdjust, - kColAdjust, - kColAdjustDelete, - kAdjustMove, - kExtmarkSet, - kExtmarkDel, + kExtmarkSplice, + kExtmarkMove, kExtmarkUpdate, - kExtmarkCopy, - kExtmarkCopyPlace, + kExtmarkSavePos, kExtmarkClear, } UndoObjectType; @@ -238,42 +59,32 @@ typedef enum { struct undo_object { UndoObjectType type; union { - Adjust adjust; - ColAdjust col_adjust; - ColAdjustDelete col_adjust_delete; - AdjustMove move; - ExtmarkSet set; - ExtmarkUpdate update; - ExtmarkCopy copy; - ExtmarkCopyPlace copy_place; - ExtmarkClear clear; + ExtmarkSplice splice; + ExtmarkMove move; + ExtmarkSavePos savepos; } data; }; -// For doing move of extmarks in substitutions typedef struct { - lpos_T startpos; - lpos_T endpos; - linenr_T lnum; - int sublen; -} ExtmarkSubSingle; + int start_row; + int start_col; + int end_row; + int end_col; + int attr_id; + VirtText *virt_text; +} HlRange; -// For doing move of extmarks in substitutions typedef struct { - lpos_T startpos; - lpos_T endpos; - linenr_T lnum; - linenr_T newline_in_pat; - linenr_T newline_in_sub; - linenr_T lnum_added; - lpos_T cm_start; // start of the match - lpos_T cm_end; // end of the match - int eol; // end of the match -} ExtmarkSubMulti; + MarkTreeIter itr[1]; + kvec_t(HlRange) active; + int top_row; + int row; + int col_until; + int current; + VirtText *virt_text; +} DecorationState; -typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t; -typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark_extended.h.generated.h" diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h index 565c599d06..439f7f0b36 100644 --- a/src/nvim/mark_extended_defs.h +++ b/src/nvim/mark_extended_defs.h @@ -2,53 +2,36 @@ #define NVIM_MARK_EXTENDED_DEFS_H #include "nvim/pos.h" // for colnr_T -#include "nvim/map.h" // for uint64_t -#include "nvim/lib/kbtree.h" #include "nvim/lib/kvec.h" -struct ExtmarkLine; +typedef struct { + char *text; + int hl_id; +} VirtTextChunk; -typedef struct Extmark +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) + +typedef struct { uint64_t ns_id; uint64_t mark_id; - struct ExtmarkLine *line; - colnr_T col; -} Extmark; - - -// We only need to compare columns as rows are stored in a different tree. -// Marks are ordered by: position, namespace, mark_id -// This improves moving marks but slows down all other use cases (searches) -static inline int extmark_cmp(Extmark a, Extmark b) -{ - int cmp = kb_generic_cmp(a.col, b.col); - if (cmp != 0) { - return cmp; - } - cmp = kb_generic_cmp(a.ns_id, b.ns_id); - if (cmp != 0) { - return cmp; - } - return kb_generic_cmp(a.mark_id, b.mark_id); -} - - -#define markitems_cmp(a, b) (extmark_cmp((a), (b))) -KBTREE_INIT(markitems, Extmark, markitems_cmp, 10) - -typedef struct ExtmarkLine -{ - linenr_T lnum; - kbtree_t(markitems) items; -} ExtmarkLine; - -#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum)) -KBTREE_INIT(extmarklines, ExtmarkLine *, EXTMARKLINE_CMP, 10) - + int hl_id; // highlight group + // TODO(bfredl): virt_text is pretty larger than the rest, + // pointer indirection? + VirtText virt_text; +} ExtmarkItem; typedef struct undo_object ExtmarkUndoObject; typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; +// Undo/redo extmarks + +typedef enum { + kExtmarkNOOP, // Extmarks shouldn't be moved + kExtmarkUndo, // Operation should be reversable/undoable + kExtmarkNoUndo, // Operation should not be reversable + kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable +} ExtmarkOp; #endif // NVIM_MARK_EXTENDED_DEFS_H diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 6ad283f5dc..52e602cd94 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -1095,6 +1095,7 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) void marktree_check(MarkTree *b) { +#ifndef NDEBUG if (b->root == NULL) { assert(b->n_keys == 0); assert(b->n_nodes == 0); @@ -1107,9 +1108,15 @@ void marktree_check(MarkTree *b) size_t nkeys = check_node(b, b->root, &dummy, &last_right); assert(b->n_keys == nkeys); assert(b->n_keys == map_size(b->id2node)); +#else + // Do nothing, as assertions are required + (void)b; +#endif } -size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right) +#ifndef NDEBUG +static size_t check_node(MarkTree *b, mtnode_t *x, + mtpos_t *last, bool *last_right) { assert(x->n <= 2 * T - 1); // TODO(bfredl): too strict if checking "in repair" post-delete tree. @@ -1153,6 +1160,7 @@ size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right) } return n_keys; } +#endif char *mt_inspect_rec(MarkTree *b) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 6a621cdaa6..5da81dbff6 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -31,6 +31,7 @@ #include "nvim/indent.h" #include "nvim/log.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -307,15 +308,6 @@ void shift_line( change_indent(INDENT_SET, count, false, NUL, call_changed_bytes); } else { (void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0); - - colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw; - colnr_T col_amount = left ? -p_sw : p_sw; - extmark_col_adjust(curbuf, - curwin->w_cursor.lnum, - mincol, - 0, - col_amount, - kExtmarkUndo); } } @@ -352,6 +344,8 @@ static void shift_block(oparg_T *oap, int amount) char_u *const oldp = get_cursor_line_ptr(); + int startcol, oldlen, newlen; + if (!left) { /* * 1. Get start vcol @@ -361,6 +355,7 @@ static void shift_block(oparg_T *oap, int amount) */ total += bd.pre_whitesp; // all virtual WS up to & incl a split TAB colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp; + char_u * old_textstart = bd.textstart; if (bd.startspaces) { if (has_mbyte) { if ((*mb_ptr2len)(bd.textstart) == 1) { @@ -387,14 +382,19 @@ static void shift_block(oparg_T *oap, int amount) j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */ else j = total; - /* if we're splitting a TAB, allow for it */ - bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0); + + // if we're splitting a TAB, allow for it + int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0); + bd.textcol -= col_pre; const int len = (int)STRLEN(bd.textstart) + 1; int col = bd.textcol + i +j + len; assert(col >= 0); newp = (char_u *)xmalloc((size_t)col); memset(newp, NUL, (size_t)col); memmove(newp, oldp, (size_t)bd.textcol); + startcol = bd.textcol; + oldlen = (int)(bd.textstart-old_textstart) + col_pre; + newlen = i+j; memset(newp + bd.textcol, TAB, (size_t)i); memset(newp + bd.textcol + i, ' ', (size_t)j); /* the end */ @@ -478,7 +478,10 @@ static void shift_block(oparg_T *oap, int amount) // - the rest of the line, pointed to by non_white. new_line_len = verbatim_diff + fill + STRLEN(non_white) + 1; - newp = (char_u *) xmalloc(new_line_len); + newp = (char_u *)xmalloc(new_line_len); + startcol = (int)verbatim_diff; + oldlen = bd.textcol + (int)(non_white - bd.textstart) - (int)verbatim_diff; + newlen = (int)fill; memmove(newp, oldp, verbatim_diff); memset(newp + verbatim_diff, ' ', fill); STRMOVE(newp + verbatim_diff + fill, non_white); @@ -486,13 +489,12 @@ static void shift_block(oparg_T *oap, int amount) // replace the line ml_replace(curwin->w_cursor.lnum, newp, false); changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol); + extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol, + 0, oldlen, 0, newlen, + kExtmarkUndo); State = oldstate; curwin->w_cursor.col = oldcol; p_ri = old_p_ri; - - colnr_T col_amount = left ? -p_sw : p_sw; - extmark_col_adjust(curbuf, curwin->w_cursor.lnum, - curwin->w_cursor.col, 0, col_amount, kExtmarkUndo); } /* @@ -561,6 +563,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def // copy up to shifted part memmove(newp, oldp, (size_t)offset); oldp += offset; + int startcol = offset; // insert pre-padding memset(newp + offset, ' ', (size_t)spaces); @@ -569,6 +572,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def memmove(newp + offset + spaces, s, s_len); offset += (int)s_len; + int skipped = 0; if (spaces && !bdp->is_short) { // insert post-padding memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); @@ -576,6 +580,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def oldp++; // We allowed for that TAB, remember this now count++; + skipped = 1; } if (spaces > 0) @@ -583,6 +588,9 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def STRMOVE(newp + offset, oldp); ml_replace(lnum, newp, false); + extmark_splice(curbuf, (int)lnum-1, startcol, + 0, skipped, + 0, offset-startcol, kExtmarkUndo); if (lnum == oap->end.lnum) { /* Set "']" mark to the end of the block instead of the end of @@ -642,14 +650,6 @@ void op_reindent(oparg_T *oap, Indenter how) first_changed = curwin->w_cursor.lnum; } last_changed = curwin->w_cursor.lnum; - - // Adjust extmarks - extmark_col_adjust(curbuf, - curwin->w_cursor.lnum, - 0, // mincol - 0, // lnum_amount - amount, // col_amount - kExtmarkUndo); } } ++curwin->w_cursor.lnum; @@ -1517,6 +1517,11 @@ int op_delete(oparg_T *oap) STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp); // replace the line ml_replace(lnum, newp, false); + + extmark_splice(curbuf, (int)lnum-1, bd.textcol, + 0, bd.textlen, + 0, bd.startspaces+bd.endspaces, + kExtmarkUndo); } check_cursor_col(); @@ -1633,6 +1638,8 @@ int op_delete(oparg_T *oap) (linenr_T)(curwin->w_cursor.lnum + oap->line_count)) == FAIL) return FAIL; + curbuf_splice_pending++; + pos_T startpos = curwin->w_cursor; // start position for delete truncate_line(true); // delete from cursor to end of line curpos = curwin->w_cursor; // remember curwin->w_cursor @@ -1646,6 +1653,9 @@ int op_delete(oparg_T *oap) oap->op_type == OP_DELETE && !oap->is_VIsual); curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); + curbuf_splice_pending--; + extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col, + (int)oap->line_count-1, n, 0, 0, kExtmarkUndo); } } @@ -1660,19 +1670,6 @@ setmarks: } curbuf->b_op_start = oap->start; - // TODO(timeyyy): refactor: Move extended marks - // + 1 to change to buf mode, - // and + 1 because we only move marks after the deleted col - colnr_T mincol = oap->start.col + 1 + 1; - colnr_T endcol; - if (oap->motion_type == kMTBlockWise) { - // TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ? - endcol = bd.end_vcol + 1; - for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) { - extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, - kExtmarkUndo, 0); - } - } return OK; } @@ -1695,8 +1692,11 @@ static void mb_adjust_opend(oparg_T *oap) */ static inline void pbyte(pos_T lp, int c) { - assert(c <= UCHAR_MAX); - *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c; + assert(c <= UCHAR_MAX); + *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c; + if (!curbuf_splice_pending) { + extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo); + } } // Replace the character under the cursor with "c". @@ -1817,6 +1817,7 @@ int op_replace(oparg_T *oap, int c) size_t after_p_len = 0; int col = oldlen - bd.textcol - bd.textlen + 1; assert(col >= 0); + int newrows = 0, newcols = 0; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { // strlen(newp) at this point int newp_len = bd.textcol + bd.startspaces; @@ -1829,21 +1830,27 @@ int op_replace(oparg_T *oap, int c) newp_len += bd.endspaces; // copy the part after the changed part memmove(newp + newp_len, oldp, (size_t)col); - } + } + newcols = newp_len - bd.textcol; } else { // Replacing with \r or \n means splitting the line. after_p_len = (size_t)col; after_p = (char_u *)xmalloc(after_p_len); memmove(after_p, oldp, after_p_len); + newrows = 1; } // replace the line ml_replace(curwin->w_cursor.lnum, newp, false); + linenr_T baselnum = curwin->w_cursor.lnum; if (after_p != NULL) { ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false); appended_lines_mark(curwin->w_cursor.lnum, 1L); oap->end.lnum++; xfree(after_p); } + extmark_splice(curbuf, (int)baselnum-1, bd.textcol, + 0, bd.textlen, + newrows, newcols, kExtmarkUndo); } } else { // Characterwise or linewise motion replace. @@ -1856,6 +1863,8 @@ int op_replace(oparg_T *oap, int c) } else if (!oap->inclusive) dec(&(oap->end)); + // TODO(bfredl): we could batch all the splicing + // done on the same line, at least while (ltoreq(curwin->w_cursor, oap->end)) { n = gchar_cursor(); if (n != NUL) { @@ -2262,10 +2271,6 @@ void op_insert(oparg_T *oap, long count1) xfree(ins_text); } } - colnr_T col = oap->start.col; - for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) { - extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo); - } } /* @@ -2380,6 +2385,9 @@ int op_change(oparg_T *oap) oldp += bd.textcol; STRMOVE(newp + offset, oldp); ml_replace(linenr, newp, false); + extmark_splice(curbuf, (int)linenr-1, bd.textcol, + 0, 0, + 0, vpos.coladd+(int)ins_len, kExtmarkUndo); } } check_cursor(); @@ -2735,28 +2743,6 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) recursive = false; } - -static void extmarks_do_put(int dir, - size_t totlen, - MotionType y_type, - linenr_T lnum, - colnr_T col) -{ - // adjust extmarks - colnr_T col_amount = (colnr_T)(dir == FORWARD ? totlen-1 : totlen); - // Move extmark with char put - if (y_type == kMTCharWise) { - extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); - // Move extmark with blockwise put - } else if (y_type == kMTBlockWise) { - for (lnum = curbuf->b_op_start.lnum; - lnum <= curbuf->b_op_end.lnum; - lnum++) { - extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); - } - } -} - /* * Put contents of register "regname" into the text. * Caller must check "regname" to be valid! @@ -3176,6 +3162,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) assert(columns >= 0); memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); + extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol, + 0, delcount, + 0, (int)totlen, + kExtmarkUndo); ++curwin->w_cursor.lnum; if (i == 0) @@ -3277,6 +3267,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) ++curwin->w_cursor.col; changed_bytes(lnum, col); + extmark_splice(curbuf, (int)lnum-1, col, + 0, 0, + 0, (int)totlen, kExtmarkUndo); } else { // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. @@ -3332,13 +3325,22 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) first_indent = FALSE; } else if ((indent = get_indent() + indent_diff) < 0) indent = 0; - (void)set_indent(indent, 0); + (void)set_indent(indent, SIN_NOMARK); curwin->w_cursor = old_pos; /* remember how many chars were removed */ if (cnt == count && i == y_size - 1) lendiff -= (int)STRLEN(ml_get(lnum)); } } + + if (y_type == kMTCharWise) { + extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, + (int)y_size-1, (int)STRLEN(y_array[y_size-1]), + kExtmarkUndo); + } else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) { + extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, + (int)y_size+1, 0, kExtmarkUndo); + } } error: @@ -3352,8 +3354,10 @@ error: // can't be marks there. if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines < curbuf->b_ml.ml_line_count) { + ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT)) + ? kExtmarkUndo : kExtmarkNOOP; mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), - (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo); + (linenr_T)MAXLNUM, nr_lines, 0L, kind); } // note changed text for displaying and folding @@ -3415,9 +3419,7 @@ end: /* If the cursor is past the end of the line put it at the end. */ adjust_cursor_eol(); - - extmarks_do_put(dir, totlen, y_type, lnum, col); -} +} // NOLINT(readability/fn_size) /* * When the cursor is on the NUL past the end of the line and it should not be @@ -3779,6 +3781,13 @@ int do_join(size_t count, } } } + + if (t > 0 && curbuf_splice_pending == 0) { + extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize, + 1, (int)(curr- curr_start), + 0, spaces[t], + kExtmarkUndo); + } currsize = (int)STRLEN(curr); sumsize += currsize + spaces[t]; endcurr1 = endcurr2 = NUL; @@ -3814,6 +3823,8 @@ int do_join(size_t count, * should not really be a problem. */ + curbuf_splice_pending++; + for (t = (linenr_T)count - 1;; t--) { cend -= currsize; memmove(cend, curr, (size_t)currsize); @@ -3830,8 +3841,7 @@ int do_join(size_t count, long lnum_amount = (linenr_T)-t; long col_amount = (long)(cend - newp - spaces_removed); - mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed, - kExtmarkUndo); + mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed); if (t == 0) { break; @@ -3867,6 +3877,7 @@ int do_join(size_t count, curwin->w_cursor.lnum++; del_lines((long)count - 1, false); curwin->w_cursor.lnum = t; + curbuf_splice_pending--; /* * Set the cursor column: @@ -4265,14 +4276,14 @@ format_lines( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0, kExtmarkNOOP); + (long)-next_leader_len, 0); } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND int indent = (int)getwhitecols_curline(); if (indent > 0) { (void)del_bytes(indent, false, false); mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0, kExtmarkNOOP); + (colnr_T)0, 0L, (long)-indent, 0); } } curwin->w_cursor.lnum--; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 0612575e67..24ad012201 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -87,6 +87,7 @@ #include "nvim/highlight.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -622,6 +623,9 @@ bool win_cursorline_standout(const win_T *wp) || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); } +static DecorationState decorations; +bool decorations_active = false; + /* * Update a single window. * @@ -1221,6 +1225,7 @@ static void win_update(win_T *wp) : (wp->w_topline + wp->w_height_inner)); args.items[0] = WINDOW_OBJ(wp->handle); args.items[1] = BUFFER_OBJ(buf->handle); + // TODO(bfredl): we are not using this, but should be first drawn line? args.items[2] = INTEGER_OBJ(wp->w_topline-1); args.items[3] = INTEGER_OBJ(knownmax); // TODO(bfredl): we could allow this callback to change mod_top, mod_bot. @@ -1232,6 +1237,8 @@ static void win_update(win_T *wp) } } + decorations_active = extmark_decorations_reset(buf, &decorations); + for (;; ) { /* stop updating when reached the end of the window (check for _past_ * the end of the window is at the end of the loop) */ @@ -2250,8 +2257,7 @@ win_line ( int prev_c1 = 0; // first composing char for prev_c bool search_attr_from_match = false; // if search_attr is from :match - BufhlLineInfo bufhl_info; // bufhl data for this line - bool has_bufhl = false; // this buffer has highlight matches + bool has_decorations = false; // this buffer has decorations bool do_virttext = false; // draw virtual text for this line /* draw_state: items that are drawn in sequence: */ @@ -2315,14 +2321,12 @@ win_line ( } } - if (bufhl_start_line(wp->w_buffer, lnum, &bufhl_info)) { - if (kv_size(bufhl_info.line->items)) { - has_bufhl = true; + if (decorations_active) { + has_decorations = extmark_decorations_line(wp->w_buffer, lnum-1, + &decorations); + if (has_decorations) { extra_check = true; } - if (kv_size(bufhl_info.line->virt_text)) { - do_virttext = true; - } } // Check for columns to display for 'colorcolumn'. @@ -3515,19 +3519,25 @@ win_line ( char_attr = hl_combine_attr(spell_attr, char_attr); } - if (has_bufhl && v > 0) { - int bufhl_attr = bufhl_get_attr(&bufhl_info, (colnr_T)v); - if (bufhl_attr != 0) { + if (has_decorations && v > 0) { + int extmark_attr = extmark_decorations_col(wp->w_buffer, (colnr_T)v-1, + &decorations); + if (extmark_attr != 0) { if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, bufhl_attr); + char_attr = hl_combine_attr(char_attr, extmark_attr); } else { - char_attr = hl_combine_attr(bufhl_attr, char_attr); + char_attr = hl_combine_attr(extmark_attr, char_attr); } } } + // TODO(bfredl): luahl should reuse the "active decorations" buffer if (buf->b_luahl && v > 0 && v < (long)lua_attr_bufsize+1) { - char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]); + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]); + } else { + char_attr = hl_combine_attr(lua_attr_buf[v-1], char_attr); + } } if (wp->w_buffer->terminal) { @@ -4008,6 +4018,19 @@ win_line ( if (draw_color_col) draw_color_col = advance_color_col(VCOL_HLC, &color_cols); + VirtText virt_text = KV_INITIAL_VALUE; + if (luatext) { + kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 })); + do_virttext = true; + } else if (has_decorations) { + VirtText *vp = extmark_decorations_virt_text(wp->w_buffer, + &decorations); + if (vp) { + virt_text = *vp; + do_virttext = true; + } + } + if (((wp->w_p_cuc && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off && (int)wp->w_virtcol < @@ -4018,14 +4041,6 @@ win_line ( int rightmost_vcol = 0; int i; - VirtText virt_text; - if (luatext) { - virt_text = (VirtText)KV_INITIAL_VALUE; - kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 })); - } else { - virt_text = do_virttext ? bufhl_info.line->virt_text - : (VirtText)KV_INITIAL_VALUE; - } size_t virt_pos = 0; LineState s = LINE_STATE((char_u *)""); int virt_attr = 0; diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 980399a3ef..fda647106d 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2244,7 +2244,7 @@ static void u_undoredo(int undo, bool do_buf_event) // Adjust marks if (oldsize != newsize) { mark_adjust(top + 1, top + oldsize, (long)MAXLNUM, - (long)newsize - (long)oldsize, false, kExtmarkNOOP); + (long)newsize - (long)oldsize, kExtmarkNOOP); if (curbuf->b_op_start.lnum > top + oldsize) { curbuf->b_op_start.lnum += newsize - oldsize; } -- cgit From b4a92aadd274c21a32baa68e2a460910ef9c77f5 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 16 Jan 2020 15:48:42 +0100 Subject: messages: echo "line1\r\nline2" should not clear line1 --- src/nvim/eval.c | 7 +++++-- src/nvim/message.c | 14 ++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5c19ce4047..9916ee3bd3 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -21242,6 +21242,7 @@ void ex_echo(exarg_T *eap) char_u *arg = eap->arg; typval_T rettv; bool atstart = true; + bool need_clear = true; const int did_emsg_before = did_emsg; if (eap->skip) @@ -21284,7 +21285,7 @@ void ex_echo(exarg_T *eap) char *tofree = encode_tv2echo(&rettv, NULL); if (*tofree != NUL) { msg_ext_set_kind("echo"); - msg_multiline_attr(tofree, echo_attr, true); + msg_multiline_attr(tofree, echo_attr, true, &need_clear); } xfree(tofree); } @@ -21297,7 +21298,9 @@ void ex_echo(exarg_T *eap) emsg_skip--; } else { // remove text that may still be there from the command - msg_clr_eos(); + if (need_clear) { + msg_clr_eos(); + } if (eap->cmdidx == CMD_echo) { msg_end(); } diff --git a/src/nvim/message.c b/src/nvim/message.c index 067f1c3283..94729dfd2a 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -228,7 +228,8 @@ int msg_attr(const char *s, const int attr) } /// similar to msg_outtrans_attr, but support newlines and tabs. -void msg_multiline_attr(const char *s, int attr, bool check_int) +void msg_multiline_attr(const char *s, int attr, + bool check_int, bool *need_clear) FUNC_ATTR_NONNULL_ALL { const char *next_spec = s; @@ -243,8 +244,9 @@ void msg_multiline_attr(const char *s, int attr, bool check_int) // Printing all char that are before the char found by strpbrk msg_outtrans_len_attr((const char_u *)s, next_spec - s, attr); - if (*next_spec != TAB) { + if (*next_spec != TAB && *need_clear) { msg_clr_eos(); + *need_clear = false; } msg_putchar_attr((uint8_t)(*next_spec), attr); s = next_spec + 1; @@ -256,6 +258,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int) if (*s != NUL) { msg_outtrans_attr((char_u *)s, attr); } + return; } @@ -314,12 +317,15 @@ bool msg_attr_keep(char_u *s, int attr, bool keep, bool multiline) if (buf != NULL) s = buf; + bool need_clear = true; if (multiline) { - msg_multiline_attr((char *)s, attr, false); + msg_multiline_attr((char *)s, attr, false, &need_clear); } else { msg_outtrans_attr(s, attr); } - msg_clr_eos(); + if (need_clear) { + msg_clr_eos(); + } retval = msg_end(); if (keep && retval && vim_strsize(s) < (int)(Rows - cmdline_row - 1) -- cgit From 5355cee77d7b3b62917036281406726832b6d7dc Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 9 Nov 2019 15:46:12 +0900 Subject: Change to use ConPTY, if available --- src/nvim/CMakeLists.txt | 4 + src/nvim/channel.c | 33 +++++++- src/nvim/main.c | 6 ++ src/nvim/option.c | 13 +++ src/nvim/option_defs.h | 1 + src/nvim/options.lua | 7 ++ src/nvim/os/os_win_conpty.c | 181 ++++++++++++++++++++++++++++++++++++++++++ src/nvim/os/os_win_conpty.h | 22 +++++ src/nvim/os/pty_process_win.c | 146 ++++++++++++++++++++++------------ src/nvim/os/pty_process_win.h | 15 +++- 10 files changed, 375 insertions(+), 53 deletions(-) create mode 100644 src/nvim/os/os_win_conpty.c create mode 100644 src/nvim/os/os_win_conpty.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index bc8e64dd41..1d4a01fa04 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -134,6 +134,9 @@ foreach(sfile ${NVIM_SOURCES}) if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$") list(APPEND to_remove ${sfile}) endif() + if(NOT WIN32 AND ${f} MATCHES "^(os_win_conpty.c)$") + list(APPEND to_remove ${sfile}) + endif() endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) @@ -637,6 +640,7 @@ endfunction() set(NO_SINGLE_CHECK_HEADERS os/win_defs.h os/pty_process_win.h + os/os_win_conpty.h ) foreach(hfile ${NVIM_HEADERS}) get_test_target(test-includes "${hfile}" relative_path texe) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 2bb568f025..b9ea36d397 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -11,6 +11,9 @@ #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/shell.h" +#ifdef WIN32 +# include "nvim/os/os_win_conpty.h" +#endif #include "nvim/path.h" #include "nvim/ascii.h" @@ -469,8 +472,34 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, 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); + int stdin_dup_fd = STDIN_FILENO; + int stdout_dup_fd = STDOUT_FILENO; +#ifdef WIN32 + // Strangely, ConPTY doesn't work if stdin and stdout are pipes. So replace + // stdin and stdout with CONIN$ and CONOUT$, respectively. + if (embedded_mode && os_has_conpty_working()) { + stdin_dup_fd = os_dup(STDIN_FILENO); + close(STDIN_FILENO); + const HANDLE conin_handle = + CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + assert(conin_handle != INVALID_HANDLE_VALUE); + const int conin_fd = _open_osfhandle((intptr_t)conin_handle, _O_RDONLY); + assert(conin_fd == STDIN_FILENO); + stdout_dup_fd = os_dup(STDOUT_FILENO); + close(STDOUT_FILENO); + const HANDLE conout_handle = + CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + assert(conout_handle != INVALID_HANDLE_VALUE); + const int conout_fd = _open_osfhandle((intptr_t)conout_handle, 0); + assert(conout_fd == STDOUT_FILENO); + } +#endif + rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0); + wstream_init_fd(&main_loop, &channel->stream.stdio.out, stdout_dup_fd, 0); if (rpc) { rpc_start(channel); diff --git a/src/nvim/main.c b/src/nvim/main.c index c7011f4f4e..33c0466e1a 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -46,6 +46,9 @@ #include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/os/os_defs.h" +#ifdef WIN32 +# include "nvim/os/os_win_conpty.h" +#endif #include "nvim/path.h" #include "nvim/profile.h" #include "nvim/popupmnu.h" @@ -226,6 +229,9 @@ void early_init(void) init_signs(); ui_comp_syn_init(); +#ifdef WIN32 + os_dyn_conpty_init(); +#endif } #ifdef MAKE_LIB diff --git a/src/nvim/option.c b/src/nvim/option.c index 9d3e02949e..591c8234c3 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -76,6 +76,9 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" +#ifdef WIN32 +# include "nvim/os/os_win_conpty.h" +#endif #include "nvim/api/private/helpers.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" @@ -310,6 +313,9 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", "yes:9", NULL }; +#ifdef WIN32 +static char *(p_twt_values[]) = { "conpty", "winpty", "", NULL }; +#endif /// All possible flags for 'shm'. static char_u SHM_ALL[] = { @@ -3282,6 +3288,13 @@ ambw_end: if (!parse_winhl_opt(curwin)) { errmsg = e_invarg; } +#ifdef WIN32 + } else if (varp == &p_twt) { + if (check_opt_strings(*varp, p_twt_values, false) != OK + || (!os_has_conpty_working() && STRCMP(*varp, "conpty") == 0)) { + errmsg = e_invarg; + } +#endif } else { // Options that are a list of flags. p = NULL; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index fcad6836bf..1d4c31dc1c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -654,6 +654,7 @@ EXTERN char_u *p_titleold; ///< 'titleold' EXTERN char_u *p_titlestring; ///< 'titlestring' EXTERN char_u *p_tsr; ///< 'thesaurus' EXTERN int p_tgc; ///< 'termguicolors' +EXTERN char_u *p_twt; ///< 'termwintype' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' EXTERN char_u *p_udir; ///< 'undodir' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index a5a14a1a25..fe970ccba3 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2496,6 +2496,13 @@ return { varname='p_tgc', defaults={if_true={vi=false}} }, + { + full_name='termwintype', abbreviation='twt', + type='string', scope={'global'}, + vi_def=false, + varname='p_twt', + defaults={if_true={vi="",vim=""}} + }, { full_name='terse', type='bool', scope={'global'}, diff --git a/src/nvim/os/os_win_conpty.c b/src/nvim/os/os_win_conpty.c new file mode 100644 index 0000000000..9c6b7ad40f --- /dev/null +++ b/src/nvim/os/os_win_conpty.c @@ -0,0 +1,181 @@ +#include + +#include "nvim/vim.h" +#include "nvim/os/os.h" +#include "nvim/os/os_win_conpty.h" + +#ifndef EXTENDED_STARTUPINFO_PRESENT +# define EXTENDED_STARTUPINFO_PRESENT 0x00080000 +#endif +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 +#endif + +static bool conpty_working = false; + +HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); +HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +HRESULT (WINAPI *pClosePseudoConsole)(HPCON); + +bool os_has_conpty_working(void) +{ + return conpty_working; +} + +void os_dyn_conpty_init(void) +{ + uv_lib_t kernel; + if (uv_dlopen("kernel32.dll", &kernel)) { + uv_dlclose(&kernel); + return; + } + static struct { + char *name; + FARPROC *ptr; + } conpty_entry[] = { + { "CreatePseudoConsole", (FARPROC *)&pCreatePseudoConsole }, + { "ResizePseudoConsole", (FARPROC *)&pResizePseudoConsole }, + { "ClosePseudoConsole", (FARPROC *)&pClosePseudoConsole }, + { NULL, NULL } + }; + for (int i = 0; + conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) { + if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) { + uv_dlclose(&kernel); + return; + } + } + conpty_working = true; +} + +conpty_t *os_conpty_init(char **in_name, char **out_name, + uint16_t width, uint16_t height) +{ + static int count = 0; + conpty_t *conpty_object = xcalloc(1, sizeof(*conpty_object)); + const char *emsg = NULL; + HANDLE in_read = INVALID_HANDLE_VALUE; + HANDLE out_write = INVALID_HANDLE_VALUE; + char buf[MAXPATHL]; + SECURITY_ATTRIBUTES sa = { 0 }; + const DWORD mode = PIPE_ACCESS_INBOUND + | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; + + sa.nLength = sizeof(sa); + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d", + os_get_pid(), count); + *in_name = xstrdup(buf); + if ((in_read = CreateNamedPipeA( + *in_name, + mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 0, + 0, + 30000, + &sa)) == INVALID_HANDLE_VALUE) { + emsg = "create input pipe failed"; + goto failed; + } + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d", + os_get_pid(), count); + *out_name = xstrdup(buf); + if ((out_write = CreateNamedPipeA( + *out_name, + mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 0, + 0, + 30000, + &sa)) == INVALID_HANDLE_VALUE) { + emsg = "create output pipe failed"; + goto failed; + } + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + HRESULT hr; + hr = pCreatePseudoConsole(size, in_read, out_write, 0, &conpty_object->pty); + if (FAILED(hr)) { + emsg = "create psudo console failed"; + goto failed; + } + + conpty_object->si_ex.StartupInfo.cb = sizeof(conpty_object->si_ex); + size_t bytes_required; + InitializeProcThreadAttributeList(NULL, 1, 0, & bytes_required); + conpty_object->si_ex.lpAttributeList = + (PPROC_THREAD_ATTRIBUTE_LIST)xmalloc(bytes_required); + if (!InitializeProcThreadAttributeList( + conpty_object->si_ex.lpAttributeList, + 1, + 0, + &bytes_required)) { + emsg = "InitializeProcThreadAttributeList failed"; + goto failed; + } + if (!UpdateProcThreadAttribute( + conpty_object->si_ex.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + conpty_object->pty, + sizeof(conpty_object->pty), + NULL, + NULL)) { + emsg = "UpdateProcThreadAttribute failed"; + goto failed; + } + count++; + goto finished; + +failed: + ELOG("os_conpty_init:%s : error code: %d", + emsg, os_translate_sys_error((int)GetLastError())); + os_conpty_free(conpty_object); + conpty_object = NULL; +finished: + if (in_read != INVALID_HANDLE_VALUE) { + CloseHandle(in_read); + } + if (out_write != INVALID_HANDLE_VALUE) { + CloseHandle(out_write); + } + return conpty_object; +} + +bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, + wchar_t *name, wchar_t *cmd_line, wchar_t *cwd, + wchar_t *env) +{ + PROCESS_INFORMATION pi = { 0 }; + if (!CreateProcessW( + name, + cmd_line, + NULL, + NULL, + false, + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, + env, + cwd, + &conpty_object->si_ex.StartupInfo, + &pi)) { + return false; + } + *process_handle = pi.hProcess; + return true; +} + +void os_conpty_free(conpty_t * conpty_object) +{ + if (conpty_object != NULL) { + if (conpty_object->si_ex.lpAttributeList != NULL) { + DeleteProcThreadAttributeList(conpty_object->si_ex.lpAttributeList); + xfree(conpty_object->si_ex.lpAttributeList); + } + if (conpty_object->pty != NULL) { + pClosePseudoConsole(conpty_object->pty); + } + } + xfree(conpty_object); +} diff --git a/src/nvim/os/os_win_conpty.h b/src/nvim/os/os_win_conpty.h new file mode 100644 index 0000000000..8e34d394a5 --- /dev/null +++ b/src/nvim/os/os_win_conpty.h @@ -0,0 +1,22 @@ +#ifndef NVIM_OS_OS_WIN_CONPTY_H +#define NVIM_OS_OS_WIN_CONPTY_H + +#ifndef HPCON +# define HPCON VOID * +#endif + +extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens) + (COORD, HANDLE, HANDLE, DWORD, HPCON *); +extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +extern HRESULT (WINAPI *pClosePseudoConsole)(HPCON); + +typedef struct conpty { + HPCON pty; + STARTUPINFOEXW si_ex; +} conpty_t; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/os_win_conpty.h.generated.h" +#endif + +#endif // NVIM_OS_OS_WIN_CONPTY_H diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 183219bd3e..f48fef76d3 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -12,6 +12,7 @@ #include "nvim/memory.h" #include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #include "nvim/os/pty_process_win.h" +#include "nvim/os/os_win_conpty.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/pty_process_win.c.generated.h" @@ -23,6 +24,11 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) PtyProcess *ptyproc = (PtyProcess *)context; Process *proc = (Process *)ptyproc; + if (ptyproc->type == PTY_TYPE_CONPTY + && ptyproc->pty_object.conpty_object != NULL) { + os_conpty_free(ptyproc->pty_object.conpty_object); + ptyproc->pty_object.conpty_object = NULL; + } uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); ptyproc->wait_eof_timer.data = (void *)ptyproc; uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); @@ -38,6 +44,7 @@ int pty_process_spawn(PtyProcess *ptyproc) winpty_config_t *cfg = NULL; winpty_spawn_config_t *spawncfg = NULL; winpty_t *winpty_object = NULL; + conpty_t *conpty_object = NULL; char *in_name = NULL; char *out_name = NULL; HANDLE process_handle = NULL; @@ -49,29 +56,40 @@ int pty_process_spawn(PtyProcess *ptyproc) assert(proc->err.closed); - cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); - if (cfg == NULL) { - emsg = "winpty_config_new failed"; - goto cleanup; + int pty_type = *p_twt; + if (pty_type == 'c' && os_has_conpty_working()) { + if ((conpty_object = + os_conpty_init(&in_name, &out_name, + ptyproc->width, ptyproc->height)) != NULL) { + ptyproc->type = PTY_TYPE_CONPTY; + } } - winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); - winpty_object = winpty_open(cfg, &err); - if (winpty_object == NULL) { - emsg = "winpty_open failed"; - goto cleanup; - } + if (ptyproc->type == PTY_TYPE_WINPTY) { + cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); + if (cfg == NULL) { + emsg = "winpty_config_new failed"; + goto cleanup; + } - status = utf16_to_utf8(winpty_conin_name(winpty_object), -1, &in_name); - if (status != 0) { - emsg = "utf16_to_utf8(winpty_conin_name) failed"; - goto cleanup; - } + winpty_config_set_initial_size(cfg, ptyproc->width, ptyproc->height); + winpty_object = winpty_open(cfg, &err); + if (winpty_object == NULL) { + emsg = "winpty_open failed"; + goto cleanup; + } - status = utf16_to_utf8(winpty_conout_name(winpty_object), -1, &out_name); - if (status != 0) { - emsg = "utf16_to_utf8(winpty_conout_name) failed"; - goto cleanup; + status = utf16_to_utf8(winpty_conin_name(winpty_object), -1, &in_name); + if (status != 0) { + emsg = "utf16_to_utf8(winpty_conin_name) failed"; + goto cleanup; + } + + status = utf16_to_utf8(winpty_conout_name(winpty_object), -1, &out_name); + if (status != 0) { + emsg = "utf16_to_utf8(winpty_conout_name) failed"; + goto cleanup; + } } if (!proc->in.closed) { @@ -107,32 +125,45 @@ int pty_process_spawn(PtyProcess *ptyproc) goto cleanup; } - spawncfg = winpty_spawn_config_new( - WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, - NULL, // Optional application name - cmd_line, - cwd, - NULL, // Optional environment variables - &err); - if (spawncfg == NULL) { - emsg = "winpty_spawn_config_new failed"; - goto cleanup; - } + if (ptyproc->type == PTY_TYPE_CONPTY) { + if (!os_conpty_spawn(conpty_object, + &process_handle, + NULL, + cmd_line, + cwd, + NULL)) { + emsg = "os_conpty_spawn failed"; + status = (int)GetLastError(); + goto cleanup; + } + } else { + spawncfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + NULL, // Optional application name + cmd_line, + cwd, + NULL, // Optional environment variables + &err); + if (spawncfg == NULL) { + emsg = "winpty_spawn_config_new failed"; + goto cleanup; + } - DWORD win_err = 0; - if (!winpty_spawn(winpty_object, - spawncfg, - &process_handle, - NULL, // Optional thread handle - &win_err, - &err)) { - if (win_err) { - status = (int)win_err; - emsg = "failed to spawn process"; - } else { - emsg = "winpty_spawn failed"; + DWORD win_err = 0; + if (!winpty_spawn(winpty_object, + spawncfg, + &process_handle, + NULL, // Optional thread handle + &win_err, + &err)) { + if (win_err) { + status = (int)win_err; + emsg = "failed to spawn process"; + } else { + emsg = "winpty_spawn failed"; + } + goto cleanup; } - goto cleanup; } proc->pid = (int)GetProcessId(process_handle); @@ -152,9 +183,14 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_run(&proc->loop->uv, UV_RUN_ONCE); } - ptyproc->winpty_object = winpty_object; + if (ptyproc->type == PTY_TYPE_CONPTY) { + ptyproc->pty_object.conpty_object = conpty_object; + } else { + ptyproc->pty_object.winpty_object = winpty_object; + } ptyproc->process_handle = process_handle; winpty_object = NULL; + conpty_object = NULL; process_handle = NULL; cleanup: @@ -171,6 +207,7 @@ cleanup: winpty_config_free(cfg); winpty_spawn_config_free(spawncfg); winpty_free(winpty_object); + os_conpty_free(conpty_object); xfree(in_name); xfree(out_name); if (process_handle != NULL) { @@ -192,8 +229,18 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->winpty_object != NULL) { - winpty_set_size(ptyproc->winpty_object, width, height, NULL); + if (ptyproc->type == PTY_TYPE_CONPTY + && ptyproc->pty_object.conpty_object != NULL) { + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + if (pResizePseudoConsole( + ptyproc->pty_object.conpty_object->pty, size) != S_OK) { + ELOG("ResizePseudoConsoel failed: error code: %d", + os_translate_sys_error((int)GetLastError())); + } + } else if (ptyproc->pty_object.winpty_object != NULL) { + winpty_set_size(ptyproc->pty_object.winpty_object, width, height, NULL); } } @@ -212,9 +259,10 @@ void pty_process_close(PtyProcess *ptyproc) void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->winpty_object != NULL) { - winpty_free(ptyproc->winpty_object); - ptyproc->winpty_object = NULL; + if (ptyproc->type == PTY_TYPE_WINPTY + && ptyproc->pty_object.winpty_object != NULL) { + winpty_free(ptyproc->pty_object.winpty_object); + ptyproc->pty_object.winpty_object = NULL; } } diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 1a4019e654..49a0d420ad 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -6,12 +6,22 @@ #include "nvim/event/process.h" #include "nvim/lib/queue.h" +#include "nvim/os/os_win_conpty.h" + +typedef enum { + PTY_TYPE_WINPTY, + PTY_TYPE_CONPTY +} pty_type_t; typedef struct pty_process { Process process; char *term_name; uint16_t width, height; - winpty_t *winpty_object; + union { + winpty_t *winpty_object; + conpty_t *conpty_object; + } pty_object; + pty_type_t type; HANDLE finish_wait; HANDLE process_handle; uv_timer_t wait_eof_timer; @@ -30,7 +40,8 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.term_name = NULL; rv.width = 80; rv.height = 24; - rv.winpty_object = NULL; + rv.pty_object.winpty_object = NULL; + rv.type = PTY_TYPE_WINPTY; rv.finish_wait = NULL; rv.process_handle = NULL; return rv; -- cgit From ed37d1081c8adbd33832b4140ba5368fa876bdb8 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 15 Nov 2019 12:51:41 +0900 Subject: Change union name from pty_object to object Co-Authored-By: Justin M. Keyes --- src/nvim/os/pty_process_win.c | 24 ++++++++++++------------ src/nvim/os/pty_process_win.h | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index f48fef76d3..0fbe3e5fad 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -25,9 +25,9 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) Process *proc = (Process *)ptyproc; if (ptyproc->type == PTY_TYPE_CONPTY - && ptyproc->pty_object.conpty_object != NULL) { - os_conpty_free(ptyproc->pty_object.conpty_object); - ptyproc->pty_object.conpty_object = NULL; + && ptyproc->object.conpty != NULL) { + os_conpty_free(ptyproc->object.conpty); + ptyproc->object.conpty = NULL; } uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); ptyproc->wait_eof_timer.data = (void *)ptyproc; @@ -184,9 +184,9 @@ int pty_process_spawn(PtyProcess *ptyproc) } if (ptyproc->type == PTY_TYPE_CONPTY) { - ptyproc->pty_object.conpty_object = conpty_object; + ptyproc->object.conpty = conpty_object; } else { - ptyproc->pty_object.winpty_object = winpty_object; + ptyproc->object.winpty = winpty_object; } ptyproc->process_handle = process_handle; winpty_object = NULL; @@ -230,17 +230,17 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width, FUNC_ATTR_NONNULL_ALL { if (ptyproc->type == PTY_TYPE_CONPTY - && ptyproc->pty_object.conpty_object != NULL) { + && ptyproc->object.conpty != NULL) { assert(width <= SHRT_MAX); assert(height <= SHRT_MAX); COORD size = { (int16_t)width, (int16_t)height }; if (pResizePseudoConsole( - ptyproc->pty_object.conpty_object->pty, size) != S_OK) { + ptyproc->object.conpty->pty, size) != S_OK) { ELOG("ResizePseudoConsoel failed: error code: %d", os_translate_sys_error((int)GetLastError())); } - } else if (ptyproc->pty_object.winpty_object != NULL) { - winpty_set_size(ptyproc->pty_object.winpty_object, width, height, NULL); + } else if (ptyproc->object.winpty != NULL) { + winpty_set_size(ptyproc->object.winpty, width, height, NULL); } } @@ -260,9 +260,9 @@ void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { if (ptyproc->type == PTY_TYPE_WINPTY - && ptyproc->pty_object.winpty_object != NULL) { - winpty_free(ptyproc->pty_object.winpty_object); - ptyproc->pty_object.winpty_object = NULL; + && ptyproc->object.winpty != NULL) { + winpty_free(ptyproc->object.winpty); + ptyproc->object.winpty = NULL; } } diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 49a0d420ad..c7eaa23b1b 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -18,9 +18,9 @@ typedef struct pty_process { char *term_name; uint16_t width, height; union { - winpty_t *winpty_object; - conpty_t *conpty_object; - } pty_object; + winpty_t *winpty; + conpty_t *conpty; + } object; pty_type_t type; HANDLE finish_wait; HANDLE process_handle; @@ -40,7 +40,7 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.term_name = NULL; rv.width = 80; rv.height = 24; - rv.pty_object.winpty_object = NULL; + rv.object.winpty = NULL; rv.type = PTY_TYPE_WINPTY; rv.finish_wait = NULL; rv.process_handle = NULL; -- cgit From 7aff0340e1f0550ff1c04dc17474d7cb6e57a89b Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 15 Nov 2019 13:03:25 +0900 Subject: Move ConPTY resize to os_win_conpty.c --- src/nvim/os/os_win_conpty.c | 14 +++++++++++++- src/nvim/os/pty_process_win.c | 9 +-------- 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/os/os_win_conpty.c b/src/nvim/os/os_win_conpty.c index 9c6b7ad40f..bf6ec62818 100644 --- a/src/nvim/os/os_win_conpty.c +++ b/src/nvim/os/os_win_conpty.c @@ -166,7 +166,19 @@ bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, return true; } -void os_conpty_free(conpty_t * conpty_object) +void os_conpty_set_size(conpty_t *conpty_object, + uint16_t width, uint16_t height) +{ + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + if (pResizePseudoConsole(conpty_object->pty, size) != S_OK) { + ELOG("ResizePseudoConsoel failed: error code: %d", + os_translate_sys_error((int)GetLastError())); + } +} + +void os_conpty_free(conpty_t *conpty_object) { if (conpty_object != NULL) { if (conpty_object->si_ex.lpAttributeList != NULL) { diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 0fbe3e5fad..ffdececf94 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -231,14 +231,7 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width, { if (ptyproc->type == PTY_TYPE_CONPTY && ptyproc->object.conpty != NULL) { - assert(width <= SHRT_MAX); - assert(height <= SHRT_MAX); - COORD size = { (int16_t)width, (int16_t)height }; - if (pResizePseudoConsole( - ptyproc->object.conpty->pty, size) != S_OK) { - ELOG("ResizePseudoConsoel failed: error code: %d", - os_translate_sys_error((int)GetLastError())); - } + os_conpty_set_size(ptyproc->object.conpty, width, height); } else if (ptyproc->object.winpty != NULL) { winpty_set_size(ptyproc->object.winpty, width, height, NULL); } -- cgit From b4ff583277ce16584f20868cef6697f52bebc7d4 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 15 Nov 2019 13:12:48 +0900 Subject: Minor changes in reviewer's point --- src/nvim/os/os_win_conpty.c | 3 +++ src/nvim/os/pty_process_win.c | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/os/os_win_conpty.c b/src/nvim/os/os_win_conpty.c index bf6ec62818..887286b59f 100644 --- a/src/nvim/os/os_win_conpty.c +++ b/src/nvim/os/os_win_conpty.c @@ -1,3 +1,6 @@ +// 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 #include "nvim/vim.h" diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index ffdececf94..0cd730401a 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -183,11 +183,9 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_run(&proc->loop->uv, UV_RUN_ONCE); } - if (ptyproc->type == PTY_TYPE_CONPTY) { - ptyproc->object.conpty = conpty_object; - } else { - ptyproc->object.winpty = winpty_object; - } + (ptyproc->type == PTY_TYPE_CONPTY) ? + (void *)(ptyproc->object.conpty = conpty_object) : + (void *)(ptyproc->object.winpty = winpty_object); ptyproc->process_handle = process_handle; winpty_object = NULL; conpty_object = NULL; -- cgit From b25e42f7989aa0a071e3132ce992ab9a6251594e Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 15 Nov 2019 14:42:49 +0900 Subject: Fix function prototype --- src/nvim/os/os_win_conpty.c | 2 +- src/nvim/os/os_win_conpty.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/os/os_win_conpty.c b/src/nvim/os/os_win_conpty.c index 887286b59f..a73b1b65a0 100644 --- a/src/nvim/os/os_win_conpty.c +++ b/src/nvim/os/os_win_conpty.c @@ -18,7 +18,7 @@ static bool conpty_working = false; HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); -HRESULT (WINAPI *pClosePseudoConsole)(HPCON); +void (WINAPI *pClosePseudoConsole)(HPCON); bool os_has_conpty_working(void) { diff --git a/src/nvim/os/os_win_conpty.h b/src/nvim/os/os_win_conpty.h index 8e34d394a5..8a5f45e2cd 100644 --- a/src/nvim/os/os_win_conpty.h +++ b/src/nvim/os/os_win_conpty.h @@ -8,7 +8,7 @@ extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens) (COORD, HANDLE, HANDLE, DWORD, HPCON *); extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); -extern HRESULT (WINAPI *pClosePseudoConsole)(HPCON); +extern void (WINAPI *pClosePseudoConsole)(HPCON); typedef struct conpty { HPCON pty; -- cgit From 59ae38a9196d596e2fe511eeb216e314bfc3dac7 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 16 Nov 2019 12:59:07 +0900 Subject: Change to use TriState instead of bool Co-Authored-By: Justin M. Keyes --- src/nvim/main.c | 6 ------ src/nvim/os/os_win_conpty.c | 17 ++++++++++------- 2 files changed, 10 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index 33c0466e1a..c7011f4f4e 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -46,9 +46,6 @@ #include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/os/os_defs.h" -#ifdef WIN32 -# include "nvim/os/os_win_conpty.h" -#endif #include "nvim/path.h" #include "nvim/profile.h" #include "nvim/popupmnu.h" @@ -229,9 +226,6 @@ void early_init(void) init_signs(); ui_comp_syn_init(); -#ifdef WIN32 - os_dyn_conpty_init(); -#endif } #ifdef MAKE_LIB diff --git a/src/nvim/os/os_win_conpty.c b/src/nvim/os/os_win_conpty.c index a73b1b65a0..3d5ba83523 100644 --- a/src/nvim/os/os_win_conpty.c +++ b/src/nvim/os/os_win_conpty.c @@ -14,23 +14,26 @@ # define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 #endif -static bool conpty_working = false; - HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); void (WINAPI *pClosePseudoConsole)(HPCON); bool os_has_conpty_working(void) { - return conpty_working; + static TriState has_conpty = kNone; + if (has_conpty == kNone) { + has_conpty = os_dyn_conpty_init(); + } + + return has_conpty == kTrue; } -void os_dyn_conpty_init(void) +TriState os_dyn_conpty_init(void) { uv_lib_t kernel; if (uv_dlopen("kernel32.dll", &kernel)) { uv_dlclose(&kernel); - return; + return kFalse; } static struct { char *name; @@ -45,10 +48,10 @@ void os_dyn_conpty_init(void) conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) { if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) { uv_dlclose(&kernel); - return; + return kFalse; } } - conpty_working = true; + return kTrue; } conpty_t *os_conpty_init(char **in_name, char **out_name, -- cgit From 60c7eabb2feeb95f405deca6680bdd59113edcaa Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 16 Nov 2019 13:16:52 +0900 Subject: Change enum to a name that follows naming convention --- src/nvim/os/pty_process_win.c | 14 +++++++------- src/nvim/os/pty_process_win.h | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 0cd730401a..7108d9c049 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -24,7 +24,7 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused) PtyProcess *ptyproc = (PtyProcess *)context; Process *proc = (Process *)ptyproc; - if (ptyproc->type == PTY_TYPE_CONPTY + if (ptyproc->type == kConpty && ptyproc->object.conpty != NULL) { os_conpty_free(ptyproc->object.conpty); ptyproc->object.conpty = NULL; @@ -61,11 +61,11 @@ int pty_process_spawn(PtyProcess *ptyproc) if ((conpty_object = os_conpty_init(&in_name, &out_name, ptyproc->width, ptyproc->height)) != NULL) { - ptyproc->type = PTY_TYPE_CONPTY; + ptyproc->type = kConpty; } } - if (ptyproc->type == PTY_TYPE_WINPTY) { + if (ptyproc->type == kWinpty) { cfg = winpty_config_new(WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION, &err); if (cfg == NULL) { emsg = "winpty_config_new failed"; @@ -125,7 +125,7 @@ int pty_process_spawn(PtyProcess *ptyproc) goto cleanup; } - if (ptyproc->type == PTY_TYPE_CONPTY) { + if (ptyproc->type == kConpty) { if (!os_conpty_spawn(conpty_object, &process_handle, NULL, @@ -183,7 +183,7 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_run(&proc->loop->uv, UV_RUN_ONCE); } - (ptyproc->type == PTY_TYPE_CONPTY) ? + (ptyproc->type == kConpty) ? (void *)(ptyproc->object.conpty = conpty_object) : (void *)(ptyproc->object.winpty = winpty_object); ptyproc->process_handle = process_handle; @@ -227,7 +227,7 @@ void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->type == PTY_TYPE_CONPTY + if (ptyproc->type == kConpty && ptyproc->object.conpty != NULL) { os_conpty_set_size(ptyproc->object.conpty, width, height); } else if (ptyproc->object.winpty != NULL) { @@ -250,7 +250,7 @@ void pty_process_close(PtyProcess *ptyproc) void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { - if (ptyproc->type == PTY_TYPE_WINPTY + if (ptyproc->type == kWinpty && ptyproc->object.winpty != NULL) { winpty_free(ptyproc->object.winpty); ptyproc->object.winpty = NULL; diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index c7eaa23b1b..04c8439c6c 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -9,9 +9,9 @@ #include "nvim/os/os_win_conpty.h" typedef enum { - PTY_TYPE_WINPTY, - PTY_TYPE_CONPTY -} pty_type_t; + kWinpty, + kConpty +} PtyType; typedef struct pty_process { Process process; @@ -21,7 +21,7 @@ typedef struct pty_process { winpty_t *winpty; conpty_t *conpty; } object; - pty_type_t type; + PtyType type; HANDLE finish_wait; HANDLE process_handle; uv_timer_t wait_eof_timer; @@ -41,7 +41,7 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data) rv.width = 80; rv.height = 24; rv.object.winpty = NULL; - rv.type = PTY_TYPE_WINPTY; + rv.type = kWinpty; rv.finish_wait = NULL; rv.process_handle = NULL; return rv; -- cgit From 2c8016c7048051da9afd809b580cf8f2d2c69614 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 16 Nov 2019 13:45:37 +0900 Subject: Add stdin, stdout replacement functions --- src/nvim/CMakeLists.txt | 4 ++++ src/nvim/channel.c | 19 +++---------------- src/nvim/main.c | 11 ++++------- src/nvim/os/os_win_console.c | 38 ++++++++++++++++++++++++++++++++++++++ src/nvim/os/os_win_console.h | 8 ++++++++ src/nvim/tui/input.c | 11 ++++------- 6 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 src/nvim/os/os_win_console.c create mode 100644 src/nvim/os/os_win_console.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 1d4a01fa04..62d45ff4a3 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -137,6 +137,9 @@ foreach(sfile ${NVIM_SOURCES}) if(NOT WIN32 AND ${f} MATCHES "^(os_win_conpty.c)$") list(APPEND to_remove ${sfile}) endif() + if(NOT WIN32 AND ${f} MATCHES "^(os_win_console.c)$") + list(APPEND to_remove ${sfile}) + endif() endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) @@ -641,6 +644,7 @@ set(NO_SINGLE_CHECK_HEADERS os/win_defs.h os/pty_process_win.h os/os_win_conpty.h + os/os_win_console.h ) foreach(hfile ${NVIM_HEADERS}) get_test_target(test-includes "${hfile}" relative_path texe) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index b9ea36d397..b346845c66 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -13,6 +13,7 @@ #include "nvim/os/shell.h" #ifdef WIN32 # include "nvim/os/os_win_conpty.h" +# include "nvim/os/os_win_console.h" #endif #include "nvim/path.h" #include "nvim/ascii.h" @@ -479,23 +480,9 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, // stdin and stdout with CONIN$ and CONOUT$, respectively. if (embedded_mode && os_has_conpty_working()) { stdin_dup_fd = os_dup(STDIN_FILENO); - close(STDIN_FILENO); - const HANDLE conin_handle = - CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, - OPEN_EXISTING, 0, (HANDLE)NULL); - assert(conin_handle != INVALID_HANDLE_VALUE); - const int conin_fd = _open_osfhandle((intptr_t)conin_handle, _O_RDONLY); - assert(conin_fd == STDIN_FILENO); + os_replace_stdin_to_conin(); stdout_dup_fd = os_dup(STDOUT_FILENO); - close(STDOUT_FILENO); - const HANDLE conout_handle = - CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, - OPEN_EXISTING, 0, (HANDLE)NULL); - assert(conout_handle != INVALID_HANDLE_VALUE); - const int conout_fd = _open_osfhandle((intptr_t)conout_handle, 0); - assert(conout_fd == STDOUT_FILENO); + os_replace_stdout_to_conout(); } #endif rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0); diff --git a/src/nvim/main.c b/src/nvim/main.c index c7011f4f4e..be279b449a 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -64,6 +64,9 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/fileio.h" +#ifdef WIN32 +# include "nvim/os/os_win_console.h" +#endif #include "nvim/event/loop.h" #include "nvim/os/signal.h" #include "nvim/event/process.h" @@ -1120,13 +1123,7 @@ scripterror: const int stdin_dup_fd = os_dup(STDIN_FILENO); #ifdef WIN32 // Replace the original stdin with the console input handle. - close(STDIN_FILENO); - const HANDLE conin_handle = - CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, - OPEN_EXISTING, 0, (HANDLE)NULL); - const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY); - assert(conin_fd == STDIN_FILENO); + os_replace_stdin_to_conin(); #endif FileDescriptor *const stdin_dup = file_open_fd_new( &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c new file mode 100644 index 0000000000..50127248a4 --- /dev/null +++ b/src/nvim/os/os_win_console.c @@ -0,0 +1,38 @@ +// 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/vim.h" + +int os_get_conin_fd(void) +{ + const HANDLE conin_handle = CreateFile("CONIN$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + assert(conin_handle != INVALID_HANDLE_VALUE); + int conin_fd = _open_osfhandle((intptr_t)conin_handle, _O_RDONLY); + assert(conin_fd != -1); + return conin_fd; +} + +void os_replace_stdin_to_conin(void) +{ + close(STDIN_FILENO); + const int conin_fd = os_get_conin_fd(); + assert(conin_fd == STDIN_FILENO); +} + +void os_replace_stdout_to_conout(void) +{ + close(STDOUT_FILENO); + const HANDLE conout_handle = + CreateFile("CONOUT$", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + assert(conout_handle != INVALID_HANDLE_VALUE); + const int conout_fd = _open_osfhandle((intptr_t)conout_handle, 0); + assert(conout_fd == STDOUT_FILENO); +} diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h new file mode 100644 index 0000000000..154ec83d8a --- /dev/null +++ b/src/nvim/os/os_win_console.h @@ -0,0 +1,8 @@ +#ifndef NVIM_OS_OS_WIN_CONSOLE_H +#define NVIM_OS_OS_WIN_CONSOLE_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/os_win_console.h.generated.h" +#endif + +#endif // NVIM_OS_OS_WIN_CONSOLE_H diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index c71378463f..951cb50c3c 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -14,6 +14,9 @@ #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/input.h" +#ifdef WIN32 +# include "nvim/os/os_win_console.h" +#endif #include "nvim/event/rstream.h" #define KEY_BUFFER_SIZE 0xfff @@ -37,13 +40,7 @@ void tinput_init(TermInput *input, Loop *loop) // ls *.md | xargs nvim #ifdef WIN32 if (!os_isatty(input->in_fd)) { - const HANDLE conin_handle = CreateFile("CONIN$", - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - (LPSECURITY_ATTRIBUTES)NULL, - OPEN_EXISTING, 0, (HANDLE)NULL); - input->in_fd = _open_osfhandle(conin_handle, _O_RDONLY); - assert(input->in_fd != -1); + input->in_fd = os_get_conin_fd(); } #else if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) { -- cgit From 8f91d709b0006e3fcfa4cb59abd9b77938096d76 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 16 Nov 2019 13:50:52 +0900 Subject: Rename from os_win_conpty.{c,h} to pty_conpty_win.{c,h} --- src/nvim/CMakeLists.txt | 4 +- src/nvim/channel.c | 2 +- src/nvim/option.c | 2 +- src/nvim/os/os_win_conpty.c | 199 ------------------------------------------ src/nvim/os/os_win_conpty.h | 22 ----- src/nvim/os/pty_conpty_win.c | 199 ++++++++++++++++++++++++++++++++++++++++++ src/nvim/os/pty_conpty_win.h | 22 +++++ src/nvim/os/pty_process_win.c | 2 +- src/nvim/os/pty_process_win.h | 2 +- 9 files changed, 227 insertions(+), 227 deletions(-) delete mode 100644 src/nvim/os/os_win_conpty.c delete mode 100644 src/nvim/os/os_win_conpty.h create mode 100644 src/nvim/os/pty_conpty_win.c create mode 100644 src/nvim/os/pty_conpty_win.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 62d45ff4a3..089dd537e9 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -134,7 +134,7 @@ foreach(sfile ${NVIM_SOURCES}) if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$") list(APPEND to_remove ${sfile}) endif() - if(NOT WIN32 AND ${f} MATCHES "^(os_win_conpty.c)$") + if(NOT WIN32 AND ${f} MATCHES "^(pty_conpty_win.c)$") list(APPEND to_remove ${sfile}) endif() if(NOT WIN32 AND ${f} MATCHES "^(os_win_console.c)$") @@ -643,7 +643,7 @@ endfunction() set(NO_SINGLE_CHECK_HEADERS os/win_defs.h os/pty_process_win.h - os/os_win_conpty.h + os/pty_conpty_win.h os/os_win_console.h ) foreach(hfile ${NVIM_HEADERS}) diff --git a/src/nvim/channel.c b/src/nvim/channel.c index b346845c66..d3e2977eae 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -12,7 +12,7 @@ #include "nvim/msgpack_rpc/server.h" #include "nvim/os/shell.h" #ifdef WIN32 -# include "nvim/os/os_win_conpty.h" +# include "nvim/os/pty_conpty_win.h" # include "nvim/os/os_win_console.h" #endif #include "nvim/path.h" diff --git a/src/nvim/option.c b/src/nvim/option.c index 591c8234c3..014eb676a0 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -77,7 +77,7 @@ #include "nvim/window.h" #include "nvim/os/os.h" #ifdef WIN32 -# include "nvim/os/os_win_conpty.h" +# include "nvim/os/pty_conpty_win.h" #endif #include "nvim/api/private/helpers.h" #include "nvim/os/input.h" diff --git a/src/nvim/os/os_win_conpty.c b/src/nvim/os/os_win_conpty.c deleted file mode 100644 index 3d5ba83523..0000000000 --- a/src/nvim/os/os_win_conpty.c +++ /dev/null @@ -1,199 +0,0 @@ -// 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 - -#include "nvim/vim.h" -#include "nvim/os/os.h" -#include "nvim/os/os_win_conpty.h" - -#ifndef EXTENDED_STARTUPINFO_PRESENT -# define EXTENDED_STARTUPINFO_PRESENT 0x00080000 -#endif -#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE -# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 -#endif - -HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); -HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); -void (WINAPI *pClosePseudoConsole)(HPCON); - -bool os_has_conpty_working(void) -{ - static TriState has_conpty = kNone; - if (has_conpty == kNone) { - has_conpty = os_dyn_conpty_init(); - } - - return has_conpty == kTrue; -} - -TriState os_dyn_conpty_init(void) -{ - uv_lib_t kernel; - if (uv_dlopen("kernel32.dll", &kernel)) { - uv_dlclose(&kernel); - return kFalse; - } - static struct { - char *name; - FARPROC *ptr; - } conpty_entry[] = { - { "CreatePseudoConsole", (FARPROC *)&pCreatePseudoConsole }, - { "ResizePseudoConsole", (FARPROC *)&pResizePseudoConsole }, - { "ClosePseudoConsole", (FARPROC *)&pClosePseudoConsole }, - { NULL, NULL } - }; - for (int i = 0; - conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) { - if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) { - uv_dlclose(&kernel); - return kFalse; - } - } - return kTrue; -} - -conpty_t *os_conpty_init(char **in_name, char **out_name, - uint16_t width, uint16_t height) -{ - static int count = 0; - conpty_t *conpty_object = xcalloc(1, sizeof(*conpty_object)); - const char *emsg = NULL; - HANDLE in_read = INVALID_HANDLE_VALUE; - HANDLE out_write = INVALID_HANDLE_VALUE; - char buf[MAXPATHL]; - SECURITY_ATTRIBUTES sa = { 0 }; - const DWORD mode = PIPE_ACCESS_INBOUND - | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; - - sa.nLength = sizeof(sa); - snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d", - os_get_pid(), count); - *in_name = xstrdup(buf); - if ((in_read = CreateNamedPipeA( - *in_name, - mode, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, - 0, - 0, - 30000, - &sa)) == INVALID_HANDLE_VALUE) { - emsg = "create input pipe failed"; - goto failed; - } - snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d", - os_get_pid(), count); - *out_name = xstrdup(buf); - if ((out_write = CreateNamedPipeA( - *out_name, - mode, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - 1, - 0, - 0, - 30000, - &sa)) == INVALID_HANDLE_VALUE) { - emsg = "create output pipe failed"; - goto failed; - } - assert(width <= SHRT_MAX); - assert(height <= SHRT_MAX); - COORD size = { (int16_t)width, (int16_t)height }; - HRESULT hr; - hr = pCreatePseudoConsole(size, in_read, out_write, 0, &conpty_object->pty); - if (FAILED(hr)) { - emsg = "create psudo console failed"; - goto failed; - } - - conpty_object->si_ex.StartupInfo.cb = sizeof(conpty_object->si_ex); - size_t bytes_required; - InitializeProcThreadAttributeList(NULL, 1, 0, & bytes_required); - conpty_object->si_ex.lpAttributeList = - (PPROC_THREAD_ATTRIBUTE_LIST)xmalloc(bytes_required); - if (!InitializeProcThreadAttributeList( - conpty_object->si_ex.lpAttributeList, - 1, - 0, - &bytes_required)) { - emsg = "InitializeProcThreadAttributeList failed"; - goto failed; - } - if (!UpdateProcThreadAttribute( - conpty_object->si_ex.lpAttributeList, - 0, - PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, - conpty_object->pty, - sizeof(conpty_object->pty), - NULL, - NULL)) { - emsg = "UpdateProcThreadAttribute failed"; - goto failed; - } - count++; - goto finished; - -failed: - ELOG("os_conpty_init:%s : error code: %d", - emsg, os_translate_sys_error((int)GetLastError())); - os_conpty_free(conpty_object); - conpty_object = NULL; -finished: - if (in_read != INVALID_HANDLE_VALUE) { - CloseHandle(in_read); - } - if (out_write != INVALID_HANDLE_VALUE) { - CloseHandle(out_write); - } - return conpty_object; -} - -bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, - wchar_t *name, wchar_t *cmd_line, wchar_t *cwd, - wchar_t *env) -{ - PROCESS_INFORMATION pi = { 0 }; - if (!CreateProcessW( - name, - cmd_line, - NULL, - NULL, - false, - EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, - env, - cwd, - &conpty_object->si_ex.StartupInfo, - &pi)) { - return false; - } - *process_handle = pi.hProcess; - return true; -} - -void os_conpty_set_size(conpty_t *conpty_object, - uint16_t width, uint16_t height) -{ - assert(width <= SHRT_MAX); - assert(height <= SHRT_MAX); - COORD size = { (int16_t)width, (int16_t)height }; - if (pResizePseudoConsole(conpty_object->pty, size) != S_OK) { - ELOG("ResizePseudoConsoel failed: error code: %d", - os_translate_sys_error((int)GetLastError())); - } -} - -void os_conpty_free(conpty_t *conpty_object) -{ - if (conpty_object != NULL) { - if (conpty_object->si_ex.lpAttributeList != NULL) { - DeleteProcThreadAttributeList(conpty_object->si_ex.lpAttributeList); - xfree(conpty_object->si_ex.lpAttributeList); - } - if (conpty_object->pty != NULL) { - pClosePseudoConsole(conpty_object->pty); - } - } - xfree(conpty_object); -} diff --git a/src/nvim/os/os_win_conpty.h b/src/nvim/os/os_win_conpty.h deleted file mode 100644 index 8a5f45e2cd..0000000000 --- a/src/nvim/os/os_win_conpty.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef NVIM_OS_OS_WIN_CONPTY_H -#define NVIM_OS_OS_WIN_CONPTY_H - -#ifndef HPCON -# define HPCON VOID * -#endif - -extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens) - (COORD, HANDLE, HANDLE, DWORD, HPCON *); -extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); -extern void (WINAPI *pClosePseudoConsole)(HPCON); - -typedef struct conpty { - HPCON pty; - STARTUPINFOEXW si_ex; -} conpty_t; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/os_win_conpty.h.generated.h" -#endif - -#endif // NVIM_OS_OS_WIN_CONPTY_H diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c new file mode 100644 index 0000000000..5bcadd6490 --- /dev/null +++ b/src/nvim/os/pty_conpty_win.c @@ -0,0 +1,199 @@ +// 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 + +#include "nvim/vim.h" +#include "nvim/os/os.h" +#include "nvim/os/pty_conpty_win.h" + +#ifndef EXTENDED_STARTUPINFO_PRESENT +# define EXTENDED_STARTUPINFO_PRESENT 0x00080000 +#endif +#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE +# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 +#endif + +HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *); +HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +void (WINAPI *pClosePseudoConsole)(HPCON); + +bool os_has_conpty_working(void) +{ + static TriState has_conpty = kNone; + if (has_conpty == kNone) { + has_conpty = os_dyn_conpty_init(); + } + + return has_conpty == kTrue; +} + +TriState os_dyn_conpty_init(void) +{ + uv_lib_t kernel; + if (uv_dlopen("kernel32.dll", &kernel)) { + uv_dlclose(&kernel); + return kFalse; + } + static struct { + char *name; + FARPROC *ptr; + } conpty_entry[] = { + { "CreatePseudoConsole", (FARPROC *)&pCreatePseudoConsole }, + { "ResizePseudoConsole", (FARPROC *)&pResizePseudoConsole }, + { "ClosePseudoConsole", (FARPROC *)&pClosePseudoConsole }, + { NULL, NULL } + }; + for (int i = 0; + conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) { + if (uv_dlsym(&kernel, conpty_entry[i].name, (void **)conpty_entry[i].ptr)) { + uv_dlclose(&kernel); + return kFalse; + } + } + return kTrue; +} + +conpty_t *os_conpty_init(char **in_name, char **out_name, + uint16_t width, uint16_t height) +{ + static int count = 0; + conpty_t *conpty_object = xcalloc(1, sizeof(*conpty_object)); + const char *emsg = NULL; + HANDLE in_read = INVALID_HANDLE_VALUE; + HANDLE out_write = INVALID_HANDLE_VALUE; + char buf[MAXPATHL]; + SECURITY_ATTRIBUTES sa = { 0 }; + const DWORD mode = PIPE_ACCESS_INBOUND + | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; + + sa.nLength = sizeof(sa); + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d", + os_get_pid(), count); + *in_name = xstrdup(buf); + if ((in_read = CreateNamedPipeA( + *in_name, + mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 0, + 0, + 30000, + &sa)) == INVALID_HANDLE_VALUE) { + emsg = "create input pipe failed"; + goto failed; + } + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d", + os_get_pid(), count); + *out_name = xstrdup(buf); + if ((out_write = CreateNamedPipeA( + *out_name, + mode, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 0, + 0, + 30000, + &sa)) == INVALID_HANDLE_VALUE) { + emsg = "create output pipe failed"; + goto failed; + } + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + HRESULT hr; + hr = pCreatePseudoConsole(size, in_read, out_write, 0, &conpty_object->pty); + if (FAILED(hr)) { + emsg = "create psudo console failed"; + goto failed; + } + + conpty_object->si_ex.StartupInfo.cb = sizeof(conpty_object->si_ex); + size_t bytes_required; + InitializeProcThreadAttributeList(NULL, 1, 0, & bytes_required); + conpty_object->si_ex.lpAttributeList = + (PPROC_THREAD_ATTRIBUTE_LIST)xmalloc(bytes_required); + if (!InitializeProcThreadAttributeList( + conpty_object->si_ex.lpAttributeList, + 1, + 0, + &bytes_required)) { + emsg = "InitializeProcThreadAttributeList failed"; + goto failed; + } + if (!UpdateProcThreadAttribute( + conpty_object->si_ex.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + conpty_object->pty, + sizeof(conpty_object->pty), + NULL, + NULL)) { + emsg = "UpdateProcThreadAttribute failed"; + goto failed; + } + count++; + goto finished; + +failed: + ELOG("os_conpty_init:%s : error code: %d", + emsg, os_translate_sys_error((int)GetLastError())); + os_conpty_free(conpty_object); + conpty_object = NULL; +finished: + if (in_read != INVALID_HANDLE_VALUE) { + CloseHandle(in_read); + } + if (out_write != INVALID_HANDLE_VALUE) { + CloseHandle(out_write); + } + return conpty_object; +} + +bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, + wchar_t *name, wchar_t *cmd_line, wchar_t *cwd, + wchar_t *env) +{ + PROCESS_INFORMATION pi = { 0 }; + if (!CreateProcessW( + name, + cmd_line, + NULL, + NULL, + false, + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, + env, + cwd, + &conpty_object->si_ex.StartupInfo, + &pi)) { + return false; + } + *process_handle = pi.hProcess; + return true; +} + +void os_conpty_set_size(conpty_t *conpty_object, + uint16_t width, uint16_t height) +{ + assert(width <= SHRT_MAX); + assert(height <= SHRT_MAX); + COORD size = { (int16_t)width, (int16_t)height }; + if (pResizePseudoConsole(conpty_object->pty, size) != S_OK) { + ELOG("ResizePseudoConsoel failed: error code: %d", + os_translate_sys_error((int)GetLastError())); + } +} + +void os_conpty_free(conpty_t *conpty_object) +{ + if (conpty_object != NULL) { + if (conpty_object->si_ex.lpAttributeList != NULL) { + DeleteProcThreadAttributeList(conpty_object->si_ex.lpAttributeList); + xfree(conpty_object->si_ex.lpAttributeList); + } + if (conpty_object->pty != NULL) { + pClosePseudoConsole(conpty_object->pty); + } + } + xfree(conpty_object); +} diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h new file mode 100644 index 0000000000..c243db4fa5 --- /dev/null +++ b/src/nvim/os/pty_conpty_win.h @@ -0,0 +1,22 @@ +#ifndef NVIM_OS_PTY_CONPTY_WIN_H +#define NVIM_OS_PTY_CONPTY_WIN_H + +#ifndef HPCON +# define HPCON VOID * +#endif + +extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens) + (COORD, HANDLE, HANDLE, DWORD, HPCON *); +extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); +extern void (WINAPI *pClosePseudoConsole)(HPCON); + +typedef struct conpty { + HPCON pty; + STARTUPINFOEXW si_ex; +} conpty_t; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/pty_conpty_win.h.generated.h" +#endif + +#endif // NVIM_OS_PTY_CONPTY_WIN_H diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 7108d9c049..64d4408ece 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -12,7 +12,7 @@ #include "nvim/memory.h" #include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #include "nvim/os/pty_process_win.h" -#include "nvim/os/os_win_conpty.h" +#include "nvim/os/pty_conpty_win.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/pty_process_win.c.generated.h" diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h index 04c8439c6c..8ad5ba7286 100644 --- a/src/nvim/os/pty_process_win.h +++ b/src/nvim/os/pty_process_win.h @@ -6,7 +6,7 @@ #include "nvim/event/process.h" #include "nvim/lib/queue.h" -#include "nvim/os/os_win_conpty.h" +#include "nvim/os/pty_conpty_win.h" typedef enum { kWinpty, -- cgit From a5a3d7160d7cb70ef2477f243d7a537fc4fa20e8 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sat, 16 Nov 2019 13:55:27 +0900 Subject: Change option name from termwintype to termtype --- src/nvim/option.c | 6 +++--- src/nvim/option_defs.h | 2 +- src/nvim/options.lua | 4 ++-- src/nvim/os/pty_process_win.c | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 014eb676a0..959b4c41a5 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -314,7 +314,7 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", "yes:9", NULL }; #ifdef WIN32 -static char *(p_twt_values[]) = { "conpty", "winpty", "", NULL }; +static char *(p_tmt_values[]) = { "conpty", "winpty", "", NULL }; #endif /// All possible flags for 'shm'. @@ -3289,8 +3289,8 @@ ambw_end: errmsg = e_invarg; } #ifdef WIN32 - } else if (varp == &p_twt) { - if (check_opt_strings(*varp, p_twt_values, false) != OK + } else if (varp == &p_tmt) { + if (check_opt_strings(*varp, p_tmt_values, false) != OK || (!os_has_conpty_working() && STRCMP(*varp, "conpty") == 0)) { errmsg = e_invarg; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1d4c31dc1c..b66c4a7f02 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -654,7 +654,7 @@ EXTERN char_u *p_titleold; ///< 'titleold' EXTERN char_u *p_titlestring; ///< 'titlestring' EXTERN char_u *p_tsr; ///< 'thesaurus' EXTERN int p_tgc; ///< 'termguicolors' -EXTERN char_u *p_twt; ///< 'termwintype' +EXTERN char_u *p_tmt; ///< 'termtype' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' EXTERN char_u *p_udir; ///< 'undodir' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fe970ccba3..2e20b692e8 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2497,10 +2497,10 @@ return { defaults={if_true={vi=false}} }, { - full_name='termwintype', abbreviation='twt', + full_name='termtype', abbreviation='tmt', type='string', scope={'global'}, vi_def=false, - varname='p_twt', + varname='p_tmt', defaults={if_true={vi="",vim=""}} }, { diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 64d4408ece..93f1656029 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -56,8 +56,8 @@ int pty_process_spawn(PtyProcess *ptyproc) assert(proc->err.closed); - int pty_type = *p_twt; - if (pty_type == 'c' && os_has_conpty_working()) { + int pty_type = *p_tmt; + if (pty_type != 'w' && os_has_conpty_working()) { if ((conpty_object = os_conpty_init(&in_name, &out_name, ptyproc->width, ptyproc->height)) != NULL) { -- cgit From 4e06594c53645c9b3dc6c57850f712a810d071d3 Mon Sep 17 00:00:00 2001 From: erw7 Date: Sun, 17 Nov 2019 12:20:24 +0900 Subject: Add missing include file --- src/nvim/os/os_win_console.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c index 50127248a4..6cabeb2c9a 100644 --- a/src/nvim/os/os_win_console.c +++ b/src/nvim/os/os_win_console.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/vim.h" +#include "nvim/os/os_win_console.h" int os_get_conin_fd(void) { -- cgit From c86d5fa981b0651167f789aaff685b42cad7aaca Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 22 Nov 2019 13:51:14 +0900 Subject: Change to replace stderr with conout --- src/nvim/channel.c | 2 +- src/nvim/os/os_win_console.c | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/channel.c b/src/nvim/channel.c index d3e2977eae..c66a0682e3 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -482,7 +482,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, stdin_dup_fd = os_dup(STDIN_FILENO); os_replace_stdin_to_conin(); stdout_dup_fd = os_dup(STDOUT_FILENO); - os_replace_stdout_to_conout(); + os_replace_stdout_and_stderr_to_conout(); } #endif rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0); diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c index 6cabeb2c9a..8a0aa2f5ae 100644 --- a/src/nvim/os/os_win_console.c +++ b/src/nvim/os/os_win_console.c @@ -24,9 +24,8 @@ void os_replace_stdin_to_conin(void) assert(conin_fd == STDIN_FILENO); } -void os_replace_stdout_to_conout(void) +void os_replace_stdout_and_stderr_to_conout(void) { - close(STDOUT_FILENO); const HANDLE conout_handle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, @@ -34,6 +33,10 @@ void os_replace_stdout_to_conout(void) (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING, 0, (HANDLE)NULL); assert(conout_handle != INVALID_HANDLE_VALUE); + close(STDOUT_FILENO); const int conout_fd = _open_osfhandle((intptr_t)conout_handle, 0); assert(conout_fd == STDOUT_FILENO); + close(STDERR_FILENO); + const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0); + assert(conerr_fd == STDERR_FILENO); } -- cgit From 1e4f29069146cbab0be0559d87e399aefa433a29 Mon Sep 17 00:00:00 2001 From: erw7 Date: Fri, 22 Nov 2019 14:01:12 +0900 Subject: Remove termtype option --- src/nvim/option.c | 10 ---------- src/nvim/option_defs.h | 1 - src/nvim/options.lua | 7 ------- src/nvim/os/pty_process_win.c | 3 +-- 4 files changed, 1 insertion(+), 20 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 959b4c41a5..0c87a422dc 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -313,9 +313,6 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", "yes:9", NULL }; -#ifdef WIN32 -static char *(p_tmt_values[]) = { "conpty", "winpty", "", NULL }; -#endif /// All possible flags for 'shm'. static char_u SHM_ALL[] = { @@ -3288,13 +3285,6 @@ ambw_end: if (!parse_winhl_opt(curwin)) { errmsg = e_invarg; } -#ifdef WIN32 - } else if (varp == &p_tmt) { - if (check_opt_strings(*varp, p_tmt_values, false) != OK - || (!os_has_conpty_working() && STRCMP(*varp, "conpty") == 0)) { - errmsg = e_invarg; - } -#endif } else { // Options that are a list of flags. p = NULL; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b66c4a7f02..fcad6836bf 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -654,7 +654,6 @@ EXTERN char_u *p_titleold; ///< 'titleold' EXTERN char_u *p_titlestring; ///< 'titlestring' EXTERN char_u *p_tsr; ///< 'thesaurus' EXTERN int p_tgc; ///< 'termguicolors' -EXTERN char_u *p_tmt; ///< 'termtype' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' EXTERN char_u *p_udir; ///< 'undodir' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2e20b692e8..a5a14a1a25 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2496,13 +2496,6 @@ return { varname='p_tgc', defaults={if_true={vi=false}} }, - { - full_name='termtype', abbreviation='tmt', - type='string', scope={'global'}, - vi_def=false, - varname='p_tmt', - defaults={if_true={vi="",vim=""}} - }, { full_name='terse', type='bool', scope={'global'}, diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 93f1656029..6f7100e846 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -56,8 +56,7 @@ int pty_process_spawn(PtyProcess *ptyproc) assert(proc->err.closed); - int pty_type = *p_tmt; - if (pty_type != 'w' && os_has_conpty_working()) { + if (os_has_conpty_working()) { if ((conpty_object = os_conpty_init(&in_name, &out_name, ptyproc->width, ptyproc->height)) != NULL) { -- cgit From 03da3697a40d56961250028ce60a49ea6e87728b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 12 Jan 2020 18:40:27 -0500 Subject: vim-patch:8.2.0112: illegal memory access when using 'cindent' Problem: Illegal memory access when using 'cindent'. Solution: Check for NUL byte. (Dominique Pelle, closes vim/vim#5470) https://github.com/vim/vim/commit/02ad46394e8f887b60fda994f8a5da2ac1937b23 --- src/nvim/indent_c.c | 3 +++ src/nvim/testdir/test_cindent.vim | 9 +++++++++ 2 files changed, 12 insertions(+) (limited to 'src') diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 1ca1f3dae5..67a7e58ed7 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -461,6 +461,9 @@ cin_iscase ( if (cin_starts_with(s, "case")) { for (s += 4; *s; ++s) { s = cin_skipcomment(s); + if (*s == NUL) { + break; + } if (*s == ':') { if (s[1] == ':') /* skip over "::" for C++ */ ++s; diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index d9795d9335..debc9da46d 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -118,4 +118,13 @@ b = something(); bw! endfunc +" this was going beyond the end of the line. +func Test_cindent_case() + new + call setline(1, "case x: // x") + set cindent + norm! f:a: + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab -- cgit From d811fab0ad3e679acc27a4ff8f399fcf04726aa9 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 15 Jan 2020 22:20:21 -0500 Subject: vim-patch:8.2.0120: virtcol() does not check arguments to be valid Problem: virtcol() does not check arguments to be valid, which may lead to a crash. Solution: Check the column to be valid. Do not decrement MAXCOL. (closes vim/vim#5480) https://github.com/vim/vim/commit/b3d33d8570bc49a7f90990572d7f9630a1bfae02 --- src/nvim/eval.c | 11 ++++++++++- src/nvim/testdir/test_marks.vim | 21 +++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9916ee3bd3..797c61a482 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16018,7 +16018,7 @@ static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const name = tv_get_string_chk(argvars); if (name != NULL) { if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { - if (--pos.col < 0) { + if (pos.col != MAXCOL && --pos.col < 0) { pos.col = 0; } if (name[0] == '.' && name[1] == NUL) { @@ -19045,6 +19045,15 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) fp = var2fpos(&argvars[0], FALSE, &fnum); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { + // Limit the column to a valid value, getvvcol() doesn't check. + if (fp->col < 0) { + fp->col = 0; + } else { + const size_t len = STRLEN(ml_get(fp->lnum)); + if (fp->col > (colnr_T)len) { + fp->col = (colnr_T)len; + } + } getvvcol(curwin, fp, NULL, NULL, &vcol); ++vcol; } diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 272553c29f..06b9dc9dab 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -26,11 +26,11 @@ function! Test_Incr_Marks() endfunction func Test_setpos() - new one + new Xone let onebuf = bufnr('%') let onewin = win_getid() call setline(1, ['aaa', 'bbb', 'ccc']) - new two + new Xtwo let twobuf = bufnr('%') let twowin = win_getid() call setline(1, ['aaa', 'bbb', 'ccc']) @@ -63,7 +63,24 @@ func Test_setpos() call setpos("'N", [onebuf, 1, 3, 0]) call assert_equal([onebuf, 1, 3, 0], getpos("'N")) + " try invalid column and check virtcol() call win_gotoid(onewin) + call setpos("'a", [0, 1, 2, 0]) + call assert_equal([0, 1, 2, 0], getpos("'a")) + call setpos("'a", [0, 1, -5, 0]) + call assert_equal([0, 1, 2, 0], getpos("'a")) + call setpos("'a", [0, 1, 0, 0]) + call assert_equal([0, 1, 1, 0], getpos("'a")) + call setpos("'a", [0, 1, 4, 0]) + call assert_equal([0, 1, 4, 0], getpos("'a")) + call assert_equal(4, virtcol("'a")) + call setpos("'a", [0, 1, 5, 0]) + call assert_equal([0, 1, 5, 0], getpos("'a")) + call assert_equal(4, virtcol("'a")) + call setpos("'a", [0, 1, 21341234, 0]) + call assert_equal([0, 1, 21341234, 0], getpos("'a")) + call assert_equal(4, virtcol("'a")) + bwipe! call win_gotoid(twowin) bwipe! -- cgit From 1042338c0062ea091ea1e52b161fd25adcdc927f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 16 Jan 2020 00:10:16 -0500 Subject: clang/'Logic error': zero-init struct --- src/nvim/ops.c | 2 +- src/nvim/spell.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 5da81dbff6..da2b81fd0a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1372,7 +1372,7 @@ int op_delete(oparg_T *oap) linenr_T lnum; char_u *ptr; char_u *newp, *oldp; - struct block_def bd; + struct block_def bd = { 0 }; linenr_T old_lcount = curbuf->b_ml.ml_line_count; if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to do diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 5feb7efda9..96ae75718d 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -6145,8 +6145,8 @@ static void spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res) static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) { salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; - int word[MAXWLEN]; - int wres[MAXWLEN]; + int word[MAXWLEN] = { 0 }; + int wres[MAXWLEN] = { 0 }; int l; int *ws; int *pf; -- cgit From a7aa1fc87da0bd66b64afc39b5d6bf7c4c129986 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 16 Jan 2020 00:24:26 -0500 Subject: spell: spell_soundfold_sal() is dead code --- src/nvim/spell.c | 242 +------------------------------------------------------ 1 file changed, 1 insertion(+), 241 deletions(-) (limited to 'src') diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 96ae75718d..c75a53a777 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -5835,10 +5835,7 @@ void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) word = fword; } - if (has_mbyte) - spell_soundfold_wsal(slang, word, res); - else - spell_soundfold_sal(slang, word, res); + spell_soundfold_wsal(slang, word, res); } } @@ -5903,243 +5900,6 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) res[ri] = NUL; } -static void spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res) -{ - salitem_T *smp; - char_u word[MAXWLEN]; - char_u *s = inword; - char_u *t; - char_u *pf; - int i, j, z; - int reslen; - int n, k = 0; - int z0; - int k0; - int n0; - int c; - int pri; - int p0 = -333; - int c0; - - // Remove accents, if wanted. We actually remove all non-word characters. - // But keep white space. We need a copy, the word may be changed here. - if (slang->sl_rem_accents) { - t = word; - while (*s != NUL) { - if (ascii_iswhite(*s)) { - *t++ = ' '; - s = skipwhite(s); - } else { - if (spell_iswordp_nmw(s, curwin)) - *t++ = *s; - ++s; - } - } - *t = NUL; - } else - STRLCPY(word, s, MAXWLEN); - - smp = (salitem_T *)slang->sl_sal.ga_data; - - // This comes from Aspell phonet.cpp. Converted from C++ to C. - // Changed to keep spaces. - i = reslen = z = 0; - while ((c = word[i]) != NUL) { - // Start with the first rule that has the character in the word. - n = slang->sl_sal_first[c]; - z0 = 0; - - if (n >= 0) { - // check all rules for the same letter - for (; (s = smp[n].sm_lead)[0] == c; ++n) { - // Quickly skip entries that don't match the word. Most - // entries are less then three chars, optimize for that. - k = smp[n].sm_leadlen; - if (k > 1) { - if (word[i + 1] != s[1]) - continue; - if (k > 2) { - for (j = 2; j < k; ++j) - if (word[i + j] != s[j]) - break; - if (j < k) - continue; - } - } - - if ((pf = smp[n].sm_oneof) != NULL) { - // Check for match with one of the chars in "sm_oneof". - while (*pf != NUL && *pf != word[i + k]) - ++pf; - if (*pf == NUL) - continue; - ++k; - } - s = smp[n].sm_rules; - pri = 5; // default priority - - p0 = *s; - k0 = k; - while (*s == '-' && k > 1) { - k--; - s++; - } - if (*s == '<') - s++; - if (ascii_isdigit(*s)) { - // determine priority - pri = *s - '0'; - s++; - } - if (*s == '^' && *(s + 1) == '^') - s++; - - if (*s == NUL - || (*s == '^' - && (i == 0 || !(word[i - 1] == ' ' - || spell_iswordp(word + i - 1, curwin))) - && (*(s + 1) != '$' - || (!spell_iswordp(word + i + k0, curwin)))) - || (*s == '$' && i > 0 - && spell_iswordp(word + i - 1, curwin) - && (!spell_iswordp(word + i + k0, curwin)))) { - // search for followup rules, if: - // followup and k > 1 and NO '-' in searchstring - c0 = word[i + k - 1]; - n0 = slang->sl_sal_first[c0]; - - if (slang->sl_followup && k > 1 && n0 >= 0 - && p0 != '-' && word[i + k] != NUL) { - // test follow-up rule for "word[i + k]" - for (; (s = smp[n0].sm_lead)[0] == c0; ++n0) { - // Quickly skip entries that don't match the word. - k0 = smp[n0].sm_leadlen; - if (k0 > 1) { - if (word[i + k] != s[1]) - continue; - if (k0 > 2) { - pf = word + i + k + 1; - for (j = 2; j < k0; ++j) - if (*pf++ != s[j]) - break; - if (j < k0) - continue; - } - } - k0 += k - 1; - - if ((pf = smp[n0].sm_oneof) != NULL) { - // Check for match with one of the chars in - // "sm_oneof". - while (*pf != NUL && *pf != word[i + k0]) - ++pf; - if (*pf == NUL) - continue; - ++k0; - } - - p0 = 5; - s = smp[n0].sm_rules; - while (*s == '-') { - // "k0" gets NOT reduced because - // "if (k0 == k)" - s++; - } - if (*s == '<') - s++; - if (ascii_isdigit(*s)) { - p0 = *s - '0'; - s++; - } - - if (*s == NUL - // *s == '^' cuts - || (*s == '$' - && !spell_iswordp(word + i + k0, - curwin))) { - if (k0 == k) - // this is just a piece of the string - continue; - - if (p0 < pri) - // priority too low - continue; - // rule fits; stop search - break; - } - } - - if (p0 >= pri && smp[n0].sm_lead[0] == c0) - continue; - } - - // replace string - s = smp[n].sm_to; - if (s == NULL) - s = (char_u *)""; - pf = smp[n].sm_rules; - p0 = (vim_strchr(pf, '<') != NULL) ? 1 : 0; - if (p0 == 1 && z == 0) { - // rule with '<' is used - if (reslen > 0 && *s != NUL && (res[reslen - 1] == c - || res[reslen - 1] == *s)) - reslen--; - z0 = 1; - z = 1; - k0 = 0; - while (*s != NUL && word[i + k0] != NUL) { - word[i + k0] = *s; - k0++; - s++; - } - if (k > k0) - STRMOVE(word + i + k0, word + i + k); - - // new "actual letter" - c = word[i]; - } else { - // no '<' rule used - i += k - 1; - z = 0; - while (*s != NUL && s[1] != NUL && reslen < MAXWLEN) { - if (reslen == 0 || res[reslen - 1] != *s) - res[reslen++] = *s; - s++; - } - // new "actual letter" - c = *s; - if (strstr((char *)pf, "^^") != NULL) { - if (c != NUL) - res[reslen++] = c; - STRMOVE(word, word + i + 1); - i = 0; - z0 = 1; - } - } - break; - } - } - } else if (ascii_iswhite(c)) { - c = ' '; - k = 1; - } - - if (z0 == 0) { - if (k && !p0 && reslen < MAXWLEN && c != NUL - && (!slang->sl_collapse || reslen == 0 - || res[reslen - 1] != c)) - // condense only double letters - res[reslen++] = c; - - i++; - z = 0; - k = 0; - } - } - - res[reslen] = NUL; -} - // Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". // Multi-byte version of spell_soundfold(). static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) -- cgit From 9a9bb9186a9f1596b1b501719b495a536065a4f3 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 17 Jan 2020 22:06:44 -0500 Subject: vim-patch:8.1.0716: get warning message when 'completefunc' returns nothing Problem: Get warning message when 'completefunc' returns nothing. Solution: Allow for returning v:none to suppress the warning message. (Yasuhiro Matsumoto, closes vim/vim#3789) https://github.com/vim/vim/commit/cee9bc2e3dc5c16a9d2a8d0e23aa0d5fdefa3a4a --- src/nvim/edit.c | 4 ++- src/nvim/testdir/test_ins_complete.vim | 61 ++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index cdb4b127da..9bc00cf2aa 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3747,6 +3747,8 @@ expand_by_function( case VAR_DICT: matchdict = rettv.vval.v_dict; break; + case VAR_SPECIAL: + FALLTHROUGH; default: // TODO(brammool): Give error message? tv_clear(&rettv); @@ -5210,7 +5212,7 @@ static int ins_complete(int c, bool enable_pum) } } - /* Show a message about what (completion) mode we're in. */ + // Show a message about what (completion) mode we're in. showmode(); if (!shortmess(SHM_COMPLETIONMENU)) { if (edit_submode_extra != NULL) { diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 52ec281d82..e77afb7a55 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -98,6 +98,15 @@ func Test_ins_complete() call delete('Xdir', 'rf') endfunc +func s:CompleteDone_CompleteFuncNone( findstart, base ) + throw 'skipped: Nvim does not support v:none' + if a:findstart + return 0 + endif + + return v:none +endfunc + function! s:CompleteDone_CompleteFuncDict( findstart, base ) if a:findstart return 0 @@ -117,6 +126,10 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base ) \ } endfunction +func s:CompleteDone_CheckCompletedItemNone() + let s:called_completedone = 1 +endfunc + function! s:CompleteDone_CheckCompletedItemDict() call assert_equal( 'aword', v:completed_item[ 'word' ] ) call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) @@ -128,15 +141,29 @@ function! s:CompleteDone_CheckCompletedItemDict() let s:called_completedone = 1 endfunction -function Test_CompleteDoneDict() +func Test_CompleteDoneNone() + throw 'skipped: Nvim does not support v:none' + au CompleteDone * :call CompleteDone_CheckCompletedItemNone() + + set completefunc=CompleteDone_CompleteFuncNone + execute "normal a\\\" + set completefunc& + + call assert_true(s:called_completedone) + + let s:called_completedone = 0 + au! CompleteDone +endfunc + +func Test_CompleteDoneDict() au CompleteDone * :call CompleteDone_CheckCompletedItemDict() set completefunc=CompleteDone_CompleteFuncDict execute "normal a\\\" set completefunc& - call assert_equal( 'test', v:completed_item[ 'user_data' ] ) - call assert_true( s:called_completedone ) + call assert_equal('test', v:completed_item[ 'user_data' ]) + call assert_true(s:called_completedone) let s:called_completedone = 0 au! CompleteDone @@ -155,7 +182,7 @@ func Test_CompleteDone_undo() au! CompleteDone endfunc -function! s:CompleteDone_CompleteFuncDictNoUserData( findstart, base ) +func s:CompleteDone_CompleteFuncDictNoUserData(findstart, base) if a:findstart return 0 endif @@ -171,9 +198,9 @@ function! s:CompleteDone_CompleteFuncDictNoUserData( findstart, base ) \ } \ ] \ } -endfunction +endfunc -function! s:CompleteDone_CheckCompletedItemDictNoUserData() +func s:CompleteDone_CheckCompletedItemDictNoUserData() call assert_equal( 'aword', v:completed_item[ 'word' ] ) call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) @@ -182,31 +209,31 @@ function! s:CompleteDone_CheckCompletedItemDictNoUserData() call assert_equal( '', v:completed_item[ 'user_data' ] ) let s:called_completedone = 1 -endfunction +endfunc -function Test_CompleteDoneDictNoUserData() +func Test_CompleteDoneDictNoUserData() au CompleteDone * :call CompleteDone_CheckCompletedItemDictNoUserData() set completefunc=CompleteDone_CompleteFuncDictNoUserData execute "normal a\\\" set completefunc& - call assert_equal( '', v:completed_item[ 'user_data' ] ) - call assert_true( s:called_completedone ) + call assert_equal('', v:completed_item[ 'user_data' ]) + call assert_true(s:called_completedone) let s:called_completedone = 0 au! CompleteDone endfunc -function! s:CompleteDone_CompleteFuncList( findstart, base ) +func s:CompleteDone_CompleteFuncList(findstart, base) if a:findstart return 0 endif return [ 'aword' ] -endfunction +endfunc -function! s:CompleteDone_CheckCompletedItemList() +func s:CompleteDone_CheckCompletedItemList() call assert_equal( 'aword', v:completed_item[ 'word' ] ) call assert_equal( '', v:completed_item[ 'abbr' ] ) call assert_equal( '', v:completed_item[ 'menu' ] ) @@ -215,17 +242,17 @@ function! s:CompleteDone_CheckCompletedItemList() call assert_equal( '', v:completed_item[ 'user_data' ] ) let s:called_completedone = 1 -endfunction +endfunc -function Test_CompleteDoneList() +func Test_CompleteDoneList() au CompleteDone * :call CompleteDone_CheckCompletedItemList() set completefunc=CompleteDone_CompleteFuncList execute "normal a\\\" set completefunc& - call assert_equal( '', v:completed_item[ 'user_data' ] ) - call assert_true( s:called_completedone ) + call assert_equal('', v:completed_item[ 'user_data' ]) + call assert_true(s:called_completedone) let s:called_completedone = 0 au! CompleteDone -- cgit From 3d0c3148fbc3b66636bb9627db64331365ac1732 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 17 Jan 2020 23:14:47 -0500 Subject: vim-patch:8.1.1139: no test for what is fixed in patch 8.1.0716 Problem: No test for what is fixed in patch 8.1.0716. Solution: Add a test. (Yasuhiro Matsumoto, closes vim/vim#3797) https://github.com/vim/vim/commit/9845f36aa6ba28e0aa388bb635d4bb8ab56f1a47 --- src/nvim/testdir/test_ins_complete.vim | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index e77afb7a55..4140d16a4c 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -144,12 +144,15 @@ endfunction func Test_CompleteDoneNone() throw 'skipped: Nvim does not support v:none' au CompleteDone * :call CompleteDone_CheckCompletedItemNone() + let oldline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '') set completefunc=CompleteDone_CompleteFuncNone execute "normal a\\\" set completefunc& + let newline = join(map(range(&columns), 'nr2char(screenchar(&lines-1, v:val+1))'), '') call assert_true(s:called_completedone) + call assert_equal(oldline, newline) let s:called_completedone = 0 au! CompleteDone -- cgit From ad35cbca76222deb33357b63a61a29945ef084f7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 17 Jan 2020 23:31:18 -0500 Subject: vim-patch:8.2.0123: complete_info() does not work when CompleteDone is triggered Problem: complete_info() does not work when CompleteDone is triggered. Solution: Trigger CompleteDone before clearing the info. https://github.com/vim/vim/commit/17e04781f26c24769e202351c194ee252927eee1 --- src/nvim/edit.c | 16 +++++++++++++--- src/nvim/testdir/test_ins_complete.vim | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 9bc00cf2aa..0c183add16 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3385,6 +3385,7 @@ static bool ins_compl_prep(int c) { char_u *ptr; bool retval = false; + const int prev_mode = ctrl_x_mode; /* Forget any previous 'special' messages if this is actually * a ^X mode key - bar ^R, in which case we wait to see what it gives us. @@ -3593,6 +3594,18 @@ static bool ins_compl_prep(int c) auto_format(FALSE, TRUE); + { + const int new_mode = ctrl_x_mode; + + // Trigger the CompleteDone event to give scripts a chance to + // act upon the completion. Do this before clearing the info, + // and restore ctrl_x_mode, so that complete_info() can be + // used. + ctrl_x_mode = prev_mode; + ins_apply_autocmds(EVENT_COMPLETEDONE); + ctrl_x_mode = new_mode; + } + ins_compl_free(); compl_started = false; compl_matches = 0; @@ -3617,9 +3630,6 @@ static bool ins_compl_prep(int c) */ if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) do_c_expr_indent(); - /* Trigger the CompleteDone event to give scripts a chance to act - * upon the completion. */ - ins_apply_autocmds(EVENT_COMPLETEDONE); } } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) /* Trigger the CompleteDone event to give scripts a chance to act diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 4140d16a4c..e6d427db05 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -138,6 +138,8 @@ function! s:CompleteDone_CheckCompletedItemDict() call assert_equal( 'W', v:completed_item[ 'kind' ] ) call assert_equal( 'test', v:completed_item[ 'user_data' ] ) + call assert_equal('function', complete_info().mode) + let s:called_completedone = 1 endfunction -- cgit From 757aad92e84709a08320a06870b6acb086bc6876 Mon Sep 17 00:00:00 2001 From: Marcos ALMEIDA Date: Sat, 29 Sep 2018 20:40:53 +0200 Subject: autocmd: add WinClosed event - only fire once, just before freeing mem - trigger when on a different buffer - avoid recursive calls in another tab --- src/nvim/auevents.lua | 2 ++ src/nvim/window.c | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index a52789c795..d596edf551 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -110,6 +110,7 @@ return { 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window + 'WinClosed', -- after closing a window }, aliases = { BufCreate = 'BufAdd', @@ -129,5 +130,6 @@ return { TermOpen=true, UIEnter=true, UILeave=true, + WinClosed=true, }, } diff --git a/src/nvim/window.c b/src/nvim/window.c index e913d33de0..4743dca3ff 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2502,9 +2502,10 @@ int win_close(win_T *win, bool free_buf) return FAIL; } win->w_closing = true; - apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf); - if (!win_valid(win)) + apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf); + if (!win_valid(win)) { return FAIL; + } win->w_closing = false; if (last_window()) return FAIL; @@ -2534,6 +2535,12 @@ int win_close(win_T *win, bool free_buf) } } + // Fire WinClosed just before starting to free window-related resources. + do_autocmd_winclosed(win); + // autocmd may have freed the window already. + if (!win_valid_any_tab(win)) { + return OK; + } /* Free independent synblock before the buffer is freed. */ if (win->w_buffer != NULL) @@ -2576,6 +2583,7 @@ int win_close(win_T *win, bool free_buf) win_close_othertab(win, false, prev_curtab); return FAIL; } + // Autocommands may have closed the window already, or closed the only // other window or moved to another tab page. if (!win_valid(win) || (!win->w_floating && last_window()) @@ -2585,8 +2593,9 @@ int win_close(win_T *win, bool free_buf) // let terminal buffers know that this window dimensions may be ignored win->w_closing = true; - /* Free the memory used for the window and get the window that received - * the screen space. */ + + // Free the memory used for the window and get the window that received + // the screen space. wp = win_free_mem(win, &dir, NULL); if (help_window) { @@ -2678,6 +2687,19 @@ int win_close(win_T *win, bool free_buf) return OK; } +static void do_autocmd_winclosed(win_T *win) + FUNC_ATTR_NONNULL_ALL +{ + static bool recursive = false; + if (recursive || !has_event(EVENT_WINCLOSED)) { + return; + } + recursive = true; + apply_autocmds(EVENT_WINCLOSED, win->w_buffer->b_fname, + win->w_buffer->b_fname, false, win->w_buffer); + recursive = false; +} + /* * Close window "win" in tab page "tp", which is not the current tab page. * This may be the last window in that tab page and result in closing the tab, @@ -2698,6 +2720,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) return; // window is already being closed } + // Fire WinClosed just before starting to free window-related resources. + do_autocmd_winclosed(win); + // autocmd may have freed the window already. + if (!win_valid_any_tab(win)) { + return; + } + if (win->w_buffer != NULL) { // Close the link to the buffer. close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false); -- cgit From 2b8e66c6ce0a5ccae09023732c06da90f96ed5f5 Mon Sep 17 00:00:00 2001 From: notomo Date: Tue, 14 Jan 2020 22:34:05 +0900 Subject: autocmd: WinClosed exposes window id as --- src/nvim/fileio.c | 3 ++- src/nvim/window.c | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 865da25009..7a46957151 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6862,7 +6862,8 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX || event == EVENT_SIGNAL - || event == EVENT_TABCLOSED) { + || event == EVENT_TABCLOSED + || event == EVENT_WINCLOSED) { fname = vim_strsave(fname); } else { fname = (char_u *)FullName_save((char *)fname, false); diff --git a/src/nvim/window.c b/src/nvim/window.c index 4743dca3ff..9efed49e70 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2695,8 +2695,10 @@ static void do_autocmd_winclosed(win_T *win) return; } recursive = true; - apply_autocmds(EVENT_WINCLOSED, win->w_buffer->b_fname, - win->w_buffer->b_fname, false, win->w_buffer); + + char_u winid[10]; + vim_snprintf((char *)winid, sizeof(winid), "%i", win->handle); + apply_autocmds(EVENT_WINCLOSED, winid, winid, false, win->w_buffer); recursive = false; } -- cgit From 156c25e4983d4c106ba70e5e3bcc6bbb012d8065 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 13 Jan 2020 00:19:20 -0800 Subject: WinClosed: sort auevents.lua; improve tests - test: reduce verbosity, condense redundancy, improve readability - auevents.lua: keep events sorted by name. ref afd1d412fa91 --- src/nvim/auevents.lua | 10 +++++----- src/nvim/window.c | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index d596edf551..96e170a9e1 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -21,12 +21,12 @@ return { 'BufWritePre', -- before writing a buffer 'ChanInfo', -- info was received about channel 'ChanOpen', -- channel was opened - 'CmdlineChanged', -- command line was modified - '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 + 'CmdlineChanged', -- command line was modified + 'CmdlineEnter', -- after entering cmdline mode + 'CmdlineLeave', -- before leaving cmdline mode 'ColorScheme', -- after loading a colorscheme 'ColorSchemePre', -- before loading a colorscheme 'CompleteChanged', -- after popup menu changed @@ -76,8 +76,8 @@ return { 'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd". 'Signal', -- after nvim process received a signal 'SourceCmd', -- sourcing a Vim script using command - 'SourcePre', -- before sourcing a Vim script 'SourcePost', -- after sourcing a Vim script + 'SourcePre', -- before sourcing a Vim script 'SpellFileMissing', -- spell file missing 'StdinReadPost', -- after reading from stdin 'StdinReadPre', -- before reading from stdin @@ -107,10 +107,10 @@ return { 'VimResized', -- after Vim window was resized 'VimResume', -- after Nvim is resumed 'VimSuspend', -- before Nvim is suspended + 'WinClosed', -- after closing a window 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window - 'WinClosed', -- after closing a window }, aliases = { BufCreate = 'BufAdd', diff --git a/src/nvim/window.c b/src/nvim/window.c index 9efed49e70..8181883426 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2695,8 +2695,7 @@ static void do_autocmd_winclosed(win_T *win) return; } recursive = true; - - char_u winid[10]; + char_u winid[NUMBUFLEN]; vim_snprintf((char *)winid, sizeof(winid), "%i", win->handle); apply_autocmds(EVENT_WINCLOSED, winid, winid, false, win->w_buffer); recursive = false; -- cgit From 58033e15d536a03f47de1e547ef0780dd05cd887 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 19 Jan 2020 09:44:43 -0500 Subject: vim-patch:8.1.2031: cursor position wrong when resizing and using conceal Problem: Cursor position wrong when resizing and using conceal. Solution: Set the flags that the cursor position is valid when setting the row and column during redrawing. (closes vim/vim#4931) https://github.com/vim/vim/commit/5babc6e858afbfa54aa0e203a64b9c70175487b7 --- src/nvim/screen.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 24ad012201..047af8db75 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3868,6 +3868,7 @@ win_line ( } wp->w_wrow = row; did_wcol = true; + curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; } // Don't override visual selection highlighting. -- cgit From f5cc5153c6a73af50b034676e116b667892ababe Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 19 Jan 2020 09:14:06 -0500 Subject: vim-patch:8.1.2421: test88 is old style Problem: Test88 is old style. Solution: Turn into a new style test. (Yegappan Lakshmanan, closes vim/vim#5347) https://github.com/vim/vim/commit/213ed008bbcd9fe0d3329b17f5f4af0169e448ff 'test_conceal.vim' requires +conceal and +terminal so it is N/A. --- src/nvim/testdir/test_python2.vim | 2 +- src/nvim/testdir/test_python3.vim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim index 9628a298b9..5895ac85a8 100644 --- a/src/nvim/testdir/test_python2.vim +++ b/src/nvim/testdir/test_python2.vim @@ -1,5 +1,5 @@ " Test for python 2 commands. -" TODO: move tests from test87.in here. +" TODO: move tests from test86.in here. if !has('python') finish diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 108f78f976..436d74b85d 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -1,5 +1,5 @@ " Test for python 3 commands. -" TODO: move tests from test88.in here. +" TODO: move tests from test87.in here. if !has('python3') finish -- cgit From b3974e4437a0119ca81c926bd2b6549aca509c13 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 19 Jan 2020 08:48:35 +0100 Subject: restore old 'termencoding' behavior Partially reverts #11647. Replaces #11662 The old implementation was removed without clear motivation. The "term option" hackaround added in its place is neither shorter nor simpler. The new behavior breaks even init.vim that expliticly check against it: if exists('&termencoding') set termencoding=utf-8 endif There was nothing wrong with the 0.4.x behavior. Empty &tenc has indicated that the &enc value should be used for all the history of Nvim. Ignoring setting the option is the expected behavior for Vim versions that does not support the option (and Nvim is such a version) 'tenc' was also irrelevant to the Test_unicode python3 test. The reason this has to be disabled is that neovim can't change internal 'encoding' --- src/nvim/option.c | 7 ------- src/nvim/options.lua | 6 ++++++ src/nvim/testdir/test_python3.vim | 4 +--- 3 files changed, 7 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 0c87a422dc..2871a4b7de 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4742,13 +4742,6 @@ bool get_tty_option(char *name, char **value) return true; } - if (strequal(name, "tenc") || strequal(name, "termencoding")) { - if (value) { - *value = xstrdup("utf-8"); - } - return true; - } - if (strequal(name, "ttytype")) { if (value) { *value = p_ttytype ? xstrdup(p_ttytype) : xstrdup("nvim"); diff --git a/src/nvim/options.lua b/src/nvim/options.lua index a5a14a1a25..7d080b8d56 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2488,6 +2488,12 @@ return { varname='p_tbidi', defaults={if_true={vi=false}} }, + { + full_name='termencoding', abbreviation='tenc', + type='string', scope={'global'}, + vi_def=true, + defaults={if_true={vi=""}} + }, { full_name='termguicolors', abbreviation='tgc', type='bool', scope={'global'}, diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 108f78f976..31e0142bf4 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -175,9 +175,7 @@ endfunc func Test_unicode() " this crashed Vim once - if &tenc != '' - throw "Skipped: 'termencoding' is not empty" - endif + throw "Skipped: nvim does not support changing 'encoding'" set encoding=utf32 py3 print('hello') -- cgit From 8e385eb46a8b961a760e05b4cfa053cf713def62 Mon Sep 17 00:00:00 2001 From: We're Yet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Sat, 18 Jan 2020 13:19:56 -0800 Subject: tabpage: :tabs indicates previous tabpage's curwin --- src/nvim/ex_docmd.c | 6 +++++- src/nvim/testdir/test_startup.vim | 4 ++-- src/nvim/testdir/test_tabpage.vim | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 30e7ecd434..6bda62594e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7037,6 +7037,10 @@ static void ex_tabs(exarg_T *eap) msg_start(); msg_scroll = TRUE; + win_T *lastused_win = valid_tabpage(lastused_tabpage) + ? lastused_tabpage->tp_curwin + : NULL; + FOR_ALL_TABS(tp) { if (got_int) { break; @@ -7054,7 +7058,7 @@ static void ex_tabs(exarg_T *eap) } msg_putchar('\n'); - msg_putchar(wp == curwin ? '>' : ' '); + msg_putchar(wp == curwin ? '>' : wp == lastused_win ? '#' : ' '); msg_putchar(' '); msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' '); msg_putchar(' '); diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index a38625cca8..15b55d35c1 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -246,7 +246,7 @@ func Test_p_arg() call assert_equal('Tab page 1', lines[0]) call assert_equal('> [No Name]', lines[1]) call assert_equal('Tab page 2', lines[2]) - call assert_equal(' [No Name]', lines[3]) + call assert_equal('# [No Name]', lines[3]) endif if RunVim([], after, '-p foo bar') @@ -255,7 +255,7 @@ func Test_p_arg() call assert_equal('Tab page 1', lines[0]) call assert_equal('> foo', lines[1]) call assert_equal('Tab page 2', lines[2]) - call assert_equal(' bar', lines[3]) + call assert_equal('# bar', lines[3]) endif call delete('Xtestout') diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index c35ddc4473..ce9f7adfef 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -548,7 +548,7 @@ func Test_tabs() norm ixxx let a=split(execute(':tabs'), "\n") call assert_equal(['Tab page 1', - \ ' [No Name]', + \ '# [No Name]', \ 'Tab page 2', \ '> + tab1'], a) -- cgit From 48a869dc6d29514e943070da9f22f702f5179826 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Mon, 20 Jan 2020 19:29:12 +0100 Subject: shed biking: it's always extmarks, never marks extended --- src/nvim/api/buffer.c | 2 +- src/nvim/api/private/helpers.c | 2 +- src/nvim/api/vim.c | 2 +- src/nvim/buffer.c | 2 +- src/nvim/change.c | 2 +- src/nvim/edit.c | 2 +- src/nvim/ex_cmds.c | 2 +- src/nvim/extmark.c | 910 +++++++++++++++++++++++++++++++++++++++++ src/nvim/extmark.h | 93 +++++ src/nvim/extmark_defs.h | 37 ++ src/nvim/fold.c | 2 +- src/nvim/indent.c | 2 +- src/nvim/map.h | 2 +- src/nvim/mark.c | 2 +- src/nvim/mark.h | 2 +- src/nvim/mark_extended.c | 910 ----------------------------------------- src/nvim/mark_extended.h | 93 ----- src/nvim/mark_extended_defs.h | 37 -- src/nvim/ops.c | 2 +- src/nvim/screen.c | 2 +- src/nvim/undo.c | 2 +- src/nvim/undo_defs.h | 2 +- 22 files changed, 1056 insertions(+), 1056 deletions(-) create mode 100644 src/nvim/extmark.c create mode 100644 src/nvim/extmark.h create mode 100644 src/nvim/extmark_defs.h delete mode 100644 src/nvim/mark_extended.c delete mode 100644 src/nvim/mark_extended.h delete mode 100644 src/nvim/mark_extended_defs.h (limited to 'src') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 3106011fe2..a666ed92da 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -26,7 +26,7 @@ #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/move.h" #include "nvim/syntax.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 37e31e0807..a1745ef777 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -24,7 +24,7 @@ #include "nvim/eval/typval.h" #include "nvim/map_defs.h" #include "nvim/map.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index baa0387fd8..9c58ce853b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -40,7 +40,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 837fcb5cc1..eb71a8fc5e 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -53,7 +53,7 @@ #include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/change.c b/src/nvim/change.c index 7eb6ea7328..a341b8fce1 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -17,7 +17,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/memline.h" #include "nvim/misc1.h" #include "nvim/move.h" diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 0c183add16..68fa99484c 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -28,7 +28,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 53caaa6a67..bc6821f60f 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -40,7 +40,7 @@ #include "nvim/buffer_updates.h" #include "nvim/main.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c new file mode 100644 index 0000000000..d60723c755 --- /dev/null +++ b/src/nvim/extmark.c @@ -0,0 +1,910 @@ +// 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 + +// Implements extended marks for plugins. Each mark exists in a btree of +// lines containing btrees of columns. +// +// The btree provides efficient range lookups. +// A map of pointers to the marks is used for fast lookup by mark id. +// +// Marks are moved by calls to extmark_splice. Additionally mark_adjust +// might adjust extmarks to line inserts/deletes. +// +// Undo/Redo of marks is implemented by storing the call arguments to +// extmark_splice. The list of arguments is applied in extmark_apply_undo. +// The only case where we have to copy extmarks is for the area being effected +// by a delete. +// +// Marks live in namespaces that allow plugins/users to segregate marks +// from other users. +// +// For possible ideas for efficency improvements see: +// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html +// TODO(bfredl): These ideas could be used for an enhanced btree, which +// wouldn't need separate line and column layers. +// Other implementations exist in gtk and tk toolkits. +// +// Deleting marks only happens when explicitly calling extmark_del, deleteing +// over a range of marks will only move the marks. Deleting on a mark will +// leave it in same position unless it is on the EOL of a line. + +#include +#include "nvim/api/vim.h" +#include "nvim/vim.h" +#include "nvim/charset.h" +#include "nvim/extmark.h" +#include "nvim/buffer_updates.h" +#include "nvim/memline.h" +#include "nvim/pos.h" +#include "nvim/globals.h" +#include "nvim/map.h" +#include "nvim/lib/kbtree.h" +#include "nvim/undo.h" +#include "nvim/buffer.h" +#include "nvim/syntax.h" +#include "nvim/highlight.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "extmark.c.generated.h" +#endif + +static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) { + if (!buf->b_extmark_ns) { + if (!put) { + return NULL; + } + buf->b_extmark_ns = map_new(uint64_t, ExtmarkNs)(); + buf->b_extmark_index = map_new(uint64_t, ExtmarkItem)(); + } + + ExtmarkNs *ns = map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put); + if (put && ns->map == NULL) { + ns->map = map_new(uint64_t, uint64_t)(); + ns->free_id = 1; + } + return ns; +} + + +/// Create or update an extmark +/// +/// must not be used during iteration! +/// @returns the mark id +uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, + int row, colnr_T col, ExtmarkOp op) +{ + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); + mtpos_t old_pos; + uint64_t mark = 0; + + if (id == 0) { + id = ns->free_id++; + } else { + uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id); + if (old_mark) { + if (old_mark & MARKTREE_PAIRED_FLAG) { + extmark_del(buf, ns_id, id); + } else { + // TODO(bfredl): we need to do more if "revising" a decoration mark. + MarkTreeIter itr[1]; + old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); + assert(itr->node); + if (old_pos.row == row && old_pos.col == col) { + map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark); + mark = marktree_revise(buf->b_marktree, itr); + goto revised; + } + marktree_del_itr(buf->b_marktree, itr, false); + } + } else { + ns->free_id = MAX(ns->free_id, id+1); + } + } + + mark = marktree_put(buf->b_marktree, row, col, true); +revised: + map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, + (ExtmarkItem){ ns_id, id, 0, + KV_INITIAL_VALUE }); + map_put(uint64_t, uint64_t)(ns->map, id, mark); + + if (op != kExtmarkNoUndo) { + // TODO(bfredl): this doesn't cover all the cases and probably shouldn't + // be done "prematurely". Any movement in undo history might necessitate + // adding new marks to old undo headers. + u_extmark_set(buf, mark, row, col); + } + return id; +} + +static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) +{ + MarkTreeIter itr[1]; + mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); + if (pos.row == -1) { + return false; + } + + if (pos.row == row && pos.col == col) { + return true; + } + + marktree_move(buf->b_marktree, itr, row, col); + return true; +} + +// Remove an extmark +// Returns 0 on missing id +bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) +{ + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); + if (!ns) { + return false; + } + + uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); + if (!mark) { + return false; + } + + MarkTreeIter itr[1]; + mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); + assert(pos.row >= 0); + marktree_del_itr(buf->b_marktree, itr, false); + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); + + if (mark & MARKTREE_PAIRED_FLAG) { + mtpos_t pos2 = marktree_lookup(buf->b_marktree, + mark|MARKTREE_END_FLAG, itr); + assert(pos2.row >= 0); + marktree_del_itr(buf->b_marktree, itr, false); + if (item.hl_id && pos2.row >= pos.row) { + redraw_buf_range_later(buf, pos.row+1, pos2.row+1); + } + } + + if (kv_size(item.virt_text)) { + redraw_buf_line_later(buf, pos.row+1); + } + clear_virttext(&item.virt_text); + + map_del(uint64_t, uint64_t)(ns->map, id); + map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); + + // TODO(bfredl): delete it from current undo header, opportunistically? + + return true; +} + +// Free extmarks in a ns between lines +// if ns = 0, it means clear all namespaces +bool extmark_clear(buf_T *buf, uint64_t ns_id, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col) +{ + if (!buf->b_extmark_ns) { + return false; + } + + bool marks_cleared = false; + + bool all_ns = (ns_id == 0); + ExtmarkNs *ns = NULL; + if (!all_ns) { + ns = buf_ns_ref(buf, ns_id, false); + if (!ns) { + // nothing to do + return false; + } + + // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes + // it could be faster to iterate over the map instead + } + + // the value is either zero or the lnum (row+1) if highlight was present. + static Map(uint64_t, uint64_t) *delete_set = NULL; + if (delete_set == NULL) { + delete_set = map_new(uint64_t, uint64_t)(); + } + + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, l_row, l_col, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 + || mark.row > u_row + || (mark.row == u_row && mark.col > u_col)) { + break; + } + uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id, + false); + if (del_status) { + marktree_del_itr(buf->b_marktree, itr, false); + map_del(uint64_t, uint64_t)(delete_set, mark.id); + if (*del_status > 0) { + redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1); + } + continue; + } + + uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, + start_id); + + assert(item.ns_id > 0 && item.mark_id > 0); + if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) { + if (kv_size(item.virt_text)) { + redraw_buf_line_later(buf, mark.row+1); + } + clear_virttext(&item.virt_text); + marks_cleared = true; + if (mark.id & MARKTREE_PAIRED_FLAG) { + uint64_t other = mark.id ^ MARKTREE_END_FLAG; + uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0; + map_put(uint64_t, uint64_t)(delete_set, other, status); + } + ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns; + map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id); + map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id); + marktree_del_itr(buf->b_marktree, itr, false); + } else { + marktree_itr_next(buf->b_marktree, itr); + } + } + uint64_t id, status; + map_foreach(delete_set, id, status, { + mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr); + assert(itr->node); + marktree_del_itr(buf->b_marktree, itr, false); + if (status > 0) { + redraw_buf_range_later(buf, (linenr_T)status, pos.row+1); + } + }); + map_clear(uint64_t, uint64_t)(delete_set); + return marks_cleared; +} + +// Returns the position of marks between a range, +// marks found at the start or end index will be included, +// if upper_lnum or upper_col are negative the buffer +// will be searched to the start, or end +// dir can be set to control the order of the array +// amount = amount of marks to find or -1 for all +ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col, + int64_t amount, bool reverse) +{ + ExtmarkArray array = KV_INITIAL_VALUE; + MarkTreeIter itr[1]; + // Find all the marks + marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col }, + itr, reverse, false, NULL); + int order = reverse ? -1 : 1; + while ((int64_t)kv_size(array) < amount) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 + || (mark.row - u_row) * order > 0 + || (mark.row == u_row && (mark.col - u_col) * order > 0)) { + break; + } + ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id); + if (item.ns_id == ns_id) { + kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id, + .mark_id = item.mark_id, + .row = mark.row, .col = mark.col })); + } + if (reverse) { + marktree_itr_prev(buf->b_marktree, itr); + } else { + marktree_itr_next(buf->b_marktree, itr); + } + } + return array; +} + +// Lookup an extmark by id +ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) +{ + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); + ExtmarkInfo ret = { 0, 0, -1, -1 }; + if (!ns) { + return ret; + } + + uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); + if (!mark) { + return ret; + } + + mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL); + assert(pos.row >= 0); + + ret.ns_id = ns_id; + ret.mark_id = id; + ret.row = pos.row; + ret.col = pos.col; + + return ret; +} + + +// free extmarks from the buffer +void extmark_free_all(buf_T *buf) +{ + if (!buf->b_extmark_ns) { + return; + } + + uint64_t id; + ExtmarkNs ns; + ExtmarkItem item; + + marktree_clear(buf->b_marktree); + + map_foreach(buf->b_extmark_ns, id, ns, { + (void)id; + map_free(uint64_t, uint64_t)(ns.map); + }); + map_free(uint64_t, ExtmarkNs)(buf->b_extmark_ns); + buf->b_extmark_ns = NULL; + + map_foreach(buf->b_extmark_index, id, item, { + (void)id; + clear_virttext(&item.virt_text); + }); + map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index); + buf->b_extmark_index = NULL; +} + + +// Save info for undo/redo of set marks +static void u_extmark_set(buf_T *buf, uint64_t mark, + int row, colnr_T col) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkSavePos pos; + pos.mark = mark; + pos.old_row = -1; + pos.old_col = -1; + pos.row = row; + pos.col = col; + + ExtmarkUndoObject undo = { .type = kExtmarkSavePos, + .data.savepos = pos }; + + kv_push(uhp->uh_extmark, undo); +} + +/// copy extmarks data between range +/// +/// useful when we cannot simply reverse the operation. This will do nothing on +/// redo, enforces correct position when undo. +void u_extmark_copy(buf_T *buf, + int l_row, colnr_T l_col, + int u_row, colnr_T u_col) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkUndoObject undo; + + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, l_row, l_col, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 + || mark.row > u_row + || (mark.row == u_row && mark.col > u_col)) { + break; + } + ExtmarkSavePos pos; + pos.mark = mark.id; + pos.old_row = mark.row; + pos.old_col = mark.col; + pos.row = -1; + pos.col = -1; + + undo.data.savepos = pos; + undo.type = kExtmarkSavePos; + kv_push(uhp->uh_extmark, undo); + + marktree_itr_next(buf->b_marktree, itr); + } +} + +/// undo or redo an extmark operation +void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) +{ + // splice: any text operation changing position (except :move) + if (undo_info.type == kExtmarkSplice) { + // Undo + ExtmarkSplice splice = undo_info.data.splice; + if (undo) { + extmark_splice(curbuf, + splice.start_row, splice.start_col, + splice.newextent_row, splice.newextent_col, + splice.oldextent_row, splice.oldextent_col, + kExtmarkNoUndo); + + } else { + extmark_splice(curbuf, + splice.start_row, splice.start_col, + splice.oldextent_row, splice.oldextent_col, + splice.newextent_row, splice.newextent_col, + kExtmarkNoUndo); + } + // kExtmarkSavePos + } else if (undo_info.type == kExtmarkSavePos) { + ExtmarkSavePos pos = undo_info.data.savepos; + if (undo) { + if (pos.old_row >= 0) { + extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col); + } + // Redo + } else { + if (pos.row >= 0) { + extmark_setraw(curbuf, pos.mark, pos.row, pos.col); + } + } + } else if (undo_info.type == kExtmarkMove) { + ExtmarkMove move = undo_info.data.move; + if (undo) { + extmark_move_region(curbuf, + move.new_row, move.new_col, + move.extent_row, move.extent_col, + move.start_row, move.start_col, + kExtmarkNoUndo); + } else { + extmark_move_region(curbuf, + move.start_row, move.start_col, + move.extent_row, move.extent_col, + move.new_row, move.new_col, + kExtmarkNoUndo); + } + } +} + + +// Adjust extmark row for inserted/deleted rows (columns stay fixed). +void extmark_adjust(buf_T *buf, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after, + ExtmarkOp undo) +{ + if (!curbuf_splice_pending) { + int old_extent, new_extent; + if (amount == MAXLNUM) { + old_extent = (int)(line2 - line1+1); + new_extent = (int)(amount_after + old_extent); + } else { + // A region is either deleted (amount == MAXLNUM) or + // added (line2 == MAXLNUM). The only other case is :move + // which is handled by a separate entry point extmark_move_region. + assert(line2 == MAXLNUM); + old_extent = 0; + new_extent = (int)amount; + } + extmark_splice(buf, + (int)line1-1, 0, + old_extent, 0, + new_extent, 0, undo); + } +} + +void extmark_splice(buf_T *buf, + int start_row, colnr_T start_col, + int oldextent_row, colnr_T oldextent_col, + int newextent_row, colnr_T newextent_col, + ExtmarkOp undo) +{ + buf_updates_send_splice(buf, start_row, start_col, + oldextent_row, oldextent_col, + newextent_row, newextent_col); + + if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) { + // Copy marks that would be effected by delete + // TODO(bfredl): Be "smart" about gravity here, left-gravity at the + // beginning and right-gravity at the end need not be preserved. + // Also be smart about marks that already have been saved (important for + // merge!) + int end_row = start_row + oldextent_row; + int end_col = (oldextent_row ? 0 : start_col) + oldextent_col; + u_extmark_copy(buf, start_row, start_col, end_row, end_col); + } + + + marktree_splice(buf->b_marktree, start_row, start_col, + oldextent_row, oldextent_col, + newextent_row, newextent_col); + + if (undo == kExtmarkUndo) { + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + bool merged = false; + // TODO(bfredl): this is quite rudimentary. We merge small (within line) + // inserts with each other and small deletes with each other. Add full + // merge algorithm later. + if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) { + ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark, + kv_size(uhp->uh_extmark)-1); + if (item->type == kExtmarkSplice) { + ExtmarkSplice *splice = &item->data.splice; + if (splice->start_row == start_row && splice->oldextent_row == 0 + && splice->newextent_row == 0) { + if (oldextent_col == 0 && start_col >= splice->start_col + && start_col <= splice->start_col+splice->newextent_col) { + splice->newextent_col += newextent_col; + merged = true; + } else if (newextent_col == 0 + && start_col == splice->start_col+splice->newextent_col) { + splice->oldextent_col += oldextent_col; + merged = true; + } else if (newextent_col == 0 + && start_col + oldextent_col == splice->start_col) { + splice->start_col = start_col; + splice->oldextent_col += oldextent_col; + merged = true; + } + } + } + } + + if (!merged) { + ExtmarkSplice splice; + splice.start_row = start_row; + splice.start_col = start_col; + splice.oldextent_row = oldextent_row; + splice.oldextent_col = oldextent_col; + splice.newextent_row = newextent_row; + splice.newextent_col = newextent_col; + + kv_push(uhp->uh_extmark, + ((ExtmarkUndoObject){ .type = kExtmarkSplice, + .data.splice = splice })); + } + } +} + + +void extmark_move_region(buf_T *buf, + int start_row, colnr_T start_col, + int extent_row, colnr_T extent_col, + int new_row, colnr_T new_col, + ExtmarkOp undo) +{ + // TODO(bfredl): this is not synced to the buffer state inside the callback. + // But unless we make the undo implementation smarter, this is not ensured + // anyway. + buf_updates_send_splice(buf, start_row, start_col, + extent_row, extent_col, + 0, 0); + + marktree_move_region(buf->b_marktree, start_row, start_col, + extent_row, extent_col, + new_row, new_col); + + buf_updates_send_splice(buf, new_row, new_col, + 0, 0, + extent_row, extent_col); + + + if (undo == kExtmarkUndo) { + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkMove move; + move.start_row = start_row; + move.start_col = start_col; + move.extent_row = extent_row; + move.extent_col = extent_col; + move.new_row = new_row; + move.new_col = new_col; + + kv_push(uhp->uh_extmark, + ((ExtmarkUndoObject){ .type = kExtmarkMove, + .data.move = move })); + } +} + +uint64_t src2ns(Integer *src_id) +{ + if (*src_id == 0) { + *src_id = (Integer)nvim_create_namespace((String)STRING_INIT); + } + if (*src_id < 0) { + return UINT64_MAX; + } else { + return (uint64_t)(*src_id); + } +} + +/// Adds a decoration to a buffer. +/// +/// Unlike matchaddpos() highlights, these follow changes to the the buffer +/// texts. Decorations are represented internally and in the API as extmarks. +/// +/// @param buf The buffer to add decorations to +/// @param ns_id A valid namespace id. +/// @param hl_id Id of the highlight group to use (or zero) +/// @param start_row The line to highlight +/// @param start_col First column to highlight +/// @param end_row The line to highlight +/// @param end_col The last column to highlight +/// @param virt_text Virtual text (currently placed at the EOL of start_row) +/// @return The extmark id inside the namespace +uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id, + int start_row, colnr_T start_col, + int end_row, colnr_T end_col, + VirtText virt_text) +{ + ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); + ExtmarkItem item; + item.ns_id = ns_id; + item.mark_id = ns->free_id++; + item.hl_id = hl_id; + item.virt_text = virt_text; + + uint64_t mark; + + if (end_row > -1) { + mark = marktree_put_pair(buf->b_marktree, + start_row, start_col, true, + end_row, end_col, false); + } else { + mark = marktree_put(buf->b_marktree, start_row, start_col, true); + } + + map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item); + map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark); + + redraw_buf_range_later(buf, start_row+1, + (end_row >= 0 ? end_row : start_row) + 1); + return item.mark_id; +} + +/// Add highlighting to a buffer, bounded by two cursor positions, +/// with an offset. +/// +/// @param buf Buffer to add highlights to +/// @param src_id src_id to use or 0 to use a new src_id group, +/// or -1 for ungrouped highlight. +/// @param hl_id Highlight group id +/// @param pos_start Cursor position to start the hightlighting at +/// @param pos_end Cursor position to end the highlighting at +/// @param offset Move the whole highlighting this many columns to the right +void bufhl_add_hl_pos_offset(buf_T *buf, + int src_id, + int hl_id, + lpos_T pos_start, + lpos_T pos_end, + colnr_T offset) +{ + colnr_T hl_start = 0; + colnr_T hl_end = 0; + + // TODO(bfredl): if decoration had blocky mode, we could avoid this loop + for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + if (pos_start.lnum < lnum && lnum < pos_end.lnum) { + hl_start = offset-1; + hl_end = MAXCOL; + } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { + hl_start = pos_start.col + offset; + hl_end = MAXCOL; + } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { + hl_start = offset-1; + hl_end = pos_end.col + offset; + } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { + hl_start = pos_start.col + offset; + hl_end = pos_end.col + offset; + } + (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id, + (int)lnum-1, hl_start, (int)lnum-1, hl_end, + VIRTTEXT_EMPTY); + } +} + +void clear_virttext(VirtText *text) +{ + for (size_t i = 0; i < kv_size(*text); i++) { + xfree(kv_A(*text, i).text); + } + kv_destroy(*text); + *text = (VirtText)KV_INITIAL_VALUE; +} + +VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) +{ + MarkTreeIter itr[1]; + marktree_itr_get(buf->b_marktree, row, 0, itr); + while (true) { + mtmark_t mark = marktree_itr_current(itr); + if (mark.row < 0 || mark.row > row) { + break; + } + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + if (item && (ns_id == 0 || ns_id == item->ns_id) + && kv_size(item->virt_text)) { + return &item->virt_text; + } + marktree_itr_next(buf->b_marktree, itr); + } + return NULL; +} + + +bool extmark_decorations_reset(buf_T *buf, DecorationState *state) +{ + state->row = -1; + return buf->b_extmark_index; +} + + +bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state) +{ + kv_size(state->active) = 0; + state->top_row = top_row; + marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); + if (!state->itr->node) { + return false; + } + marktree_itr_rewind(buf->b_marktree, state->itr); + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0) { // || mark.row > end_row + break; + } + // TODO(bfredl): dedicated flag for being a decoration? + if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { + goto next_mark; + } + mtpos_t altpos = marktree_lookup(buf->b_marktree, + mark.id^MARKTREE_END_FLAG, NULL); + + uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + start_id, false); + if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row + && !kv_size(item->virt_text)) + || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + goto next_mark; + } + + if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { + int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; + VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; + HlRange range; + if (mark.id&MARKTREE_END_FLAG) { + range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, + attr_id, vt }; + } else { + range = (HlRange){ mark.row, mark.col, altpos.row, + altpos.col, attr_id, vt }; + } + kv_push(state->active, range); + } +next_mark: + if (marktree_itr_node_done(state->itr)) { + break; + } + marktree_itr_next(buf->b_marktree, state->itr); + } + + return true; // TODO(bfredl): check if available in the region +} + +bool extmark_decorations_line(buf_T *buf, int row, DecorationState *state) +{ + if (state->row == -1) { + extmark_decorations_start(buf, row, state); + } + state->row = row; + state->col_until = -1; + return true; // TODO(bfredl): be more precise +} + +int extmark_decorations_col(buf_T *buf, int col, DecorationState *state) +{ + if (col <= state->col_until) { + return state->current; + } + state->col_until = MAXCOL; + while (true) { + mtmark_t mark = marktree_itr_current(state->itr); + if (mark.row < 0 || mark.row > state->row) { + break; + } else if (mark.row == state->row && mark.col > col) { + state->col_until = mark.col-1; + break; + } + + if ((mark.id&MARKTREE_END_FLAG)) { + // TODO(bfredl): check decorations flag + goto next_mark; + } + mtpos_t endpos = marktree_lookup(buf->b_marktree, + mark.id|MARKTREE_END_FLAG, NULL); + + ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, + mark.id, false); + + if (endpos.row < mark.row + || (endpos.row == mark.row && endpos.col <= mark.col)) { + if (!kv_size(item->virt_text)) { + goto next_mark; + } + } + + if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { + int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; + VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; + kv_push(state->active, ((HlRange){ mark.row, mark.col, + endpos.row, endpos.col, + attr_id, vt })); + } + +next_mark: + marktree_itr_next(buf->b_marktree, state->itr); + } + + int attr = 0; + size_t j = 0; + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + bool active = false, keep = true; + if (item.end_row < state->row + || (item.end_row == state->row && item.end_col <= col)) { + if (!(item.start_row >= state->row && item.virt_text)) { + keep = false; + } + } else { + if (item.start_row < state->row + || (item.start_row == state->row && item.start_col <= col)) { + active = true; + if (item.end_row == state->row) { + state->col_until = MIN(state->col_until, item.end_col-1); + } + } else { + if (item.start_row == state->row) { + state->col_until = MIN(state->col_until, item.start_col-1); + } + } + } + if (active && item.attr_id > 0) { + attr = hl_combine_attr(attr, item.attr_id); + } + if (keep) { + kv_A(state->active, j++) = kv_A(state->active, i); + } + } + kv_size(state->active) = j; + state->current = attr; + return attr; +} + +VirtText *extmark_decorations_virt_text(buf_T *buf, DecorationState *state) +{ + extmark_decorations_col(buf, MAXCOL, state); + for (size_t i = 0; i < kv_size(state->active); i++) { + HlRange item = kv_A(state->active, i); + if (item.start_row == state->row && item.virt_text) { + return item.virt_text; + } + } + return NULL; +} diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h new file mode 100644 index 0000000000..829cbe0236 --- /dev/null +++ b/src/nvim/extmark.h @@ -0,0 +1,93 @@ +#ifndef NVIM_EXTMARK_H +#define NVIM_EXTMARK_H + +#include "nvim/buffer_defs.h" +#include "nvim/extmark_defs.h" +#include "nvim/marktree.h" + +EXTERN int extmark_splice_pending INIT(= 0); + +typedef struct +{ + uint64_t ns_id; + uint64_t mark_id; + int row; + colnr_T col; +} ExtmarkInfo; + +typedef kvec_t(ExtmarkInfo) ExtmarkArray; + + +// delete the columns between mincol and endcol +typedef struct { + int start_row; + colnr_T start_col; + int oldextent_row; + colnr_T oldextent_col; + int newextent_row; + colnr_T newextent_col; +} ExtmarkSplice; + +// adjust marks after :move operation +typedef struct { + int start_row; + int start_col; + int extent_row; + int extent_col; + int new_row; + int new_col; +} ExtmarkMove; + +// extmark was updated +typedef struct { + uint64_t mark; // raw mark id of the marktree + int old_row; + colnr_T old_col; + int row; + colnr_T col; +} ExtmarkSavePos; + +typedef enum { + kExtmarkSplice, + kExtmarkMove, + kExtmarkUpdate, + kExtmarkSavePos, + kExtmarkClear, +} UndoObjectType; + +// TODO(bfredl): reduce the number of undo action types +struct undo_object { + UndoObjectType type; + union { + ExtmarkSplice splice; + ExtmarkMove move; + ExtmarkSavePos savepos; + } data; +}; + + +typedef struct { + int start_row; + int start_col; + int end_row; + int end_col; + int attr_id; + VirtText *virt_text; +} HlRange; + +typedef struct { + MarkTreeIter itr[1]; + kvec_t(HlRange) active; + int top_row; + int row; + int col_until; + int current; + VirtText *virt_text; +} DecorationState; + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "extmark.h.generated.h" +#endif + +#endif // NVIM_EXTMARK_H diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h new file mode 100644 index 0000000000..c927048981 --- /dev/null +++ b/src/nvim/extmark_defs.h @@ -0,0 +1,37 @@ +#ifndef NVIM_EXTMARK_DEFS_H +#define NVIM_EXTMARK_DEFS_H + +#include "nvim/pos.h" // for colnr_T +#include "nvim/lib/kvec.h" + +typedef struct { + char *text; + int hl_id; +} VirtTextChunk; + +typedef kvec_t(VirtTextChunk) VirtText; +#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) + +typedef struct +{ + uint64_t ns_id; + uint64_t mark_id; + int hl_id; // highlight group + // TODO(bfredl): virt_text is pretty larger than the rest, + // pointer indirection? + VirtText virt_text; +} ExtmarkItem; + +typedef struct undo_object ExtmarkUndoObject; +typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; + +// Undo/redo extmarks + +typedef enum { + kExtmarkNOOP, // Extmarks shouldn't be moved + kExtmarkUndo, // Operation should be reversable/undoable + kExtmarkNoUndo, // Operation should not be reversable + kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable +} ExtmarkOp; + +#endif // NVIM_EXTMARK_DEFS_H diff --git a/src/nvim/fold.c b/src/nvim/fold.c index addfab8f08..0b14a6affb 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -22,7 +22,7 @@ #include "nvim/func_attr.h" #include "nvim/indent.h" #include "nvim/buffer_updates.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 2c5fdd8ea6..f8018c039d 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -13,7 +13,7 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/misc1.h" diff --git a/src/nvim/map.h b/src/nvim/map.h index 761938776d..0ad7865bf0 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -4,7 +4,7 @@ #include #include "nvim/map_defs.h" -#include "nvim/mark_extended_defs.h" +#include "nvim/extmark_defs.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/highlight_defs.h" diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 4a7452493a..fa7c7d61c9 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -20,7 +20,7 @@ #include "nvim/ex_cmds.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/mark.h b/src/nvim/mark.h index d8370c367a..b3d9b5d95a 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -6,7 +6,7 @@ #include "nvim/buffer_defs.h" #include "nvim/func_attr.h" #include "nvim/mark_defs.h" -#include "nvim/mark_extended_defs.h" +#include "nvim/extmark_defs.h" #include "nvim/memory.h" #include "nvim/pos.h" #include "nvim/os/time.h" diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c deleted file mode 100644 index b60d847676..0000000000 --- a/src/nvim/mark_extended.c +++ /dev/null @@ -1,910 +0,0 @@ -// 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 - -// Implements extended marks for plugins. Each mark exists in a btree of -// lines containing btrees of columns. -// -// The btree provides efficient range lookups. -// A map of pointers to the marks is used for fast lookup by mark id. -// -// Marks are moved by calls to extmark_splice. Additionally mark_adjust -// might adjust extmarks to line inserts/deletes. -// -// Undo/Redo of marks is implemented by storing the call arguments to -// extmark_splice. The list of arguments is applied in extmark_apply_undo. -// The only case where we have to copy extmarks is for the area being effected -// by a delete. -// -// Marks live in namespaces that allow plugins/users to segregate marks -// from other users. -// -// For possible ideas for efficency improvements see: -// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html -// TODO(bfredl): These ideas could be used for an enhanced btree, which -// wouldn't need separate line and column layers. -// Other implementations exist in gtk and tk toolkits. -// -// Deleting marks only happens when explicitly calling extmark_del, deleteing -// over a range of marks will only move the marks. Deleting on a mark will -// leave it in same position unless it is on the EOL of a line. - -#include -#include "nvim/api/vim.h" -#include "nvim/vim.h" -#include "nvim/charset.h" -#include "nvim/mark_extended.h" -#include "nvim/buffer_updates.h" -#include "nvim/memline.h" -#include "nvim/pos.h" -#include "nvim/globals.h" -#include "nvim/map.h" -#include "nvim/lib/kbtree.h" -#include "nvim/undo.h" -#include "nvim/buffer.h" -#include "nvim/syntax.h" -#include "nvim/highlight.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "mark_extended.c.generated.h" -#endif - -static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) { - if (!buf->b_extmark_ns) { - if (!put) { - return NULL; - } - buf->b_extmark_ns = map_new(uint64_t, ExtmarkNs)(); - buf->b_extmark_index = map_new(uint64_t, ExtmarkItem)(); - } - - ExtmarkNs *ns = map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put); - if (put && ns->map == NULL) { - ns->map = map_new(uint64_t, uint64_t)(); - ns->free_id = 1; - } - return ns; -} - - -/// Create or update an extmark -/// -/// must not be used during iteration! -/// @returns the mark id -uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id, - int row, colnr_T col, ExtmarkOp op) -{ - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); - mtpos_t old_pos; - uint64_t mark = 0; - - if (id == 0) { - id = ns->free_id++; - } else { - uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (old_mark) { - if (old_mark & MARKTREE_PAIRED_FLAG) { - extmark_del(buf, ns_id, id); - } else { - // TODO(bfredl): we need to do more if "revising" a decoration mark. - MarkTreeIter itr[1]; - old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); - assert(itr->node); - if (old_pos.row == row && old_pos.col == col) { - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark); - mark = marktree_revise(buf->b_marktree, itr); - goto revised; - } - marktree_del_itr(buf->b_marktree, itr, false); - } - } else { - ns->free_id = MAX(ns->free_id, id+1); - } - } - - mark = marktree_put(buf->b_marktree, row, col, true); -revised: - map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, - (ExtmarkItem){ ns_id, id, 0, - KV_INITIAL_VALUE }); - map_put(uint64_t, uint64_t)(ns->map, id, mark); - - if (op != kExtmarkNoUndo) { - // TODO(bfredl): this doesn't cover all the cases and probably shouldn't - // be done "prematurely". Any movement in undo history might necessitate - // adding new marks to old undo headers. - u_extmark_set(buf, mark, row, col); - } - return id; -} - -static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) -{ - MarkTreeIter itr[1]; - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); - if (pos.row == -1) { - return false; - } - - if (pos.row == row && pos.col == col) { - return true; - } - - marktree_move(buf->b_marktree, itr, row, col); - return true; -} - -// Remove an extmark -// Returns 0 on missing id -bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) -{ - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - if (!ns) { - return false; - } - - uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (!mark) { - return false; - } - - MarkTreeIter itr[1]; - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); - assert(pos.row >= 0); - marktree_del_itr(buf->b_marktree, itr, false); - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); - - if (mark & MARKTREE_PAIRED_FLAG) { - mtpos_t pos2 = marktree_lookup(buf->b_marktree, - mark|MARKTREE_END_FLAG, itr); - assert(pos2.row >= 0); - marktree_del_itr(buf->b_marktree, itr, false); - if (item.hl_id && pos2.row >= pos.row) { - redraw_buf_range_later(buf, pos.row+1, pos2.row+1); - } - } - - if (kv_size(item.virt_text)) { - redraw_buf_line_later(buf, pos.row+1); - } - clear_virttext(&item.virt_text); - - map_del(uint64_t, uint64_t)(ns->map, id); - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); - - // TODO(bfredl): delete it from current undo header, opportunistically? - - return true; -} - -// Free extmarks in a ns between lines -// if ns = 0, it means clear all namespaces -bool extmark_clear(buf_T *buf, uint64_t ns_id, - int l_row, colnr_T l_col, - int u_row, colnr_T u_col) -{ - if (!buf->b_extmark_ns) { - return false; - } - - bool marks_cleared = false; - - bool all_ns = (ns_id == 0); - ExtmarkNs *ns = NULL; - if (!all_ns) { - ns = buf_ns_ref(buf, ns_id, false); - if (!ns) { - // nothing to do - return false; - } - - // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes - // it could be faster to iterate over the map instead - } - - // the value is either zero or the lnum (row+1) if highlight was present. - static Map(uint64_t, uint64_t) *delete_set = NULL; - if (delete_set == NULL) { - delete_set = map_new(uint64_t, uint64_t)(); - } - - MarkTreeIter itr[1]; - marktree_itr_get(buf->b_marktree, l_row, l_col, itr); - while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || mark.row > u_row - || (mark.row == u_row && mark.col > u_col)) { - break; - } - uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id, - false); - if (del_status) { - marktree_del_itr(buf->b_marktree, itr, false); - map_del(uint64_t, uint64_t)(delete_set, mark.id); - if (*del_status > 0) { - redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1); - } - continue; - } - - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id); - - assert(item.ns_id > 0 && item.mark_id > 0); - if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) { - if (kv_size(item.virt_text)) { - redraw_buf_line_later(buf, mark.row+1); - } - clear_virttext(&item.virt_text); - marks_cleared = true; - if (mark.id & MARKTREE_PAIRED_FLAG) { - uint64_t other = mark.id ^ MARKTREE_END_FLAG; - uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0; - map_put(uint64_t, uint64_t)(delete_set, other, status); - } - ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns; - map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id); - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id); - marktree_del_itr(buf->b_marktree, itr, false); - } else { - marktree_itr_next(buf->b_marktree, itr); - } - } - uint64_t id, status; - map_foreach(delete_set, id, status, { - mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr); - assert(itr->node); - marktree_del_itr(buf->b_marktree, itr, false); - if (status > 0) { - redraw_buf_range_later(buf, (linenr_T)status, pos.row+1); - } - }); - map_clear(uint64_t, uint64_t)(delete_set); - return marks_cleared; -} - -// Returns the position of marks between a range, -// marks found at the start or end index will be included, -// if upper_lnum or upper_col are negative the buffer -// will be searched to the start, or end -// dir can be set to control the order of the array -// amount = amount of marks to find or -1 for all -ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id, - int l_row, colnr_T l_col, - int u_row, colnr_T u_col, - int64_t amount, bool reverse) -{ - ExtmarkArray array = KV_INITIAL_VALUE; - MarkTreeIter itr[1]; - // Find all the marks - marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col }, - itr, reverse, false, NULL); - int order = reverse ? -1 : 1; - while ((int64_t)kv_size(array) < amount) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || (mark.row - u_row) * order > 0 - || (mark.row == u_row && (mark.col - u_col) * order > 0)) { - break; - } - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id); - if (item.ns_id == ns_id) { - kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id, - .mark_id = item.mark_id, - .row = mark.row, .col = mark.col })); - } - if (reverse) { - marktree_itr_prev(buf->b_marktree, itr); - } else { - marktree_itr_next(buf->b_marktree, itr); - } - } - return array; -} - -// Lookup an extmark by id -ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) -{ - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - ExtmarkInfo ret = { 0, 0, -1, -1 }; - if (!ns) { - return ret; - } - - uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (!mark) { - return ret; - } - - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL); - assert(pos.row >= 0); - - ret.ns_id = ns_id; - ret.mark_id = id; - ret.row = pos.row; - ret.col = pos.col; - - return ret; -} - - -// free extmarks from the buffer -void extmark_free_all(buf_T *buf) -{ - if (!buf->b_extmark_ns) { - return; - } - - uint64_t id; - ExtmarkNs ns; - ExtmarkItem item; - - marktree_clear(buf->b_marktree); - - map_foreach(buf->b_extmark_ns, id, ns, { - (void)id; - map_free(uint64_t, uint64_t)(ns.map); - }); - map_free(uint64_t, ExtmarkNs)(buf->b_extmark_ns); - buf->b_extmark_ns = NULL; - - map_foreach(buf->b_extmark_index, id, item, { - (void)id; - clear_virttext(&item.virt_text); - }); - map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index); - buf->b_extmark_index = NULL; -} - - -// Save info for undo/redo of set marks -static void u_extmark_set(buf_T *buf, uint64_t mark, - int row, colnr_T col) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } - - ExtmarkSavePos pos; - pos.mark = mark; - pos.old_row = -1; - pos.old_col = -1; - pos.row = row; - pos.col = col; - - ExtmarkUndoObject undo = { .type = kExtmarkSavePos, - .data.savepos = pos }; - - kv_push(uhp->uh_extmark, undo); -} - -/// copy extmarks data between range -/// -/// useful when we cannot simply reverse the operation. This will do nothing on -/// redo, enforces correct position when undo. -void u_extmark_copy(buf_T *buf, - int l_row, colnr_T l_col, - int u_row, colnr_T u_col) -{ - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } - - ExtmarkUndoObject undo; - - MarkTreeIter itr[1]; - marktree_itr_get(buf->b_marktree, l_row, l_col, itr); - while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || mark.row > u_row - || (mark.row == u_row && mark.col > u_col)) { - break; - } - ExtmarkSavePos pos; - pos.mark = mark.id; - pos.old_row = mark.row; - pos.old_col = mark.col; - pos.row = -1; - pos.col = -1; - - undo.data.savepos = pos; - undo.type = kExtmarkSavePos; - kv_push(uhp->uh_extmark, undo); - - marktree_itr_next(buf->b_marktree, itr); - } -} - -/// undo or redo an extmark operation -void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) -{ - // splice: any text operation changing position (except :move) - if (undo_info.type == kExtmarkSplice) { - // Undo - ExtmarkSplice splice = undo_info.data.splice; - if (undo) { - extmark_splice(curbuf, - splice.start_row, splice.start_col, - splice.newextent_row, splice.newextent_col, - splice.oldextent_row, splice.oldextent_col, - kExtmarkNoUndo); - - } else { - extmark_splice(curbuf, - splice.start_row, splice.start_col, - splice.oldextent_row, splice.oldextent_col, - splice.newextent_row, splice.newextent_col, - kExtmarkNoUndo); - } - // kExtmarkSavePos - } else if (undo_info.type == kExtmarkSavePos) { - ExtmarkSavePos pos = undo_info.data.savepos; - if (undo) { - if (pos.old_row >= 0) { - extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col); - } - // Redo - } else { - if (pos.row >= 0) { - extmark_setraw(curbuf, pos.mark, pos.row, pos.col); - } - } - } else if (undo_info.type == kExtmarkMove) { - ExtmarkMove move = undo_info.data.move; - if (undo) { - extmark_move_region(curbuf, - move.new_row, move.new_col, - move.extent_row, move.extent_col, - move.start_row, move.start_col, - kExtmarkNoUndo); - } else { - extmark_move_region(curbuf, - move.start_row, move.start_col, - move.extent_row, move.extent_col, - move.new_row, move.new_col, - kExtmarkNoUndo); - } - } -} - - -// Adjust extmark row for inserted/deleted rows (columns stay fixed). -void extmark_adjust(buf_T *buf, - linenr_T line1, - linenr_T line2, - long amount, - long amount_after, - ExtmarkOp undo) -{ - if (!curbuf_splice_pending) { - int old_extent, new_extent; - if (amount == MAXLNUM) { - old_extent = (int)(line2 - line1+1); - new_extent = (int)(amount_after + old_extent); - } else { - // A region is either deleted (amount == MAXLNUM) or - // added (line2 == MAXLNUM). The only other case is :move - // which is handled by a separate entry point extmark_move_region. - assert(line2 == MAXLNUM); - old_extent = 0; - new_extent = (int)amount; - } - extmark_splice(buf, - (int)line1-1, 0, - old_extent, 0, - new_extent, 0, undo); - } -} - -void extmark_splice(buf_T *buf, - int start_row, colnr_T start_col, - int oldextent_row, colnr_T oldextent_col, - int newextent_row, colnr_T newextent_col, - ExtmarkOp undo) -{ - buf_updates_send_splice(buf, start_row, start_col, - oldextent_row, oldextent_col, - newextent_row, newextent_col); - - if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) { - // Copy marks that would be effected by delete - // TODO(bfredl): Be "smart" about gravity here, left-gravity at the - // beginning and right-gravity at the end need not be preserved. - // Also be smart about marks that already have been saved (important for - // merge!) - int end_row = start_row + oldextent_row; - int end_col = (oldextent_row ? 0 : start_col) + oldextent_col; - u_extmark_copy(buf, start_row, start_col, end_row, end_col); - } - - - marktree_splice(buf->b_marktree, start_row, start_col, - oldextent_row, oldextent_col, - newextent_row, newextent_col); - - if (undo == kExtmarkUndo) { - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } - - bool merged = false; - // TODO(bfredl): this is quite rudimentary. We merge small (within line) - // inserts with each other and small deletes with each other. Add full - // merge algorithm later. - if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) { - ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark, - kv_size(uhp->uh_extmark)-1); - if (item->type == kExtmarkSplice) { - ExtmarkSplice *splice = &item->data.splice; - if (splice->start_row == start_row && splice->oldextent_row == 0 - && splice->newextent_row == 0) { - if (oldextent_col == 0 && start_col >= splice->start_col - && start_col <= splice->start_col+splice->newextent_col) { - splice->newextent_col += newextent_col; - merged = true; - } else if (newextent_col == 0 - && start_col == splice->start_col+splice->newextent_col) { - splice->oldextent_col += oldextent_col; - merged = true; - } else if (newextent_col == 0 - && start_col + oldextent_col == splice->start_col) { - splice->start_col = start_col; - splice->oldextent_col += oldextent_col; - merged = true; - } - } - } - } - - if (!merged) { - ExtmarkSplice splice; - splice.start_row = start_row; - splice.start_col = start_col; - splice.oldextent_row = oldextent_row; - splice.oldextent_col = oldextent_col; - splice.newextent_row = newextent_row; - splice.newextent_col = newextent_col; - - kv_push(uhp->uh_extmark, - ((ExtmarkUndoObject){ .type = kExtmarkSplice, - .data.splice = splice })); - } - } -} - - -void extmark_move_region(buf_T *buf, - int start_row, colnr_T start_col, - int extent_row, colnr_T extent_col, - int new_row, colnr_T new_col, - ExtmarkOp undo) -{ - // TODO(bfredl): this is not synced to the buffer state inside the callback. - // But unless we make the undo implementation smarter, this is not ensured - // anyway. - buf_updates_send_splice(buf, start_row, start_col, - extent_row, extent_col, - 0, 0); - - marktree_move_region(buf->b_marktree, start_row, start_col, - extent_row, extent_col, - new_row, new_col); - - buf_updates_send_splice(buf, new_row, new_col, - 0, 0, - extent_row, extent_col); - - - if (undo == kExtmarkUndo) { - u_header_T *uhp = u_force_get_undo_header(buf); - if (!uhp) { - return; - } - - ExtmarkMove move; - move.start_row = start_row; - move.start_col = start_col; - move.extent_row = extent_row; - move.extent_col = extent_col; - move.new_row = new_row; - move.new_col = new_col; - - kv_push(uhp->uh_extmark, - ((ExtmarkUndoObject){ .type = kExtmarkMove, - .data.move = move })); - } -} - -uint64_t src2ns(Integer *src_id) -{ - if (*src_id == 0) { - *src_id = (Integer)nvim_create_namespace((String)STRING_INIT); - } - if (*src_id < 0) { - return UINT64_MAX; - } else { - return (uint64_t)(*src_id); - } -} - -/// Adds a decoration to a buffer. -/// -/// Unlike matchaddpos() highlights, these follow changes to the the buffer -/// texts. Decorations are represented internally and in the API as extmarks. -/// -/// @param buf The buffer to add decorations to -/// @param ns_id A valid namespace id. -/// @param hl_id Id of the highlight group to use (or zero) -/// @param start_row The line to highlight -/// @param start_col First column to highlight -/// @param end_row The line to highlight -/// @param end_col The last column to highlight -/// @param virt_text Virtual text (currently placed at the EOL of start_row) -/// @return The extmark id inside the namespace -uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id, - int start_row, colnr_T start_col, - int end_row, colnr_T end_col, - VirtText virt_text) -{ - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); - ExtmarkItem item; - item.ns_id = ns_id; - item.mark_id = ns->free_id++; - item.hl_id = hl_id; - item.virt_text = virt_text; - - uint64_t mark; - - if (end_row > -1) { - mark = marktree_put_pair(buf->b_marktree, - start_row, start_col, true, - end_row, end_col, false); - } else { - mark = marktree_put(buf->b_marktree, start_row, start_col, true); - } - - map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item); - map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark); - - redraw_buf_range_later(buf, start_row+1, - (end_row >= 0 ? end_row : start_row) + 1); - return item.mark_id; -} - -/// Add highlighting to a buffer, bounded by two cursor positions, -/// with an offset. -/// -/// @param buf Buffer to add highlights to -/// @param src_id src_id to use or 0 to use a new src_id group, -/// or -1 for ungrouped highlight. -/// @param hl_id Highlight group id -/// @param pos_start Cursor position to start the hightlighting at -/// @param pos_end Cursor position to end the highlighting at -/// @param offset Move the whole highlighting this many columns to the right -void bufhl_add_hl_pos_offset(buf_T *buf, - int src_id, - int hl_id, - lpos_T pos_start, - lpos_T pos_end, - colnr_T offset) -{ - colnr_T hl_start = 0; - colnr_T hl_end = 0; - - // TODO(bfredl): if decoration had blocky mode, we could avoid this loop - for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { - if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - hl_start = offset-1; - hl_end = MAXCOL; - } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { - hl_start = pos_start.col + offset; - hl_end = MAXCOL; - } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = offset-1; - hl_end = pos_end.col + offset; - } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { - hl_start = pos_start.col + offset; - hl_end = pos_end.col + offset; - } - (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id, - (int)lnum-1, hl_start, (int)lnum-1, hl_end, - VIRTTEXT_EMPTY); - } -} - -void clear_virttext(VirtText *text) -{ - for (size_t i = 0; i < kv_size(*text); i++) { - xfree(kv_A(*text, i).text); - } - kv_destroy(*text); - *text = (VirtText)KV_INITIAL_VALUE; -} - -VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id) -{ - MarkTreeIter itr[1]; - marktree_itr_get(buf->b_marktree, row, 0, itr); - while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 || mark.row > row) { - break; - } - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (item && (ns_id == 0 || ns_id == item->ns_id) - && kv_size(item->virt_text)) { - return &item->virt_text; - } - marktree_itr_next(buf->b_marktree, itr); - } - return NULL; -} - - -bool extmark_decorations_reset(buf_T *buf, DecorationState *state) -{ - state->row = -1; - return buf->b_extmark_index; -} - - -bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state) -{ - kv_size(state->active) = 0; - state->top_row = top_row; - marktree_itr_get(buf->b_marktree, top_row, 0, state->itr); - if (!state->itr->node) { - return false; - } - marktree_itr_rewind(buf->b_marktree, state->itr); - while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0) { // || mark.row > end_row - break; - } - // TODO(bfredl): dedicated flag for being a decoration? - if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) { - goto next_mark; - } - mtpos_t altpos = marktree_lookup(buf->b_marktree, - mark.id^MARKTREE_END_FLAG, NULL); - - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id, false); - if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && !kv_size(item->virt_text)) - || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { - goto next_mark; - } - - if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { - int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; - VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; - HlRange range; - if (mark.id&MARKTREE_END_FLAG) { - range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col, - attr_id, vt }; - } else { - range = (HlRange){ mark.row, mark.col, altpos.row, - altpos.col, attr_id, vt }; - } - kv_push(state->active, range); - } -next_mark: - if (marktree_itr_node_done(state->itr)) { - break; - } - marktree_itr_next(buf->b_marktree, state->itr); - } - - return true; // TODO(bfredl): check if available in the region -} - -bool extmark_decorations_line(buf_T *buf, int row, DecorationState *state) -{ - if (state->row == -1) { - extmark_decorations_start(buf, row, state); - } - state->row = row; - state->col_until = -1; - return true; // TODO(bfredl): be more precise -} - -int extmark_decorations_col(buf_T *buf, int col, DecorationState *state) -{ - if (col <= state->col_until) { - return state->current; - } - state->col_until = MAXCOL; - while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0 || mark.row > state->row) { - break; - } else if (mark.row == state->row && mark.col > col) { - state->col_until = mark.col-1; - break; - } - - if ((mark.id&MARKTREE_END_FLAG)) { - // TODO(bfredl): check decorations flag - goto next_mark; - } - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); - - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - - if (endpos.row < mark.row - || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (!kv_size(item->virt_text)) { - goto next_mark; - } - } - - if (item && (item->hl_id > 0 || kv_size(item->virt_text))) { - int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0; - VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL; - kv_push(state->active, ((HlRange){ mark.row, mark.col, - endpos.row, endpos.col, - attr_id, vt })); - } - -next_mark: - marktree_itr_next(buf->b_marktree, state->itr); - } - - int attr = 0; - size_t j = 0; - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - bool active = false, keep = true; - if (item.end_row < state->row - || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && item.virt_text)) { - keep = false; - } - } else { - if (item.start_row < state->row - || (item.start_row == state->row && item.start_col <= col)) { - active = true; - if (item.end_row == state->row) { - state->col_until = MIN(state->col_until, item.end_col-1); - } - } else { - if (item.start_row == state->row) { - state->col_until = MIN(state->col_until, item.start_col-1); - } - } - } - if (active && item.attr_id > 0) { - attr = hl_combine_attr(attr, item.attr_id); - } - if (keep) { - kv_A(state->active, j++) = kv_A(state->active, i); - } - } - kv_size(state->active) = j; - state->current = attr; - return attr; -} - -VirtText *extmark_decorations_virt_text(buf_T *buf, DecorationState *state) -{ - extmark_decorations_col(buf, MAXCOL, state); - for (size_t i = 0; i < kv_size(state->active); i++) { - HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text) { - return item.virt_text; - } - } - return NULL; -} diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h deleted file mode 100644 index f809148d9b..0000000000 --- a/src/nvim/mark_extended.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef NVIM_MARK_EXTENDED_H -#define NVIM_MARK_EXTENDED_H - -#include "nvim/buffer_defs.h" -#include "nvim/mark_extended_defs.h" -#include "nvim/marktree.h" - -EXTERN int extmark_splice_pending INIT(= 0); - -typedef struct -{ - uint64_t ns_id; - uint64_t mark_id; - int row; - colnr_T col; -} ExtmarkInfo; - -typedef kvec_t(ExtmarkInfo) ExtmarkArray; - - -// delete the columns between mincol and endcol -typedef struct { - int start_row; - colnr_T start_col; - int oldextent_row; - colnr_T oldextent_col; - int newextent_row; - colnr_T newextent_col; -} ExtmarkSplice; - -// adjust marks after :move operation -typedef struct { - int start_row; - int start_col; - int extent_row; - int extent_col; - int new_row; - int new_col; -} ExtmarkMove; - -// extmark was updated -typedef struct { - uint64_t mark; // raw mark id of the marktree - int old_row; - colnr_T old_col; - int row; - colnr_T col; -} ExtmarkSavePos; - -typedef enum { - kExtmarkSplice, - kExtmarkMove, - kExtmarkUpdate, - kExtmarkSavePos, - kExtmarkClear, -} UndoObjectType; - -// TODO(bfredl): reduce the number of undo action types -struct undo_object { - UndoObjectType type; - union { - ExtmarkSplice splice; - ExtmarkMove move; - ExtmarkSavePos savepos; - } data; -}; - - -typedef struct { - int start_row; - int start_col; - int end_row; - int end_col; - int attr_id; - VirtText *virt_text; -} HlRange; - -typedef struct { - MarkTreeIter itr[1]; - kvec_t(HlRange) active; - int top_row; - int row; - int col_until; - int current; - VirtText *virt_text; -} DecorationState; - - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "mark_extended.h.generated.h" -#endif - -#endif // NVIM_MARK_EXTENDED_H diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h deleted file mode 100644 index 439f7f0b36..0000000000 --- a/src/nvim/mark_extended_defs.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef NVIM_MARK_EXTENDED_DEFS_H -#define NVIM_MARK_EXTENDED_DEFS_H - -#include "nvim/pos.h" // for colnr_T -#include "nvim/lib/kvec.h" - -typedef struct { - char *text; - int hl_id; -} VirtTextChunk; - -typedef kvec_t(VirtTextChunk) VirtText; -#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) - -typedef struct -{ - uint64_t ns_id; - uint64_t mark_id; - int hl_id; // highlight group - // TODO(bfredl): virt_text is pretty larger than the rest, - // pointer indirection? - VirtText virt_text; -} ExtmarkItem; - -typedef struct undo_object ExtmarkUndoObject; -typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; - -// Undo/redo extmarks - -typedef enum { - kExtmarkNOOP, // Extmarks shouldn't be moved - kExtmarkUndo, // Operation should be reversable/undoable - kExtmarkNoUndo, // Operation should not be reversable - kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable -} ExtmarkOp; - -#endif // NVIM_MARK_EXTENDED_DEFS_H diff --git a/src/nvim/ops.c b/src/nvim/ops.c index da2b81fd0a..641323ae5e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -31,7 +31,7 @@ #include "nvim/indent.h" #include "nvim/log.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/screen.c b/src/nvim/screen.c index cb155cfc65..a451451726 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -87,7 +87,7 @@ #include "nvim/highlight.h" #include "nvim/main.h" #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" diff --git a/src/nvim/undo.c b/src/nvim/undo.c index fda647106d..1f74bada41 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -93,7 +93,7 @@ #include "nvim/buffer_updates.h" #include "nvim/pos.h" // MAXLNUM #include "nvim/mark.h" -#include "nvim/mark_extended.h" +#include "nvim/extmark.h" #include "nvim/memline.h" #include "nvim/message.h" #include "nvim/misc1.h" diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 0fa3b415ec..cc2c39a711 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -4,7 +4,7 @@ #include // for time_t #include "nvim/pos.h" -#include "nvim/mark_extended_defs.h" +#include "nvim/extmark_defs.h" #include "nvim/mark_defs.h" typedef struct u_header u_header_T; -- cgit From f245c0218adc9ff3452660dff97e62cea8e9a411 Mon Sep 17 00:00:00 2001 From: butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> Date: Mon, 20 Jan 2020 15:14:51 -0800 Subject: tabpage: "tabnext #" switches to previous tab #11734 --- src/nvim/ex_docmd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6bda62594e..02bee838d5 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4651,6 +4651,8 @@ static int get_tabpage_arg(exarg_T *eap) if (relative == 0) { if (STRCMP(p, "$") == 0) { tab_number = LAST_TAB_NR; + } else if (STRCMP(p, "#") == 0) { + tab_number = tabpage_index(lastused_tabpage); } else if (p == p_save || *p_save == '-' || *p != NUL || tab_number > LAST_TAB_NR) { // No numbers as argument. -- cgit From da0cd5436b4a50e681f490ec6fc7730a894b1542 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 18 Jan 2020 10:59:17 -0500 Subject: doc: provider-perl Enable 'has("perl")' as an alias for 'g:loaded_perl_provider'. TODO: - +perl interface --- src/nvim/eval.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 797c61a482..8c4261f4ba 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -24611,6 +24611,7 @@ bool eval_has_provider(const char *feat) && !strequal(feat, "python_dynamic") && !strequal(feat, "python3_compiled") && !strequal(feat, "python3_dynamic") + && !strequal(feat, "perl") && !strequal(feat, "ruby") && !strequal(feat, "node")) { // Avoid autoload for non-provider has() features. -- cgit From 97dcc48c998ccecaa37a3cbea568d85c2f1407f9 Mon Sep 17 00:00:00 2001 From: akovaski Date: Tue, 21 Jan 2020 02:35:01 -0600 Subject: wildmode: fix wildmode=longest,full with pum #11690 With "wildmode=longest,full" + wildoptions=pum, wildmode should show popupmenu after Tab-Tab, not the horizontal wildmenu. Fixes #11622 --- src/nvim/ex_getln.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 551482dab3..e6b7bfaebf 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -941,8 +941,10 @@ static int command_line_execute(VimState *state, int key) // if 'wildmode' contains "list" may still need to list if (s->xpc.xp_numfiles > 1 && !s->did_wild_list - && (wim_flags[s->wim_index] & WIM_LIST)) { - (void)showmatches(&s->xpc, false); + && ((wim_flags[s->wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) { + (void)showmatches(&s->xpc, p_wmnu + && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); redrawcmd(); s->did_wild_list = true; } -- cgit From e53e860759e8b74a98072bc333a49b48649ad6f9 Mon Sep 17 00:00:00 2001 From: Billy SU Date: Wed, 22 Jan 2020 15:47:32 +0800 Subject: vim-patch:8.1.0061: fix resetting, setting 'title' #11733 Problem: Window title is wrong after resetting and setting 'title'. Solution: Move resetting the title into maketitle(). (Jason Franklin) https://github.com/vim/vim/commit/84a9308511871d9ff94c91a1c6badb92300ded98 --- src/nvim/buffer.c | 65 ++++++++++++++++++++++++++++--------------------------- src/nvim/option.c | 11 ++++------ 2 files changed, 37 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index eb71a8fc5e..5083780719 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3068,18 +3068,15 @@ void col_print(char_u *buf, size_t buflen, int col, int vcol) } } -/* - * put file name in title bar of window and in icon title - */ - static char_u *lasttitle = NULL; static char_u *lasticon = NULL; + +// Put the title name in the title bar and icon of the window. void maketitle(void) { - char_u *t_str = NULL; - char_u *i_name; - char_u *i_str = NULL; + char_u *title_str = NULL; + char_u *icon_str = NULL; int maxlen = 0; int len; int mustset; @@ -3093,7 +3090,7 @@ void maketitle(void) need_maketitle = false; if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) { - return; + return; // nothing to do } if (p_title) { @@ -3114,14 +3111,14 @@ void maketitle(void) build_stl_str_hl(curwin, (char_u *)buf, sizeof(buf), p_titlestring, use_sandbox, 0, maxlen, NULL, NULL); - t_str = (char_u *)buf; + title_str = (char_u *)buf; if (called_emsg) { set_string_option_direct((char_u *)"titlestring", -1, (char_u *)"", OPT_FREE, SID_ERROR); } called_emsg |= save_called_emsg; } else { - t_str = p_titlestring; + title_str = p_titlestring; } } else { // Format: "fname + (path) (1 of 2) - VIM". @@ -3205,16 +3202,16 @@ void maketitle(void) trunc_string((char_u *)buf, (char_u *)buf, maxlen, sizeof(buf)); } } - t_str = (char_u *)buf; + title_str = (char_u *)buf; #undef SPACE_FOR_FNAME #undef SPACE_FOR_DIR #undef SPACE_FOR_ARGNR } } - mustset = ti_change(t_str, &lasttitle); + mustset = value_change(title_str, &lasttitle); if (p_icon) { - i_str = (char_u *)buf; + icon_str = (char_u *)buf; if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { int use_sandbox = false; @@ -3222,37 +3219,40 @@ void maketitle(void) use_sandbox = was_set_insecurely((char_u *)"iconstring", 0); called_emsg = false; - build_stl_str_hl(curwin, i_str, sizeof(buf), - p_iconstring, use_sandbox, - 0, 0, NULL, NULL); - if (called_emsg) + build_stl_str_hl(curwin, icon_str, sizeof(buf), + p_iconstring, use_sandbox, + 0, 0, NULL, NULL); + if (called_emsg) { set_string_option_direct((char_u *)"iconstring", -1, - (char_u *)"", OPT_FREE, SID_ERROR); + (char_u *)"", OPT_FREE, SID_ERROR); + } called_emsg |= save_called_emsg; - } else - i_str = p_iconstring; + } else { + icon_str = p_iconstring; + } } else { + char_u *buf_p; if (buf_spname(curbuf) != NULL) { - i_name = buf_spname(curbuf); + buf_p = buf_spname(curbuf); } else { // use file name only in icon - i_name = path_tail(curbuf->b_ffname); + buf_p = path_tail(curbuf->b_ffname); } - *i_str = NUL; + *icon_str = NUL; // Truncate name at 100 bytes. - len = (int)STRLEN(i_name); + len = (int)STRLEN(buf_p); if (len > 100) { len -= 100; if (has_mbyte) { - len += (*mb_tail_off)(i_name, i_name + len) + 1; + len += (*mb_tail_off)(buf_p, buf_p + len) + 1; } - i_name += len; + buf_p += len; } - STRCPY(i_str, i_name); - trans_characters(i_str, IOSIZE); + STRCPY(icon_str, buf_p); + trans_characters(icon_str, IOSIZE); } } - mustset |= ti_change(i_str, &lasticon); + mustset |= value_change(icon_str, &lasticon); if (mustset) { resettitle(); @@ -3266,8 +3266,8 @@ void maketitle(void) /// @param str desired title string /// @param[in,out] last current title string // -/// @return true when "*last" changed. -static bool ti_change(char_u *str, char_u **last) +/// @return true if resettitle() is to be called. +static bool value_change(char_u *str, char_u **last) FUNC_ATTR_WARN_UNUSED_RESULT { if ((str == NULL) != (*last == NULL) @@ -3275,10 +3275,11 @@ static bool ti_change(char_u *str, char_u **last) xfree(*last); if (str == NULL) { *last = NULL; + resettitle(); } else { *last = vim_strsave(str); + return true; } - return true; } return false; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 2871a4b7de..f03dcc2bf2 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2021,13 +2021,10 @@ static char_u *check_cedit(void) // maketitle() to create and display it. // When switching the title or icon off, call ui_set_{icon,title}(NULL) to get // the old value back. -static void did_set_title( - int icon // Did set icon instead of title -) +static void did_set_title(void) { if (starting != NO_SCREEN) { maketitle(); - resettitle(); } } @@ -2986,7 +2983,7 @@ ambw_end: } else { stl_syntax &= ~flagval; } - did_set_title(varp == &p_iconstring); + did_set_title(); } else if (varp == &p_sel) { // 'selection' if (*p_sel == NUL @@ -4025,9 +4022,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, (void)buf_init_chartab(curbuf, false); // ignore errors } else if ((int *)varp == &p_title) { // when 'title' changed, may need to change the title; same for 'icon' - did_set_title(false); + did_set_title(); } else if ((int *)varp == &p_icon) { - did_set_title(true); + did_set_title(); } else if ((int *)varp == &curbuf->b_changed) { if (!value) { save_file_ff(curbuf); // Buffer is unchanged -- cgit From 041ec8997a7f613f2ca13bf339c652f117fcb809 Mon Sep 17 00:00:00 2001 From: Billy Su Date: Mon, 20 Jan 2020 20:06:38 +0800 Subject: Fix f_jobstop() failed loudly The return value of jobstop() @return 1 for valid job id 0 for invalid id, including jobs have exited or stopped --- src/nvim/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 797c61a482..5eec44a7b7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -12706,7 +12706,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Channel *data = find_job(argvars[0].vval.v_number, true); + Channel *data = find_job(argvars[0].vval.v_number, false); if (!data) { return; } -- cgit From 71ee46accfd235bad2b8460065100b31bb2d8165 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 22 Jan 2020 20:21:05 -0500 Subject: vim-patch:8.2.0141: no swift filetype detection (#11747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: No swift filetype detection. Solution: Add swift, swiftgyb and sil. (Emir Sarı, closes vim/vim#5517) https://github.com/vim/vim/commit/0d76683e094c6cac2e879601aff3acf1163cbe0b --- src/nvim/testdir/test_filetype.vim | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index cc0037b4cf..2334cc95a7 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -431,6 +431,9 @@ let s:filename_checks = { \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp'], \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp'], + \ 'swift': ['file.swift'], + \ 'swiftgyb': ['file.swift.gyb'], + \ 'sil': ['file.sil'], \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'], \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file'], \ 'systemverilog': ['file.sv', 'file.svh'], -- cgit From 961c528afc7f3a21a8c4b118a2c585dbe670c5c5 Mon Sep 17 00:00:00 2001 From: Billy Su Date: Wed, 22 Jan 2020 17:50:33 +0800 Subject: ex_getln.c: wildmenu add cancel and apply ops --- src/nvim/ex_getln.c | 18 +++++++++++++++++- src/nvim/ex_getln.h | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 551482dab3..380dc43b50 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -621,6 +621,16 @@ static int command_line_execute(VimState *state, int key) s->c = Ctrl_N; } } + if (compl_match_array || s->did_wild_list) { + if (s->c == Ctrl_E) { + s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP, + s->firstc != '@'); + } else if (s->c == Ctrl_Y) { + s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, + s->firstc != '@'); + s->c = Ctrl_E; + } + } // Hitting CR after "emenu Name.": complete submenu if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu @@ -3788,6 +3798,12 @@ ExpandOne ( return NULL; } + if (mode == WILD_CANCEL) { + ss = vim_strsave(orig_save); + } else if (mode == WILD_APPLY) { + ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]); + } + /* free old names */ if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { FreeWild(xp->xp_numfiles, xp->xp_files); @@ -3799,7 +3815,7 @@ ExpandOne ( if (mode == WILD_FREE) /* only release file name */ return NULL; - if (xp->xp_numfiles == -1) { + if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) { xfree(orig_save); orig_save = orig; orig_saved = TRUE; diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 99d5a7786d..84b2b41f30 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -16,6 +16,8 @@ #define WILD_ALL 6 #define WILD_LONGEST 7 #define WILD_ALL_KEEP 8 +#define WILD_CANCEL 9 +#define WILD_APPLY 10 #define WILD_LIST_NOTFOUND 0x01 #define WILD_HOME_REPLACE 0x02 -- cgit From 376fa27237d0a1ce5ef9967473920ac7d68fcf29 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 23 Jan 2020 19:11:32 -0500 Subject: vim-patch:8.2.0146: wrong indent when 'showbreak' and 'breakindent' are set Problem: Wrong indent when 'showbreak' and 'breakindent' are set and 'briopt' includes "sbr". Solution: Reset "need_showbreak" where needed. (Ken Takata, closes vim/vim#5523) https://github.com/vim/vim/commit/dfede9a70b3136988a8f262742101ad5cb98f46d --- src/nvim/screen.c | 30 +++++++++++++++++++----------- src/nvim/testdir/test_breakindent.vim | 27 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a451451726..3e59169918 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2243,7 +2243,7 @@ win_line ( int change_start = MAXCOL; // first col of changed area int change_end = -1; // last col of changed area colnr_T trailcol = MAXCOL; // start of trailing spaces - int need_showbreak = false; // overlong line, skip first x chars + bool need_showbreak = false; // overlong line, skip first x chars int line_attr = 0; // attribute for the whole line int line_attr_lowprio = 0; // low-priority attribute for the line matchitem_T *cur; // points to the match list @@ -2654,11 +2654,12 @@ win_line ( else if (fromcol >= 0 && fromcol < vcol) fromcol = vcol; - /* When w_skipcol is non-zero, first line needs 'showbreak' */ - if (wp->w_p_wrap) - need_showbreak = TRUE; - /* When spell checking a word we need to figure out the start of the - * word and if it's badly spelled or not. */ + // When w_skipcol is non-zero, first line needs 'showbreak' + if (wp->w_p_wrap) { + need_showbreak = true; + } + // When spell checking a word we need to figure out the start of the + // word and if it's badly spelled or not. if (has_spell) { size_t len; colnr_T linecol = (colnr_T)(ptr - line); @@ -2975,11 +2976,16 @@ win_line ( } p_extra = NULL; c_extra = ' '; - n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, FALSE)); - /* Correct end of highlighted area for 'breakindent', - required wen 'linebreak' is also set. */ - if (tocol == vcol) + n_extra = + get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); + if (wp->w_skipcol > 0 && wp->w_p_wrap) { + need_showbreak = false; + } + // Correct end of highlighted area for 'breakindent', + // required wen 'linebreak' is also set. + if (tocol == vcol) { tocol += n_extra; + } } } @@ -3008,7 +3014,9 @@ win_line ( c_final = NUL; n_extra = (int)STRLEN(p_sbr); char_attr = win_hl_attr(wp, HLF_AT); - need_showbreak = false; + if (wp->w_skipcol == 0 || !wp->w_p_wrap) { + need_showbreak = false; + } vcol_sbr = vcol + MB_CHARLEN(p_sbr); /* Correct end of highlighted area for 'showbreak', * required when 'linebreak' is also set. */ diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 4b34420cab..9717043976 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -296,3 +296,30 @@ function Test_breakindent16() call s:compare_lines(expect, lines) call s:close_windows() endfunction + +func Test_breakindent19_sbr_nextpage() + let s:input = "" + call s:test_windows('setl breakindent briopt=shift:2,sbr,min:18 sbr=>') + call setline(1, repeat('a', 200)) + norm! 1gg + redraw! + let lines = s:screen_lines(1, 20) + let expect = [ + \ "aaaaaaaaaaaaaaaaaaaa", + \ "> aaaaaaaaaaaaaaaaaa", + \ "> aaaaaaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) + " Scroll down one screen line + setl scrolloff=5 + norm! 5gj + redraw! + let lines = s:screen_lines(1, 20) + let expect = [ + \ "> aaaaaaaaaaaaaaaaaa", + \ "> aaaaaaaaaaaaaaaaaa", + \ "> aaaaaaaaaaaaaaaaaa", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set breakindent& briopt& sbr&') +endfunc -- cgit From e22d0cf12c985e670fcc562a6ce75e82a0b3a741 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 23 Jan 2020 19:36:39 -0500 Subject: vim-patch:8.2.0147: block Visual mode operators not correct when 'linebreak' set Problem: Block Visual mode operators not correct when 'linebreak' set. Solution: Set w_p_lbr to lbr_saved more often. (Ken Takata, closes vim/vim#5524) https://github.com/vim/vim/commit/03c3bd9fd094c1aede2e8fe3ad8fd25b9f033053 --- src/nvim/normal.c | 15 +++------------ src/nvim/ops.c | 4 ++++ src/nvim/testdir/test_listlbr.vim | 31 +++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 3aff3cef84..be6819d19f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1846,10 +1846,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) restart_edit = 0; // Restore linebreak, so that when the user edits it looks as before. - if (curwin->w_p_lbr != lbr_saved) { - curwin->w_p_lbr = lbr_saved; - get_op_vcol(oap, redo_VIsual_mode, false); - } + curwin->w_p_lbr = lbr_saved; // Reset finish_op now, don't want it set inside edit(). finish_op = false; @@ -1935,10 +1932,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) restart_edit = 0; // Restore linebreak, so that when the user edits it looks as before. - if (curwin->w_p_lbr != lbr_saved) { - curwin->w_p_lbr = lbr_saved; - get_op_vcol(oap, redo_VIsual_mode, false); - } + curwin->w_p_lbr = lbr_saved; op_insert(oap, cap->count1); @@ -1964,10 +1958,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) CancelRedo(); } else { // Restore linebreak, so that when the user edits it looks as before. - if (curwin->w_p_lbr != lbr_saved) { - curwin->w_p_lbr = lbr_saved; - get_op_vcol(oap, redo_VIsual_mode, false); - } + curwin->w_p_lbr = lbr_saved; op_replace(oap, cap->nchar); } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 641323ae5e..bcf54087f5 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4414,7 +4414,10 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, char_u *line; char_u *prev_pstart; char_u *prev_pend; + const int lbr_saved = curwin->w_p_lbr; + // Avoid a problem with unwanted linebreaks in block mode. + curwin->w_p_lbr = false; bdp->startspaces = 0; bdp->endspaces = 0; bdp->textlen = 0; @@ -4514,6 +4517,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, } bdp->textcol = (colnr_T) (pstart - line); bdp->textstart = pstart; + curwin->w_p_lbr = lbr_saved; } /// Handle the add/subtract operator. diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index d28dbc444c..cdc5e4cc7c 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -103,6 +103,37 @@ func Test_linebreak_with_conceal() call s:close_windows() endfunc +func Test_linebreak_with_visual_operations() + call s:test_windows() + let line = '1234567890 2234567890 3234567890' + call setline(1, line) + + " yank + exec "norm! ^w\ey" + call assert_equal('2234567890', @@) + exec "norm! w\ey" + call assert_equal('3234567890', @@) + + " increment / decrement + exec "norm! ^w\\w\\" + call assert_equal('1234567890 3234567890 2234567890', getline(1)) + + " replace + exec "norm! ^w\3lraw\3lrb" + call assert_equal('1234567890 aaaa567890 bbbb567890', getline(1)) + + " tilde + exec "norm! ^w\2l~w\2l~" + call assert_equal('1234567890 AAAa567890 BBBb567890', getline(1)) + + " delete and insert + exec "norm! ^w\3lc2345\w\3lc3456\" + call assert_equal('1234567890 2345567890 3456567890', getline(1)) + call assert_equal('BBBb', @@) + + call s:close_windows() +endfunc + func Test_virtual_block() call s:test_windows('setl sbr=+') call setline(1, [ -- cgit From 9d826700f7f6556774b76592788e0cf5a1b44cd5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 24 Jan 2020 02:11:53 -0500 Subject: vim-patch:8.1.2245: third character of 'listchars' tab shows in wrong place Problem: Third character of 'listchars' tab shows in wrong place when 'breakindent' is set. Solution: Set c_final to NUL. (Naruhiko Nishino, closes vim/vim#5165) https://github.com/vim/vim/commit/2f7b7b1e123d505637d21e0df28eb9e92667479c --- src/nvim/screen.c | 5 ++--- src/nvim/testdir/test_breakindent.vim | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 3e59169918..ae38f657cd 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2976,6 +2976,7 @@ win_line ( } p_extra = NULL; c_extra = ' '; + c_final = NUL; n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false)); if (wp->w_skipcol > 0 && wp->w_p_wrap) { @@ -3293,9 +3294,7 @@ win_line ( } else { int c0; - if (p_extra_free != NULL) { - XFREE_CLEAR(p_extra_free); - } + XFREE_CLEAR(p_extra_free); // Get a character from the line itself. c0 = c = *ptr; diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 9717043976..6d88f1dc5a 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -297,6 +297,46 @@ function Test_breakindent16() call s:close_windows() endfunction +func Test_breakindent17_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent list listchars=tab:<-> showbreak=+++') + call setline(1, "\t" . repeat('a', 63)) + vert resize 30 + norm! 1gg$ + redraw! + let lines = s:screen_lines(1, 30) + let expect = [ + \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa", + \ " +++aaaaaaaaaaaaaaaaaaaaaaa", + \ " +++aaaaaaaaaaaaaa ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set breakindent& list& listchars& showbreak&') +endfunc + +func Test_breakindent18_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent list listchars=tab:<->') + call setline(1, "\t" . repeat('a', 63)) + vert resize 30 + norm! 1gg$ + redraw! + let lines = s:screen_lines(1, 30) + let expect = [ + \ "<-->aaaaaaaaaaaaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaaaaaaaaaaaaaaaaa", + \ " aaaaaaaaaaa ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set breakindent& list& listchars&') +endfunc + func Test_breakindent19_sbr_nextpage() let s:input = "" call s:test_windows('setl breakindent briopt=shift:2,sbr,min:18 sbr=>') -- cgit From 94ad6652f11e1d6791292d0a2bf2d777ddeefbcc Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Fri, 24 Jan 2020 23:40:47 -0500 Subject: Remove enc_utf8,has_mbyte dead code --- src/nvim/memline.c | 5 +- src/nvim/normal.c | 161 ++++++++++++++++++++--------------------------------- src/nvim/ops.c | 39 +++++-------- 3 files changed, 78 insertions(+), 127 deletions(-) (limited to 'src') diff --git a/src/nvim/memline.c b/src/nvim/memline.c index e5ba17a0a7..922b684120 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1802,9 +1802,10 @@ char_u *ml_get(linenr_T lnum) /* * Return pointer to position "pos". */ -char_u *ml_get_pos(pos_T *pos) +char_u *ml_get_pos(const pos_T *pos) + FUNC_ATTR_NONNULL_ALL { - return ml_get_buf(curbuf, pos->lnum, FALSE) + pos->col; + return ml_get_buf(curbuf, pos->lnum, false) + pos->col; } /* diff --git a/src/nvim/normal.c b/src/nvim/normal.c index be6819d19f..dac4c8f527 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -760,7 +760,7 @@ static void normal_get_additional_char(NormalState *s) // because if it's put back with vungetc() it's too late to apply // mapping. no_mapping--; - while (enc_utf8 && lang && (s->c = vpeekc()) > 0 + while (lang && (s->c = vpeekc()) > 0 && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) { s->c = plain_vgetc(); if (!utf_iscomposing(s->c)) { @@ -1711,13 +1711,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } } - /* Include the trailing byte of a multi-byte char. */ - if (has_mbyte && oap->inclusive) { - int l; - - l = (*mb_ptr2len)(ml_get_pos(&oap->end)); - if (l > 1) + // Include the trailing byte of a multi-byte char. + if (oap->inclusive) { + const int l = utfc_ptr2len(ml_get_pos(&oap->end)); + if (l > 1) { oap->end.col += l - 1; + } } curwin->w_set_curswant = true; @@ -2897,17 +2896,17 @@ static void find_end_of_word(pos_T *pos) */ static int get_mouse_class(char_u *p) { - int c; - - if (has_mbyte && MB_BYTE2LEN(p[0]) > 1) + if (MB_BYTE2LEN(p[0]) > 1) { return mb_get_class(p); + } - c = *p; - if (c == ' ' || c == '\t') + const int c = *p; + if (c == ' ' || c == '\t') { return 0; - - if (vim_iswordc(c)) + } + if (vim_iswordc(c)) { return 2; + } /* * There are a few special cases where we want certain combinations of @@ -4907,10 +4906,9 @@ static void nv_ident(cmdarg_T *cap) *p++ = '\\'; /* When current byte is a part of multibyte character, copy all * bytes of that character. */ - if (has_mbyte) { - size_t len = (size_t)((*mb_ptr2len)(ptr) - 1); - for (size_t i = 0; i < len && n > 0; ++i, --n) - *p++ = *ptr++; + const size_t len = (size_t)(utfc_ptr2len(ptr) - 1); + for (size_t i = 0; i < len && n > 0; i++, n--) { + *p++ = *ptr++; } *p++ = *ptr++; } @@ -4921,11 +4919,11 @@ static void nv_ident(cmdarg_T *cap) * Execute the command. */ if (cmdchar == '*' || cmdchar == '#') { - if (!g_cmd && ( - has_mbyte ? vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr)) : - vim_iswordc(ptr[-1]))) + if (!g_cmd + && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) { STRCAT(buf, "\\>"); - /* put pattern in search history */ + } + // put pattern in search history init_history(); add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0, @@ -4968,9 +4966,8 @@ get_visual_text ( *pp = ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } - if (has_mbyte) - /* Correct the length to include the whole last character. */ - *lenp += (size_t)((*mb_ptr2len)(*pp + (*lenp - 1)) - 1); + // Correct the length to include the whole last character. + *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); } reset_VIsual_and_resel(); return true; @@ -5188,11 +5185,7 @@ static void nv_left(cmdarg_T *cap) char_u *cp = get_cursor_pos_ptr(); if (*cp != NUL) { - if (has_mbyte) { - curwin->w_cursor.col += (*mb_ptr2len)(cp); - } else { - curwin->w_cursor.col++; - } + curwin->w_cursor.col += utfc_ptr2len(cp); } cap->retval |= CA_NO_ADJ_OP_END; } @@ -5850,7 +5843,6 @@ static void nv_replace(cmdarg_T *cap) { char_u *ptr; int had_ctrl_v; - long n; if (checkclearop(cap->oap)) return; @@ -5904,7 +5896,7 @@ static void nv_replace(cmdarg_T *cap) /* Abort if not enough characters to replace. */ ptr = get_cursor_pos_ptr(); if (STRLEN(ptr) < (unsigned)cap->count1 - || (has_mbyte && mb_charlen(ptr) < cap->count1) + || (mb_charlen(ptr) < cap->count1) ) { clearopbeep(cap->oap); return; @@ -5946,71 +5938,44 @@ static void nv_replace(cmdarg_T *cap) NUL, 'r', NUL, had_ctrl_v, cap->nchar); curbuf->b_op_start = curwin->w_cursor; - if (has_mbyte) { - int old_State = State; - - if (cap->ncharC1 != 0) - AppendCharToRedobuff(cap->ncharC1); - if (cap->ncharC2 != 0) - AppendCharToRedobuff(cap->ncharC2); - - /* This is slow, but it handles replacing a single-byte with a - * multi-byte and the other way around. Also handles adding - * composing characters for utf-8. */ - for (n = cap->count1; n > 0; --n) { - State = REPLACE; - if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) { - int c = ins_copychar(curwin->w_cursor.lnum - + (cap->nchar == Ctrl_Y ? -1 : 1)); - if (c != NUL) - ins_char(c); - else - /* will be decremented further down */ - ++curwin->w_cursor.col; - } else - ins_char(cap->nchar); - State = old_State; - if (cap->ncharC1 != 0) - ins_char(cap->ncharC1); - if (cap->ncharC2 != 0) - ins_char(cap->ncharC2); - } - } else { - /* - * Replace the characters within one line. - */ - for (n = cap->count1; n > 0; --n) { - /* - * Get ptr again, because u_save and/or showmatch() will have - * released the line. At the same time we let know that the - * line will be changed. - */ - ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true); - if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) { - int c = ins_copychar(curwin->w_cursor.lnum - + (cap->nchar == Ctrl_Y ? -1 : 1)); - if (c != NUL) { - assert(c >= 0 && c <= UCHAR_MAX); - ptr[curwin->w_cursor.col] = (char_u)c; - } + const int old_State = State; + + if (cap->ncharC1 != 0) { + AppendCharToRedobuff(cap->ncharC1); + } + if (cap->ncharC2 != 0) { + AppendCharToRedobuff(cap->ncharC2); + } + + // This is slow, but it handles replacing a single-byte with a + // multi-byte and the other way around. Also handles adding + // composing characters for utf-8. + for (long n = cap->count1; n > 0; n--) { + State = REPLACE; + if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) { + int c = ins_copychar(curwin->w_cursor.lnum + + (cap->nchar == Ctrl_Y ? -1 : 1)); + if (c != NUL) { + ins_char(c); } else { - assert(cap->nchar >= 0 && cap->nchar <= UCHAR_MAX); - ptr[curwin->w_cursor.col] = (char_u)cap->nchar; + // will be decremented further down + curwin->w_cursor.col++; } - if (p_sm && msg_silent == 0) - showmatch(cap->nchar); - ++curwin->w_cursor.col; + } else { + ins_char(cap->nchar); + } + State = old_State; + if (cap->ncharC1 != 0) { + ins_char(cap->ncharC1); + } + if (cap->ncharC2 != 0) { + ins_char(cap->ncharC2); } - - /* mark the buffer as changed and prepare for displaying */ - changed_bytes(curwin->w_cursor.lnum, - (colnr_T)(curwin->w_cursor.col - cap->count1)); } --curwin->w_cursor.col; /* cursor on the last replaced char */ /* if the character on the left of the current cursor is a multi-byte * character, move two characters left */ - if (has_mbyte) - mb_adjust_cursor(); + mb_adjust_cursor(); curbuf->b_op_end = curwin->w_cursor; curwin->w_set_curswant = true; set_last_insert(cap->nchar); @@ -7356,10 +7321,9 @@ static void adjust_cursor(oparg_T *oap) && (!VIsual_active || *p_sel == 'o') && !virtual_active() && (ve_flags & VE_ONEMORE) == 0 ) { - --curwin->w_cursor.col; - /* prevent cursor from moving on the trail byte */ - if (has_mbyte) - mb_adjust_cursor(); + curwin->w_cursor.col--; + // prevent cursor from moving on the trail byte + mb_adjust_cursor(); oap->inclusive = true; } } @@ -7386,10 +7350,7 @@ static void adjust_for_sel(cmdarg_T *cap) { if (VIsual_active && cap->oap->inclusive && *p_sel == 'e' && gchar_cursor() != NUL && lt(VIsual, curwin->w_cursor)) { - if (has_mbyte) - inc_cursor(); - else - ++curwin->w_cursor.col; + inc_cursor(); cap->oap->inclusive = false; } } @@ -7979,9 +7940,7 @@ static void get_op_vcol( oap->motion_type = kMTBlockWise; // prevent from moving onto a trail byte - if (has_mbyte) { - mark_mb_adjustpos(curwin->w_buffer, &oap->end); - } + mark_mb_adjustpos(curwin->w_buffer, &oap->end); getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol); if (!redo_VIsual_busy) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index bcf54087f5..db5c98ed78 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -357,15 +357,11 @@ static void shift_block(oparg_T *oap, int amount) colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp; char_u * old_textstart = bd.textstart; if (bd.startspaces) { - if (has_mbyte) { - if ((*mb_ptr2len)(bd.textstart) == 1) { - bd.textstart++; - } else { - ws_vcol = 0; - bd.startspaces = 0; - } - } else { + if (utfc_ptr2len(bd.textstart) == 1) { bd.textstart++; + } else { + ws_vcol = 0; + bd.startspaces = 0; } } for (; ascii_iswhite(*bd.textstart); ) { @@ -1215,9 +1211,7 @@ static void stuffescaped(const char *arg, int literally) /* stuff a single special character */ if (*arg != NUL) { - const int c = (has_mbyte - ? mb_cptr2char_adv((const char_u **)&arg) - : (uint8_t)(*arg++)); + const int c = mb_cptr2char_adv((const char_u **)&arg); if (literally && ((c < ' ' && c != TAB) || c == DEL)) { stuffcharReadbuff(Ctrl_V); } @@ -1389,8 +1383,7 @@ int op_delete(oparg_T *oap) return FAIL; } - if (has_mbyte) - mb_adjust_opend(oap); + mb_adjust_opend(oap); /* * Imitate the strange Vi behaviour: If the delete spans more than one @@ -1736,8 +1729,7 @@ int op_replace(oparg_T *oap, int c) c = NL; } - if (has_mbyte) - mb_adjust_opend(oap); + mb_adjust_opend(oap); if (u_save((linenr_T)(oap->start.lnum - 1), (linenr_T)(oap->end.lnum + 1)) == FAIL) @@ -2012,17 +2004,16 @@ void op_tilde(oparg_T *oap) * Returns TRUE if some character was changed. */ static int swapchars(int op_type, pos_T *pos, int length) + FUNC_ATTR_NONNULL_ALL { - int todo; int did_change = 0; - for (todo = length; todo > 0; --todo) { - if (has_mbyte) { - int len = (*mb_ptr2len)(ml_get_pos(pos)); + for (int todo = length; todo > 0; todo--) { + const int len = utfc_ptr2len(ml_get_pos(pos)); - /* we're counting bytes, not characters */ - if (len > 0) - todo -= len - 1; + // we're counting bytes, not characters + if (len > 0) { + todo -= len - 1; } did_change |= swapchar(op_type, pos); if (inc(pos) == -1) /* at end of file */ @@ -3052,7 +3043,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); // move to start of next multi-byte character - curwin->w_cursor.col += (*mb_ptr2len)(get_cursor_pos_ptr()); + curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); col++; } else { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2); @@ -3615,7 +3606,7 @@ dis_msg( while (*p != NUL && !(*p == ESC && skip_esc && *(p + 1) == NUL) && (n -= ptr2cells(p)) >= 0) { - if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) { + if ((l = utfc_ptr2len(p)) > 1) { msg_outtrans_len(p, l); p += l; } else -- cgit From cbcb50dcc958131b95cb12846dc567dc4fd32b8b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 25 Jan 2020 16:49:53 -0800 Subject: shell: "..." instead of "[...]" #11760 0c1be45ea0b7 changed pulse logic to output "[...]" instead of nothing. But that doesn't align with the "..." pulse which may follow it. ref #11130 --- src/nvim/os/shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index f4377b1457..d1de18d5b3 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -423,7 +423,7 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick > 1) ? '.' : ' '; pulse_msg[2] = (tick > 2) ? '.' : ' '; if (visit == 1) { - msg_puts("[...]\n"); + msg_puts("...\n"); } msg_putchar('\r'); // put cursor at start of line msg_puts(pulse_msg); -- cgit From 451af7f08779ba39d3ebef4b2295ba702cc4f3d7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 26 Jan 2020 01:05:04 -0500 Subject: vim-patch:8.1.2171: mouse support not always available #11761 Problem: Mouse support not always available. Solution: Enable mouse support also in tiny version. Do not define FEAT_MOUSE_XTERM on MS-Windows (didn't really work). https://github.com/vim/vim/commit/a1cb1d1dce14dd005797590721f1bcd0e7c3b35f --- src/nvim/move.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/move.c b/src/nvim/move.c index efbc548620..3ae4f32a83 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1551,15 +1551,14 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) /* Stop when scrolled nothing or at least "min_scroll", found "extra" * context for 'scrolloff' and counted all lines below the window. */ if ((((scrolled <= 0 || scrolled >= min_scroll) - && extra >= ( - mouse_dragging > 0 ? mouse_dragging - 1 : - p_so)) + && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : p_so)) || boff.lnum + 1 > curbuf->b_ml.ml_line_count) && loff.lnum <= curwin->w_botline && (loff.lnum < curwin->w_botline || loff.fill >= fill_below_window) - ) + ) { break; + } /* Add one line above */ topline_back(&loff); @@ -1590,9 +1589,8 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) if (used > curwin->w_height_inner) { break; } - if (extra < ( - mouse_dragging > 0 ? mouse_dragging - 1 : - p_so) || scrolled < min_scroll) { + if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : p_so) + || scrolled < min_scroll) { extra += boff.height; if (boff.lnum >= curwin->w_botline || (boff.lnum + 1 == curwin->w_botline @@ -1742,8 +1740,7 @@ void cursor_correct(void) } validate_botline(); if (curwin->w_botline == curbuf->b_ml.ml_line_count + 1 - && mouse_dragging == 0 - ) { + && mouse_dragging == 0) { below_wanted = 0; int max_off = (curwin->w_height_inner - 1) / 2; if (above_wanted > max_off) { -- cgit From c6ff23d7a0d5ccf0d8995e3204c18df55d28fc7f Mon Sep 17 00:00:00 2001 From: Chris LaRose Date: Sun, 26 Jan 2020 00:24:42 -0800 Subject: terminal: absolute CWD in term:// URI #11289 This makes it possible to restore the working directory of :terminal buffers when reading those buffers from a session file. Fixes #11288 Co-authored-by: Justin M. Keyes --- src/nvim/eval.c | 12 ++++++++---- src/nvim/main.c | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9379d94fa3..ce1d55afab 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18383,13 +18383,17 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) int pid = chan->stream.pty.process.pid; - char buf[1024]; - // format the title with the pid to conform with the term:// URI - snprintf(buf, sizeof(buf), "term://%s//%d:%s", cwd, pid, cmd); + // "./…" => "/home/foo/…" + vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); + // "/home/foo/…" => "~/…" + home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); + // Terminal URI: "term://$CWD//$PID:$CMD" + snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", + (char *)IObuff, pid, cmd); // at this point the buffer has no terminal instance associated yet, so unset // the 'swapfile' option to ensure no swap file will be created curbuf->b_p_swf = false; - (void)setfname(curbuf, (char_u *)buf, NULL, true); + (void)setfname(curbuf, NameBuff, NULL, true); // Save the job id and pid in b:terminal_job_{id,pid} Error err = ERROR_INIT; // deprecated: use 'channel' buffer option diff --git a/src/nvim/main.c b/src/nvim/main.c index be279b449a..a816221a9e 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -339,8 +339,8 @@ int main(int argc, char **argv) "matchstr(expand(\"\"), " "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " // capture the working directory - "{'cwd': get(matchlist(expand(\"\"), " - "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, '')})" + "{'cwd': expand(get(matchlist(expand(\"\"), " + "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})" "|endif"); do_cmdline_cmd("augroup END"); #undef PROTO -- cgit From 07a105f0cb6e2827e621ea31c52cd7714a44a418 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 02:13:37 -0800 Subject: terminal: trim CWD slash #11762 Trailing CWD slash in term:// buffer name breaks the BufReadCmd handler. Before: term://~///25232:/bin/bash After: term://~//25232:/bin/bash ref c6ff23d7a0d5 ref #11289 --- src/nvim/eval.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ce1d55afab..55b18d5f4f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18386,7 +18386,12 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) // "./…" => "/home/foo/…" vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); // "/home/foo/…" => "~/…" - home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); + size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); + // Trim slash. + if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') { + IObuff[len - 1] = '\0'; + } + // Terminal URI: "term://$CWD//$PID:$CMD" snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", (char *)IObuff, pid, cmd); -- cgit From 687fc527de5262133991c5721c349328c9a58d3d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 24 Jan 2020 10:52:17 +0100 Subject: screen: add missing redraws after a message --- src/nvim/screen.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a451451726..97893223a4 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -370,6 +370,17 @@ int update_screen(int type) grid_clear_line(&default_grid, default_grid.line_offset[i], Columns, false); } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } + if (W_ENDROW(wp) > valid) { + wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); + } + if (W_ENDROW(wp) + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } } msg_grid_set_pos(Rows-p_ch, false); msg_grid_invalid = false; -- cgit From 6c7a995c5f585c507ac9aaced3ee859dbd8adab6 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 25 Jan 2020 00:50:56 -0500 Subject: spell: zero-init structs to fix garbage ptrs --- src/nvim/spell.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/spell.c b/src/nvim/spell.c index c75a53a777..48c3d42baa 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -848,7 +848,7 @@ static void find_word(matchinf_T *mip, int mode) mip->mi_compflags[mip->mi_complen] = ((unsigned)flags >> 24); mip->mi_compflags[mip->mi_complen + 1] = NUL; if (word_ends) { - char_u fword[MAXWLEN]; + char_u fword[MAXWLEN] = { 0 }; if (slang->sl_compsylmax < MAXWLEN) { // "fword" is only needed for checking syllables. @@ -3603,7 +3603,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so { char_u tword[MAXWLEN]; // good word collected so far trystate_T stack[MAXWLEN]; - char_u preword[MAXWLEN * 3]; // word found with proper case; + char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; // concatenation of prefix compound // words and split word. NUL terminated // when going deeper but not when coming -- cgit From 86c1630a1b63d5f5e0eac9235459ec8da10b47ba Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 25 Jan 2020 01:00:20 -0500 Subject: spell: remove enc_utf8 dead code --- src/nvim/spell.c | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 48c3d42baa..5559556489 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1026,26 +1026,25 @@ match_checkcompoundpattern ( // Returns true if "flags" is a valid sequence of compound flags and "word" // does not have too many syllables. -static bool can_compound(slang_T *slang, char_u *word, char_u *flags) +static bool can_compound(slang_T *slang, const char_u *word, + const char_u *flags) + FUNC_ATTR_NONNULL_ALL { - char_u uflags[MAXWLEN * 2]; - int i; - char_u *p; + char_u uflags[MAXWLEN * 2] = { 0 }; - if (slang->sl_compprog == NULL) + if (slang->sl_compprog == NULL) { return false; - if (enc_utf8) { - // Need to convert the single byte flags to utf8 characters. - p = uflags; - for (i = 0; flags[i] != NUL; i++) { - p += utf_char2bytes(flags[i], p); - } - *p = NUL; - p = uflags; - } else - p = flags; - if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0)) + } + // Need to convert the single byte flags to utf8 characters. + char_u *p = uflags; + for (int i = 0; flags[i] != NUL; i++) { + p += utf_char2bytes(flags[i], p); + } + *p = NUL; + p = uflags; + if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0)) { return false; + } // Count the number of syllables. This may be slow, do it last. If there // are too many syllables AND the number of compound words is above @@ -4268,9 +4267,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // For changing a composing character adjust // the score from SCORE_SUBST to // SCORE_SUBCOMP. - if (enc_utf8 - && utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen - - sp->ts_tcharlen)) + if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen + - sp->ts_tcharlen)) && utf_iscomposing(utf_ptr2char(fword + sp->ts_fcharstart))) { sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; @@ -5855,7 +5853,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // 255, sl_sal the rest. for (s = inword; *s != NUL; ) { c = mb_cptr2char_adv((const char_u **)&s); - if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) { + if (utf_class(c) == 0) { c = ' '; } else if (c < 256) { c = slang->sl_sal_first[c]; @@ -5932,9 +5930,10 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) const char_u *t = s; c = mb_cptr2char_adv((const char_u **)&s); if (slang->sl_rem_accents) { - if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) { - if (did_white) + if (utf_class(c) == 0) { + if (did_white) { continue; + } c = ' '; did_white = true; } else { -- cgit From eab052eca2c255ac4410a00f444e076cf5b43042 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 25 Jan 2020 14:19:10 -0500 Subject: spell_defs: remove enc_utf8 redundant checks --- src/nvim/spell_defs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index e83b21b219..7bc5296239 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -265,15 +265,15 @@ typedef struct trystate_S { // Multi-byte implementation. For Unicode we can call utf_*(), but don't do // that for ASCII, because we don't want to use 'casemap' here. Otherwise use // the "w" library function for characters above 255. -#define SPELL_TOFOLD(c) (enc_utf8 && (c) >= 128 ? utf_fold(c) \ +#define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) \ : (c) < \ 256 ? (int)spelltab.st_fold[c] : (int)towlower(c)) -#define SPELL_TOUPPER(c) (enc_utf8 && (c) >= 128 ? mb_toupper(c) \ +#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \ : (c) < \ 256 ? (int)spelltab.st_upper[c] : (int)towupper(c)) -#define SPELL_ISUPPER(c) (enc_utf8 && (c) >= 128 ? mb_isupper(c) \ +#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) \ : (c) < 256 ? spelltab.st_isu[c] : iswupper(c)) // First language that is loaded, start of the linked list of loaded -- cgit From d5322c838141db93a136a58e6edb3a8304526934 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 25 Jan 2020 14:21:53 -0500 Subject: spellfile: set_spell_chartab() is dead code --- src/nvim/spellfile.c | 78 +--------------------------------------------------- 1 file changed, 1 insertion(+), 77 deletions(-) (limited to 'src') diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 14abfda9ff..f8c10d0258 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -304,9 +304,6 @@ static char *e_spell_trunc = N_("E758: Truncated spell file"); static char *e_afftrailing = N_("Trailing text in %s line %d: %s"); static char *e_affname = N_("Affix name too long in %s line %d: %s"); -static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP"); -static char *e_affrange = N_( - "E762: Character in FOL, LOW or UPP is out of range"); static char *msg_compressing = N_("Compressing word tree..."); #define MAXLINELEN 500 // Maximum length in bytes of a line in a .aff @@ -1386,8 +1383,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) // Inserting backslashes may double the length, "^\(\)$" is 7 bytes. // Conversion to utf-8 may double the size. c = todo * 2 + 7; - if (enc_utf8) - c += todo * 2; + c += todo * 2; pat = xmalloc(c); // We also need a list of all flags that can appear at the start and one @@ -2624,19 +2620,6 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) spin->si_clear_chartab = false; } - // Don't write a word table for an ASCII file, so that we don't check - // for conflicts with a word table that matches 'encoding'. - // Don't write one for utf-8 either, we use utf_*() and - // mb_get_class(), the list of chars in the file will be incomplete. - if (!spin->si_ascii - && !enc_utf8 - ) { - if (fol == NULL || low == NULL || upp == NULL) - smsg(_("Missing FOL/LOW/UPP line in %s"), fname); - else - (void)set_spell_chartab(fol, low, upp); - } - xfree(fol); xfree(low); xfree(upp); @@ -5533,65 +5516,6 @@ static void init_spellfile(void) } } -// Set the spell character tables from strings in the affix file. -static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp) -{ - // We build the new tables here first, so that we can compare with the - // previous one. - spelltab_T new_st; - char_u *pf = fol, *pl = low, *pu = upp; - int f, l, u; - - clear_spell_chartab(&new_st); - - while (*pf != NUL) { - if (*pl == NUL || *pu == NUL) { - EMSG(_(e_affform)); - return FAIL; - } - f = mb_ptr2char_adv((const char_u **)&pf); - l = mb_ptr2char_adv((const char_u **)&pl); - u = mb_ptr2char_adv((const char_u **)&pu); - // Every character that appears is a word character. - if (f < 256) - new_st.st_isw[f] = true; - if (l < 256) - new_st.st_isw[l] = true; - if (u < 256) - new_st.st_isw[u] = true; - - // if "LOW" and "FOL" are not the same the "LOW" char needs - // case-folding - if (l < 256 && l != f) { - if (f >= 256) { - EMSG(_(e_affrange)); - return FAIL; - } - new_st.st_fold[l] = f; - } - - // if "UPP" and "FOL" are not the same the "UPP" char needs - // case-folding, it's upper case and the "UPP" is the upper case of - // "FOL" . - if (u < 256 && u != f) { - if (f >= 256) { - EMSG(_(e_affrange)); - return FAIL; - } - new_st.st_fold[u] = f; - new_st.st_isu[u] = true; - new_st.st_upper[f] = u; - } - } - - if (*pl != NUL || *pu != NUL) { - EMSG(_(e_affform)); - return FAIL; - } - - return set_spell_finish(&new_st); -} - // Set the spell character tables from strings in the .spl file. static void set_spell_charflags ( -- cgit From 08c5a874ab97d52e215025ccd010d68fcdf14731 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 26 Jan 2020 08:17:08 -0500 Subject: vim-patch:8.1.1143: may pass weird strings to file name expansion Problem: May pass weird strings to file name expansion. Solution: Check for matching characters. Disallow control characters. https://github.com/vim/vim/commit/8f130eda4747e4a4d68353cdb650f359fd01469b --- src/nvim/option.c | 33 +++++++++++++++++++++++++++------ src/nvim/path.c | 16 ++++++++++++++-- src/nvim/spell.c | 4 ++++ src/nvim/testdir/test_escaped_glob.vim | 2 +- src/nvim/testdir/test_spell.vim | 6 ++++++ 5 files changed, 52 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index f03dcc2bf2..9168509aa8 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2509,18 +2509,35 @@ static char *set_string_option(const int opt_idx, const char *const value, return r; } -/// Return true if "val" is a valid 'filetype' name. -/// Also used for 'syntax' and 'keymap'. -static bool valid_filetype(char_u *val) +/// Return true if "val" is a valid name: only consists of alphanumeric ASCII +/// characters or characters in "allowed". +static bool valid_name(const char_u *val, const char *allowed) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (char_u *s = val; *s != NUL; s++) { - if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) { + for (const char_u *s = val; *s != NUL; s++) { + if (!ASCII_ISALNUM(*s) + && vim_strchr((const char_u *)allowed, *s) == NULL) { return false; } } return true; } +/// Return true if "val" is a valid 'filetype' name. +/// Also used for 'syntax' and 'keymap'. +static bool valid_filetype(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_"); +} + +/// Return true if "val" is a valid 'spellang' value. +bool valid_spellang(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return valid_name(val, ".-_,"); +} + /// Handle string options that need some action to perform when changed. /// Returns NULL for success, or an error message for an error. static char_u * @@ -3032,7 +3049,11 @@ ambw_end: || varp == &(curwin->w_s->b_p_spf)) { // When 'spelllang' or 'spellfile' is set and there is a window for this // buffer in which 'spell' is set load the wordlists. - errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf)); + if (!valid_spellang(*varp)) { + errmsg = e_invarg; + } else { + errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf)); + } } else if (varp == &(curwin->w_s->b_p_spc)) { // When 'spellcapcheck' is set compile the regexp program. errmsg = compile_cap_prog(curwin->w_s); diff --git a/src/nvim/path.c b/src/nvim/path.c index a53870acb8..0142724a5b 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1120,10 +1120,22 @@ static bool has_env_var(char_u *p) static bool has_special_wildchar(char_u *p) { for (; *p; MB_PTR_ADV(p)) { - // Allow for escaping - if (*p == '\\' && p[1] != NUL) { + // Disallow line break characters. + if (*p == '\r' || *p == '\n') { + break; + } + // Allow for escaping. + if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') { p++; } else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) { + // A { must be followed by a matching }. + if (*p == '{' && vim_strchr(p, '}') == NULL) { + continue; + } + // A quote and backtick must be followed by another one. + if ((*p == '`' || *p == '\'') && vim_strchr(p, *p) == NULL) { + continue; + } return true; } } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index c75a53a777..a221f3fd75 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2008,6 +2008,10 @@ char_u *did_set_spelllang(win_T *wp) region = NULL; len = (int)STRLEN(lang); + if (!valid_spellang(lang)) { + continue; + } + if (STRCMP(lang, "cjk") == 0) { wp->w_s->b_cjk = 1; continue; diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim index aad3a1e835..2bfd82c296 100644 --- a/src/nvim/testdir/test_escaped_glob.vim +++ b/src/nvim/testdir/test_escaped_glob.vim @@ -17,7 +17,7 @@ function Test_glob() " Setting 'shell' to an invalid name causes a memory leak. sandbox call assert_equal("", glob('Xxx\{')) sandbox call assert_equal("", glob('Xxx\$')) - w! Xxx{ + w! Xxx\{ " } to fix highlighting w! Xxx\$ sandbox call assert_equal("Xxx{", glob('Xxx\{')) diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e2016d7927..098cd957ae 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -151,6 +151,12 @@ func Test_spellinfo() set nospell spelllang=en call assert_fails('spellinfo', 'E756:') + call assert_fails('set spelllang=foo/bar', 'E474:') + call assert_fails('set spelllang=foo\ bar', 'E474:') + call assert_fails("set spelllang=foo\\\nbar", 'E474:') + call assert_fails("set spelllang=foo\\\rbar", 'E474:') + call assert_fails("set spelllang=foo+bar", 'E474:') + set enc& spell& spelllang& bwipe endfunc -- cgit From ad272cd2d7ac6c9d28c2981169b779e146cbb5d6 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 26 Jan 2020 10:21:28 -0500 Subject: vim-patch:8.1.1144: too strict checking of the 'spellfile' option Problem: Too strict checking of the 'spellfile' option. Solution: Allow for a path. https://github.com/vim/vim/commit/862f1e17eaf2b9c6617dfba31d8487cde462658d --- src/nvim/option.c | 19 +++++++++++++++++-- src/nvim/testdir/test_spell.vim | 5 +++++ 2 files changed, 22 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index 9168509aa8..52742c8b64 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2538,6 +2538,18 @@ bool valid_spellang(const char_u *val) return valid_name(val, ".-_,"); } +/// Return true if "val" is a valid 'spellfile' value. +static bool valid_spellfile(const char_u *val) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + for (const char_u *s = val; *s != NUL; s++) { + if (!vim_isfilec(*s) && *s != ',') { + return false; + } + } + return true; +} + /// Handle string options that need some action to perform when changed. /// Returns NULL for success, or an error message for an error. static char_u * @@ -3049,10 +3061,13 @@ ambw_end: || varp == &(curwin->w_s->b_p_spf)) { // When 'spelllang' or 'spellfile' is set and there is a window for this // buffer in which 'spell' is set load the wordlists. - if (!valid_spellang(*varp)) { + const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); + + if ((is_spellfile && !valid_spellfile(*varp)) + || (!is_spellfile && !valid_spellang(*varp))) { errmsg = e_invarg; } else { - errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf)); + errmsg = did_set_spell_option(is_spellfile); } } else if (varp == &(curwin->w_s->b_p_spc)) { // When 'spellcapcheck' is set compile the regexp program. diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 098cd957ae..94789d6ea3 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -392,6 +392,11 @@ func Test_zz_sal_and_addition() call assert_equal("elekwint", SecondSpellWord()) endfunc +func Test_spellfile_value() + set spellfile=Xdir/Xtest.latin1.add + set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add +endfunc + func Test_region_error() messages clear call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add") -- cgit From 0e1dd0a502c4a1b83dd5c338566fc097958f6eb5 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 26 Jan 2020 10:42:08 -0500 Subject: spell: towupper(),towlower() are not called --- src/nvim/charset.c | 1 - src/nvim/spell_defs.h | 11 +++-------- 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/charset.c b/src/nvim/charset.c index e9140f8ec5..0f9e2e23c0 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -8,7 +8,6 @@ #include #include #include -#include // for towupper() and towlower() #include #include "nvim/vim.h" diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 7bc5296239..034c580b3e 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -261,20 +261,15 @@ typedef struct trystate_S { // Use our own character-case definitions, because the current locale may // differ from what the .spl file uses. // These must not be called with negative number! -#include // for towupper() and towlower() // Multi-byte implementation. For Unicode we can call utf_*(), but don't do // that for ASCII, because we don't want to use 'casemap' here. Otherwise use // the "w" library function for characters above 255. -#define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) \ - : (c) < \ - 256 ? (int)spelltab.st_fold[c] : (int)towlower(c)) +#define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) : (int)spelltab.st_fold[c]) #define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \ - : (c) < \ - 256 ? (int)spelltab.st_upper[c] : (int)towupper(c)) + : (int)spelltab.st_upper[c]) -#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) \ - : (c) < 256 ? spelltab.st_isu[c] : iswupper(c)) +#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c]) // First language that is loaded, start of the linked list of loaded // languages. -- cgit From cf67f19ac2104ece76d040c8184bc287428299b3 Mon Sep 17 00:00:00 2001 From: Alexandre Dubray Date: Tue, 9 Jan 2018 12:01:02 +0100 Subject: mksession: restore same :term buf in split windows Problem: When session-restore creates a terminal buffer with command like `:edit term://.//16450:/bin/bash`, the buffer gets a different name (depends on PID). Thus the later call to `bufexists('term://.//16450:/bin/bash)` will return false. Solution: Force the buffer name with :file. This as least ensures the same buffer will show in multiple windows correctly, as expected when saving the session. But it still has problems: 1. the PID in the buffer name is bogus 2. redundant :terminal buffers still hang around fix #5250 --- src/nvim/ex_docmd.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 02bee838d5..0732409666 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9346,6 +9346,10 @@ makeopens( /* * Restore the view of the window (options, file, cursor, etc.). */ + if (put_line(fd, "let s:buffer_names = []") == FAIL) { + return FAIL; + } + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { if (!ses_do_win(wp)) continue; @@ -9357,6 +9361,16 @@ makeopens( next_arg_idx = wp->w_arg_idx; } + if (put_line(fd, "for [s:name, s:tbuf] in s:buffer_names") == FAIL + || put_line(fd, " if buflisted(s:tbuf)") == FAIL + || put_line(fd, " execute 'buffer' fnameescape(s:tbuf)") == FAIL + || put_line(fd, " execute 'file' fnameescape(s:name)") == FAIL + || put_line(fd, " endif") == FAIL + || put_line(fd, "endfor") == FAIL + || put_line(fd, "unlet! s:buffer_names s:tbuf s:name") == FAIL) { + return FAIL; + } + /* The argument index in the first tab page is zero, need to set it in * each window. For further tab pages it's the window where we do * "tabedit". */ @@ -9662,6 +9676,19 @@ put_view( || put_eol(fd) == FAIL) { return FAIL; } + + if (fputs("call add(s:buffer_names, [bufname('%'),'", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL + || fputs("'])", fd) < 0 + || put_eol(fd) == FAIL) { + return FAIL; + } + + if (fputs("file ", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL + || put_eol(fd) == FAIL) { + return FAIL; + } } else { // No file in this buffer, just make it empty. if (put_line(fd, "enew") == FAIL) { -- cgit From 1e103b3c12597a9dd2f20d45686822ab6ee089b0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 02:43:12 -0800 Subject: mksession: simplify generated commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doing ":file …" immediately after is enough to fixup the :terminal buffer name. ref #5250 --- src/nvim/ex_docmd.c | 78 ++++++++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0732409666..5253233c15 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8090,14 +8090,12 @@ static void close_redir(void) static int mksession_nl = FALSE; /* use NL only in put_eol() */ #endif -/* - * ":mkexrc", ":mkvimrc", ":mkview" and ":mksession". - */ +/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". static void ex_mkrc(exarg_T *eap) { FILE *fd; int failed = false; - int view_session = false; + int view_session = false; // :mkview, :mksession int using_vdir = false; // using 'viewdir'? char *viewFile = NULL; unsigned *flagp; @@ -8159,11 +8157,11 @@ static void ex_mkrc(exarg_T *eap) failed = TRUE; } - if (!view_session - || (eap->cmdidx == CMD_mksession - && (*flagp & SSOP_OPTIONS))) + if (!view_session || (eap->cmdidx == CMD_mksession + && (*flagp & SSOP_OPTIONS))) { failed |= (makemap(fd, NULL) == FAIL || makeset(fd, OPT_GLOBAL, FALSE) == FAIL); + } if (!failed && view_session) { if (put_line(fd, @@ -9119,15 +9117,13 @@ char_u *expand_sfile(char_u *arg) } -/* - * Write openfile commands for the current buffers to an .exrc file. - * Return FAIL on error, OK otherwise. - */ -static int -makeopens( - FILE *fd, - char_u *dirnow /* Current directory name */ -) +/// Writes commands for restoring the current buffers, for :mksession. +/// +/// @param dirnow Current directory name +/// @param fd File descriptor to write to +/// +/// @return FAIL on error, OK otherwise. +static int makeopens(FILE *fd, char_u *dirnow) { int only_save_windows = TRUE; int nr; @@ -9240,12 +9236,11 @@ makeopens( restore_stal = TRUE; } - /* - * May repeat putting Windows for each tab, when "tabpages" is in - * 'sessionoptions'. - * Don't use goto_tabpage(), it may change directory and trigger - * autocommands. - */ + // + // For each tab: + // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. + // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. + // tab_firstwin = firstwin; /* first window in tab page "tabnr" */ tab_topframe = topframe; for (tabnr = 1;; tabnr++) { @@ -9269,11 +9264,11 @@ makeopens( need_tabnew = TRUE; } - /* - * Before creating the window layout, try loading one file. If this - * is aborted we don't end up with a number of useless windows. - * This may have side effects! (e.g., compressed or network file). - */ + // + // Before creating the window layout, try loading one file. If this + // is aborted we don't end up with a number of useless windows. + // This may have side effects! (e.g., compressed or network file). + // for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { if (ses_do_win(wp) && wp->w_buffer->b_ffname != NULL @@ -9343,13 +9338,9 @@ makeopens( return FAIL; } - /* - * Restore the view of the window (options, file, cursor, etc.). - */ - if (put_line(fd, "let s:buffer_names = []") == FAIL) { - return FAIL; - } - + // + // Restore the view of the window (options, file, cursor, etc.). + // for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { if (!ses_do_win(wp)) continue; @@ -9361,16 +9352,6 @@ makeopens( next_arg_idx = wp->w_arg_idx; } - if (put_line(fd, "for [s:name, s:tbuf] in s:buffer_names") == FAIL - || put_line(fd, " if buflisted(s:tbuf)") == FAIL - || put_line(fd, " execute 'buffer' fnameescape(s:tbuf)") == FAIL - || put_line(fd, " execute 'file' fnameescape(s:name)") == FAIL - || put_line(fd, " endif") == FAIL - || put_line(fd, "endfor") == FAIL - || put_line(fd, "unlet! s:buffer_names s:tbuf s:name") == FAIL) { - return FAIL; - } - /* The argument index in the first tab page is zero, need to set it in * each window. For further tab pages it's the window where we do * "tabedit". */ @@ -9677,14 +9658,7 @@ put_view( return FAIL; } - if (fputs("call add(s:buffer_names, [bufname('%'),'", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs("'])", fd) < 0 - || put_eol(fd) == FAIL) { - return FAIL; - } - - if (fputs("file ", fd) < 0 + if (fputs("silent file ", fd) < 0 || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL || put_eol(fd) == FAIL) { return FAIL; -- cgit From 598a1cd7c5b9b0f4e4b175f55b58bdb0e1a398eb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 03:09:18 -0800 Subject: mksession: avoid ":file …" when restoring non-terminal bufs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/nvim/ex_docmd.c | 84 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 5253233c15..6791bbd34c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8160,7 +8160,7 @@ static void ex_mkrc(exarg_T *eap) if (!view_session || (eap->cmdidx == CMD_mksession && (*flagp & SSOP_OPTIONS))) { failed |= (makemap(fd, NULL) == FAIL - || makeset(fd, OPT_GLOBAL, FALSE) == FAIL); + || makeset(fd, OPT_GLOBAL, false) == FAIL); } if (!failed && view_session) { @@ -9241,7 +9241,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. // - tab_firstwin = firstwin; /* first window in tab page "tabnr" */ + tab_firstwin = firstwin; // First window in tab page "tabnr". tab_topframe = topframe; for (tabnr = 1;; tabnr++) { tabpage_T *tp = find_tabpage(tabnr); @@ -9658,11 +9658,18 @@ put_view( return FAIL; } - if (fputs("silent file ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || put_eol(fd) == FAIL) { + char *fname_esc = + ses_escape_fname(ses_get_fname(wp->w_buffer, flagp, false), flagp); + // Fixup :terminal buffer name. + if (fprintf(fd, + "if &buftype ==# 'terminal'\n" + " silent file %s\n" + "endif\n", + fname_esc) < 0) { + xfree(fname_esc); return FAIL; } + xfree(fname_esc); } else { // No file in this buffer, just make it empty. if (put_line(fd, "enew") == FAIL) { @@ -9828,44 +9835,45 @@ ses_arglist( return OK; } -/// Write a buffer name to the session file. -/// Also ends the line, if "add_eol" is true. -/// Returns FAIL if writing fails. -static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) +/// Gets the buffer name for `buf`. +static char *ses_get_fname(buf_T *buf, unsigned *flagp, bool add_eol) { - char_u *name; - - /* Use the short file name if the current directory is known at the time - * the session file will be sourced. - * Don't do this for ":mkview", we don't know the current directory. - * Don't do this after ":lcd", we don't keep track of what the current - * directory is. */ + // Use the short file name if the current directory is known at the time + // the session file will be sourced. + // Don't do this for ":mkview", we don't know the current directory. + // Don't do this after ":lcd", we don't keep track of what the current + // directory is. if (buf->b_sfname != NULL && flagp == &ssop_flags && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) && !p_acd - && !did_lcd) - name = buf->b_sfname; - else - name = buf->b_ffname; - if (ses_put_fname(fd, name, flagp) == FAIL + && !did_lcd) { + return (char *)buf->b_sfname; + } + return (char *)buf->b_ffname; +} +/// Write a buffer name to the session file. +/// Also ends the line, if "add_eol" is true. +/// Returns FAIL if writing fails. +static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) +{ + char *name = ses_get_fname(buf, flagp, add_eol); + if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL || (add_eol && put_eol(fd) == FAIL)) { return FAIL; } return OK; } -/* - * Write a file name to the session file. - * Takes care of the "slash" option in 'sessionoptions' and escapes special - * characters. - * Returns FAIL if writing fails. - */ -static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +// Escapes a filename for session writing. +// Takes care of "slash" flag in 'sessionoptions' and escapes special +// characters. +// +// Returns allocated string or NULL. +static char *ses_escape_fname(char *name, unsigned *flagp) { - char_u *p; - - char_u *sname = home_replace_save(NULL, name); + char *p; + char *sname = (char *)home_replace_save(NULL, (char_u *)name); if (*flagp & SSOP_SLASH) { // change all backslashes to forward slashes @@ -9877,11 +9885,19 @@ static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) } // Escape special characters. - p = (char_u *)vim_strsave_fnameescape((const char *)sname, false); + p = vim_strsave_fnameescape(sname, false); xfree(sname); + return p; +} - /* write the result */ - bool retval = fputs((char *)p, fd) < 0 ? FAIL : OK; +// Write a file name to the session file. +// Takes care of the "slash" option in 'sessionoptions' and escapes special +// characters. +// Returns FAIL if writing fails. +static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +{ + char *p = ses_escape_fname((char *)name, flagp); + bool retval = fputs(p, fd) < 0 ? FAIL : OK; xfree(p); return retval; } -- cgit From 2c1d12d0beda7b359fec94c00746b7206ae8fedd Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 03:18:45 -0800 Subject: mksession: always write LF "\n" line-endings - remove `MKSESSION_NL`, `mksession_nl` - deprecate the "unix" flag of 'sessionoptions' There is no reason to choose CRLF or LF for session files. Instead just always write LF. --- src/nvim/ex_docmd.c | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6791bbd34c..2950b2950c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8085,11 +8085,6 @@ static void close_redir(void) } } -#ifdef USE_CRNL -# define MKSESSION_NL -static int mksession_nl = FALSE; /* use NL only in put_eol() */ -#endif - /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". static void ex_mkrc(exarg_T *eap) { @@ -8142,12 +8137,6 @@ static void ex_mkrc(exarg_T *eap) else flagp = &ssop_flags; -#ifdef MKSESSION_NL - /* "unix" in 'sessionoptions': use NL line separator */ - if (view_session && (*flagp & SSOP_UNIX)) - mksession_nl = TRUE; -#endif - /* Write the version command for :mkvimrc */ if (eap->cmdidx == CMD_mkvimrc) (void)put_line(fd, "version 6.0"); @@ -8236,9 +8225,6 @@ static void ex_mkrc(exarg_T *eap) } xfree(tbuf); } -#ifdef MKSESSION_NL - mksession_nl = FALSE; -#endif } xfree(viewFile); @@ -9964,19 +9950,11 @@ static char *get_view_file(int c) } -/* - * Write end-of-line character(s) for ":mkexrc", ":mkvimrc" and ":mksession". - * Return FAIL for a write error. - */ +/// TODO(justinmk): remove this. Formerly used to choose CRLF or LF for session +// files, but that's useless--instead we always write LF. int put_eol(FILE *fd) { -#if defined(USE_CRNL) && defined(MKSESSION_NL) - if ((!mksession_nl && putc('\r', fd) < 0) || putc('\n', fd) < 0) { -#elif defined(USE_CRNL) - if (putc('\r', fd) < 0 || putc('\n', fd) < 0) { -#else if (putc('\n', fd) < 0) { -#endif return FAIL; } return OK; -- cgit From 2070c082b516361161dba04e72fcaafad8bc3860 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 03:45:48 -0800 Subject: cleanup/ex_docmd.c: remove most put_line() calls - prefer fprintf() instead of put_line() - PUTLINE_FAIL macro to avoid some boilerplate --- src/nvim/ex_docmd.c | 349 ++++++++++++++++++++++++------------------------- src/nvim/option_defs.h | 2 +- 2 files changed, 170 insertions(+), 181 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 2950b2950c..2b952b3f15 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8085,6 +8085,9 @@ static void close_redir(void) } } +#define PUTLINE_FAIL(s) \ + do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) + /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". static void ex_mkrc(exarg_T *eap) { @@ -8137,9 +8140,10 @@ static void ex_mkrc(exarg_T *eap) else flagp = &ssop_flags; - /* Write the version command for :mkvimrc */ - if (eap->cmdidx == CMD_mkvimrc) + // Write the version command for :mkvimrc + if (eap->cmdidx == CMD_mkvimrc) { (void)put_line(fd, "version 6.0"); + } if (eap->cmdidx == CMD_mksession) { if (put_line(fd, "let SessionLoad = 1") == FAIL) @@ -8200,14 +8204,17 @@ static void ex_mkrc(exarg_T *eap) failed |= (put_view(fd, curwin, !using_vdir, flagp, -1) == FAIL); } - if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save") - == FAIL) - failed = TRUE; - if (put_line(fd, "doautoall SessionLoadPost") == FAIL) - failed = TRUE; + if (fprintf(fd, + "%s", + "let &so = s:so_save | let &siso = s:siso_save\n" + "doautoall SessionLoadPost\n") + < 0) { + failed = true; + } if (eap->cmdidx == CMD_mksession) { - if (put_line(fd, "unlet SessionLoad") == FAIL) - failed = TRUE; + if (fprintf(fd, "unlet SessionLoad\n") < 0) { + failed = true; + } } } if (put_line(fd, "\" vim: set ft=vim :") == FAIL) @@ -9127,28 +9134,21 @@ static int makeopens(FILE *fd, char_u *dirnow) only_save_windows = FALSE; /* Save ALL buffers */ // Begin by setting v:this_session, and then other sessionable variables. - if (put_line(fd, "let v:this_session=expand(\":p\")") == FAIL) { - return FAIL; - } + PUTLINE_FAIL("let v:this_session=expand(\":p\")"); if (ssop_flags & SSOP_GLOBALS) { if (store_session_globals(fd) == FAIL) { return FAIL; } } - /* - * Close all windows but one. - */ - if (put_line(fd, "silent only") == FAIL) - return FAIL; + // Close all windows but one. + PUTLINE_FAIL("silent only"); - /* - * Now a :cd command to the session directory or the current directory - */ + // + // Now a :cd command to the session directory or the current directory + // if (ssop_flags & SSOP_SESDIR) { - if (put_line(fd, "exe \"cd \" . escape(expand(\":p:h\"), ' ')") - == FAIL) - return FAIL; + PUTLINE_FAIL("exe \"cd \" . escape(expand(\":p:h\"), ' ')"); } else if (ssop_flags & SSOP_CURDIR) { sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); if (fputs("cd ", fd) < 0 @@ -9160,27 +9160,20 @@ static int makeopens(FILE *fd, char_u *dirnow) xfree(sname); } - /* - * If there is an empty, unnamed buffer we will wipe it out later. - * Remember the buffer number. - */ - if (put_line(fd, - "if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''") - == - FAIL) - return FAIL; - if (put_line(fd, " let s:wipebuf = bufnr('%')") == FAIL) - return FAIL; - if (put_line(fd, "endif") == FAIL) - return FAIL; - - /* - * Now save the current files, current buffer first. - */ - if (put_line(fd, "set shortmess=aoO") == FAIL) + if (fprintf(fd, + "%s", + // If there is an empty, unnamed buffer we will wipe it out later. + // Remember the buffer number. + "if expand('%') == '' && !&modified && line('$') <= 1" + " && getline(1) == ''\n" + " let s:wipebuf = bufnr('%')\n" + "endif\n" + // Now save the current files, current buffer first. + "set shortmess=aoO\n") < 0) { return FAIL; + } - /* Now put the other buffers into the buffer list */ + // Now put the other buffers into the buffer list. FOR_ALL_BUFFERS(buf) { if (!(only_save_windows && buf->b_nwindows == 0) && !(buf->b_help && !(ssop_flags & SSOP_HELP)) @@ -9216,9 +9209,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // in the first tab, which may cause problems. Set 'showtabline' to 2 // temporarily to avoid that. if (p_stal == 1 && first_tabpage->tp_next != NULL) { - if (put_line(fd, "set stal=2") == FAIL) { - return FAIL; - } + PUTLINE_FAIL("set stal=2"); restore_stal = TRUE; } @@ -9273,26 +9264,29 @@ static int makeopens(FILE *fd, char_u *dirnow) } } - /* If no file got edited create an empty tab page. */ - if (need_tabnew && put_line(fd, "tabnew") == FAIL) + // If no file got edited create an empty tab page. + if (need_tabnew && put_line(fd, "tabnew") == FAIL) { return FAIL; + } - /* - * Save current window layout. - */ - if (put_line(fd, "set splitbelow splitright") == FAIL) - return FAIL; - if (ses_win_rec(fd, tab_topframe) == FAIL) + // + // Save current window layout. + // + PUTLINE_FAIL("set splitbelow splitright"); + if (ses_win_rec(fd, tab_topframe) == FAIL) { return FAIL; - if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) + } + if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { return FAIL; - if (!p_spr && put_line(fd, "set nosplitright") == FAIL) + } + if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { return FAIL; + } - /* - * Check if window sizes can be restored (no windows omitted). - * Remember the window number of the current window after restoring. - */ + // + // Check if window sizes can be restored (no windows omitted). + // Remember the window number of the current window after restoring. + // nr = 0; for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { if (ses_do_win(wp)) @@ -9303,9 +9297,8 @@ static int makeopens(FILE *fd, char_u *dirnow) cnr = nr; } - /* Go to the first window. */ - if (put_line(fd, "wincmd t") == FAIL) - return FAIL; + // Go to the first window. + PUTLINE_FAIL("wincmd t"); // If more than one window, see if sizes can be restored. // First set 'winheight' and 'winwidth' to 1 to avoid the windows being @@ -9314,10 +9307,11 @@ static int makeopens(FILE *fd, char_u *dirnow) // cursor can be set. This is done again below. // winminheight and winminwidth need to be set to avoid an error if the // user has set winheight or winwidth. - if (put_line(fd, "set winminheight=0") == FAIL - || put_line(fd, "set winheight=1") == FAIL - || put_line(fd, "set winminwidth=0") == FAIL - || put_line(fd, "set winwidth=1") == FAIL) { + if (fprintf(fd, + "set winminheight=0\n" + "set winheight=1\n" + "set winminwidth=0\n" + "set winwidth=1\n") < 0) { return FAIL; } if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { @@ -9328,34 +9322,39 @@ static int makeopens(FILE *fd, char_u *dirnow) // Restore the view of the window (options, file, cursor, etc.). // for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) + if (!ses_do_win(wp)) { continue; - if (put_view(fd, wp, wp != edited_win, &ssop_flags, - cur_arg_idx) == FAIL) + } + if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx) + == FAIL) { return FAIL; - if (nr > 1 && put_line(fd, "wincmd w") == FAIL) + } + if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { return FAIL; + } next_arg_idx = wp->w_arg_idx; } - /* The argument index in the first tab page is zero, need to set it in - * each window. For further tab pages it's the window where we do - * "tabedit". */ + // The argument index in the first tab page is zero, need to set it in + // each window. For further tab pages it's the window where we do + // "tabedit". cur_arg_idx = next_arg_idx; - /* - * Restore cursor to the current window if it's not the first one. - */ + // + // Restore cursor to the current window if it's not the first one. + // if (cnr > 1 && (fprintf(fd, "%dwincmd w", cnr) < 0 - || put_eol(fd) == FAIL)) + || put_eol(fd) == FAIL)) { return FAIL; + } - /* - * Restore window sizes again after jumping around in windows, because - * the current window has a minimum size while others may not. - */ - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) + // + // Restore window sizes again after jumping around in windows, because + // the current window has a minimum size while others may not. + // + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { return FAIL; + } // Take care of tab-local working directories if applicable if (tp->tp_localdir) { @@ -9368,34 +9367,33 @@ static int makeopens(FILE *fd, char_u *dirnow) did_lcd = true; } - /* Don't continue in another tab page when doing only the current one - * or when at the last tab page. */ - if (!(ssop_flags & SSOP_TABPAGES)) + // Don't continue in another tab page when doing only the current one + // or when at the last tab page. + if (!(ssop_flags & SSOP_TABPAGES)) { break; + } } if (ssop_flags & SSOP_TABPAGES) { - if (fprintf(fd, "tabnext %d", tabpage_index(curtab)) < 0 - || put_eol(fd) == FAIL) + if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { return FAIL; + } } if (restore_stal && put_line(fd, "set stal=1") == FAIL) { return FAIL; } - /* - * Wipe out an empty unnamed buffer we started in. - */ - if (put_line(fd, "if exists('s:wipebuf') " - "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'") - == FAIL) - return FAIL; - if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL) - return FAIL; - if (put_line(fd, "endif") == FAIL) - return FAIL; - if (put_line(fd, "unlet! s:wipebuf") == FAIL) + // + // Wipe out an empty unnamed buffer we started in. + // + if (fprintf(fd, "%s", + "if exists('s:wipebuf') " + "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" + " silent exe 'bwipe ' . s:wipebuf\n" + "endif\n" + "unlet! s:wipebuf\n") < 0) { return FAIL; + } // Re-apply options. if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 @@ -9410,14 +9408,16 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - /* - * Lastly, execute the x.vim file if it exists. - */ - if (put_line(fd, "let s:sx = expand(\":p:r\").\"x.vim\"") == FAIL - || put_line(fd, "if file_readable(s:sx)") == FAIL - || put_line(fd, " exe \"source \" . fnameescape(s:sx)") == FAIL - || put_line(fd, "endif") == FAIL) + // + // Lastly, execute the x.vim file if it exists. + // + if (fprintf(fd, "%s", + "let s:sx = expand(\":p:r\").\"x.vim\"\n" + "if file_readable(s:sx)\n" + " exe \"source \" . fnameescape(s:sx)\n" + "endif\n") < 0) { return FAIL; + } return OK; } @@ -9457,53 +9457,50 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) } } } else { - // Just equalise window sizes - if (put_line(fd, "wincmd =") == FAIL) { - return FAIL; - } + // Just equalize window sizes. + PUTLINE_FAIL("wincmd ="); } return OK; } -/* - * Write commands to "fd" to recursively create windows for frame "fr", - * horizontally and vertically split. - * After the commands the last window in the frame is the current window. - * Returns FAIL when writing the commands to "fd" fails. - */ +// Write commands to "fd" to recursively create windows for frame "fr", +// horizontally and vertically split. +// After the commands the last window in the frame is the current window. +// Returns FAIL when writing the commands to "fd" fails. static int ses_win_rec(FILE *fd, frame_T *fr) { frame_T *frc; int count = 0; if (fr->fr_layout != FR_LEAF) { - /* Find first frame that's not skipped and then create a window for - * each following one (first frame is already there). */ + // Find first frame that's not skipped and then create a window for + // each following one (first frame is already there). frc = ses_skipframe(fr->fr_child); if (frc != NULL) while ((frc = ses_skipframe(frc->fr_next)) != NULL) { - /* Make window as big as possible so that we have lots of room - * to split. */ - if (put_line(fd, "wincmd _ | wincmd |") == FAIL - || put_line(fd, fr->fr_layout == FR_COL - ? "split" : "vsplit") == FAIL) + // Make window as big as possible so that we have lots of room + // to split. + if (fprintf(fd, "%s%s", + "wincmd _ | wincmd |\n", + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n") + ) < 0) { return FAIL; + } ++count; } - /* Go back to the first window. */ + // Go back to the first window. if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL ? "%dwincmd k" : "%dwincmd h", count) < 0 || put_eol(fd) == FAIL)) return FAIL; - /* Recursively create frames/windows in each window of this column or - * row. */ + // Recursively create frames/windows in each window of this column or row. frc = ses_skipframe(fr->fr_child); while (frc != NULL) { ses_win_rec(fd, frc); frc = ses_skipframe(frc->fr_next); - /* Go to next window. */ + // Go to next window. if (frc != NULL && put_line(fd, "wincmd w") == FAIL) return FAIL; } @@ -9511,10 +9508,8 @@ static int ses_win_rec(FILE *fd, frame_T *fr) return OK; } -/* - * Skip frames that don't contain windows we want to save in the Session. - * Returns NULL when there none. - */ +// Skip frames that don't contain windows we want to save in the Session. +// Returns NULL when there none. static frame_T *ses_skipframe(frame_T *fr) { frame_T *frc; @@ -9598,14 +9593,14 @@ put_view( * Local argument list. */ if (wp->w_alist == &global_alist) { - if (put_line(fd, "argglobal") == FAIL) - return FAIL; + PUTLINE_FAIL("argglobal"); } else { if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, - flagp == &vop_flags - || !(*flagp & SSOP_CURDIR) - || wp->w_localdir != NULL, flagp) == FAIL) + flagp == &vop_flags + || !(*flagp & SSOP_CURDIR) + || wp->w_localdir != NULL, flagp) == FAIL) { return FAIL; + } } /* Only when part of a session: restore the argument index. Some @@ -9658,9 +9653,7 @@ put_view( xfree(fname_esc); } else { // No file in this buffer, just make it empty. - if (put_line(fd, "enew") == FAIL) { - return FAIL; - } + PUTLINE_FAIL("enew"); if (wp->w_buffer->b_ffname != NULL) { // The buffer does have a name, but it's not a file name. if (fputs("file ", fd) < 0 @@ -9717,41 +9710,41 @@ put_view( */ if (do_cursor) { - /* Restore the cursor line in the file and relatively in the - * window. Don't use "G", it changes the jumplist. */ + // Restore the cursor line in the file and relatively in the + // window. Don't use "G", it changes the jumplist. if (fprintf(fd, "let s:l = %" PRId64 " - ((%" PRId64 - " * winheight(0) + %" PRId64 ") / %" PRId64 ")", + " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:l < 1 | let s:l = 1 | endif\n" + "exe s:l\n" + "normal! zt\n" + "%" PRId64 "\n" + , (int64_t)wp->w_cursor.lnum, (int64_t)(wp->w_cursor.lnum - wp->w_topline), (int64_t)(wp->w_height_inner / 2), - (int64_t)wp->w_height_inner) < 0 - || put_eol(fd) == FAIL - || put_line(fd, "if s:l < 1 | let s:l = 1 | endif") == FAIL - || put_line(fd, "exe s:l") == FAIL - || put_line(fd, "normal! zt") == FAIL - || fprintf(fd, "%" PRId64, (int64_t)wp->w_cursor.lnum) < 0 - || put_eol(fd) == FAIL) + (int64_t)wp->w_height_inner, + (int64_t)wp->w_cursor.lnum + ) < 0) { return FAIL; - /* Restore the cursor column and left offset when not wrapping. */ + } + // Restore the cursor column and left offset when not wrapping. if (wp->w_cursor.col == 0) { - if (put_line(fd, "normal! 0") == FAIL) - return FAIL; + PUTLINE_FAIL("normal! 0"); } else { if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { if (fprintf(fd, "let s:c = %" PRId64 " - ((%" PRId64 - " * winwidth(0) + %" PRId64 ") / %" PRId64 ")", + " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:c > 0\n" + " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" + "else\n" + , (int64_t)wp->w_virtcol + 1, (int64_t)(wp->w_virtcol - wp->w_leftcol), (int64_t)(wp->w_width / 2), - (int64_t)wp->w_width) < 0 - || put_eol(fd) == FAIL - || put_line(fd, "if s:c > 0") == FAIL - || fprintf(fd, " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'", - (int64_t)wp->w_virtcol + 1) < 0 - || put_eol(fd) == FAIL - || put_line(fd, "else") == FAIL + (int64_t)wp->w_width, + (int64_t)wp->w_virtcol + 1) < 0 || put_view_curpos(fd, wp, " ") == FAIL || put_line(fd, "endif") == FAIL) { return FAIL; @@ -9779,18 +9772,17 @@ put_view( return OK; } -/* - * Write an argument list to the session file. - * Returns FAIL if writing fails. - */ -static int -ses_arglist( - FILE *fd, - char *cmd, - garray_T *gap, - int fullname, /* TRUE: use full path name */ - unsigned *flagp -) +/// Writes an :argument list to the session file. +/// +/// @param fd +/// @param cmd +/// @param gap +/// @param fullname true: use full path name +/// @param flagp +/// +/// @returns FAIL if writing fails. +static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, + unsigned *flagp) { char_u *buf = NULL; char_u *s; @@ -9798,9 +9790,7 @@ ses_arglist( if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) { return FAIL; } - if (put_line(fd, "%argdel") == FAIL) { - return FAIL; - } + PUTLINE_FAIL("%argdel"); for (int i = 0; i < gap->ga_len; ++i) { /* NULL file names are skipped (only happens when out of memory). */ s = alist_name(&((aentry_T *)gap->ga_data)[i]); @@ -9838,6 +9828,7 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp, bool add_eol) } return (char *)buf->b_ffname; } + /// Write a buffer name to the session file. /// Also ends the line, if "add_eol" is true. /// Returns FAIL if writing fails. @@ -9960,14 +9951,12 @@ int put_eol(FILE *fd) return OK; } -/* - * Write a line to "fd". - * Return FAIL for a write error. - */ +// TODO(justinmk): remove this, not needed after 823750fef315. int put_line(FILE *fd, char *s) { - if (fputs(s, fd) < 0 || put_eol(fd) == FAIL) + if (0 > fprintf(fd, "%s\n", s)) { return FAIL; + } return OK; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index fcad6836bf..5f7d0b0614 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -578,7 +578,7 @@ static char *(p_ssop_values[]) = { # define SSOP_BLANK 0x080 # define SSOP_GLOBALS 0x100 # define SSOP_SLASH 0x200 -# define SSOP_UNIX 0x400 +# define SSOP_UNIX 0x400 /* Deprecated, not used. */ # define SSOP_SESDIR 0x800 # define SSOP_CURDIR 0x1000 # define SSOP_FOLDS 0x2000 -- cgit From 90486278061f114eba8703d2f058678bb6e247e6 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 05:08:12 -0800 Subject: lint --- src/nvim/ex_docmd.c | 38 ++++++++++++++++++-------------------- src/nvim/option_defs.h | 2 +- 2 files changed, 19 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 2b952b3f15..47c42c001e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9210,7 +9210,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // temporarily to avoid that. if (p_stal == 1 && first_tabpage->tp_next != NULL) { PUTLINE_FAIL("set stal=2"); - restore_stal = TRUE; + restore_stal = true; } // @@ -9482,18 +9482,18 @@ static int ses_win_rec(FILE *fd, frame_T *fr) // to split. if (fprintf(fd, "%s%s", "wincmd _ | wincmd |\n", - (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n") - ) < 0) { + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { return FAIL; } - ++count; + count++; } // Go back to the first window. if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k" : "%dwincmd h", count) < 0 - || put_eol(fd) == FAIL)) + ? "%dwincmd k" : "%dwincmd h", count) < 0 + || put_eol(fd) == FAIL)) { return FAIL; + } // Recursively create frames/windows in each window of this column or row. frc = ses_skipframe(fr->fr_child); @@ -9501,8 +9501,9 @@ static int ses_win_rec(FILE *fd, frame_T *fr) ses_win_rec(fd, frc); frc = ses_skipframe(frc->fr_next); // Go to next window. - if (frc != NULL && put_line(fd, "wincmd w") == FAIL) + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { return FAIL; + } } } return OK; @@ -9694,9 +9695,9 @@ put_view( if (f == FAIL) return FAIL; - /* - * Save Folds when 'buftype' is empty and for help files. - */ + // + // Save Folds when 'buftype' is empty and for help files. + // if ((*flagp & SSOP_FOLDS) && wp->w_buffer->b_ffname != NULL && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) @@ -9705,11 +9706,10 @@ put_view( return FAIL; } - /* - * Set the cursor after creating folds, since that moves the cursor. - */ + // + // Set the cursor after creating folds, since that moves the cursor. + // if (do_cursor) { - // Restore the cursor line in the file and relatively in the // window. Don't use "G", it changes the jumplist. if (fprintf(fd, @@ -9718,14 +9718,12 @@ put_view( "if s:l < 1 | let s:l = 1 | endif\n" "exe s:l\n" "normal! zt\n" - "%" PRId64 "\n" - , + "%" PRId64 "\n", (int64_t)wp->w_cursor.lnum, (int64_t)(wp->w_cursor.lnum - wp->w_topline), (int64_t)(wp->w_height_inner / 2), (int64_t)wp->w_height_inner, - (int64_t)wp->w_cursor.lnum - ) < 0) { + (int64_t)wp->w_cursor.lnum) < 0) { return FAIL; } // Restore the cursor column and left offset when not wrapping. @@ -9791,8 +9789,8 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, return FAIL; } PUTLINE_FAIL("%argdel"); - for (int i = 0; i < gap->ga_len; ++i) { - /* NULL file names are skipped (only happens when out of memory). */ + for (int i = 0; i < gap->ga_len; i++) { + // NULL file names are skipped (only happens when out of memory). s = alist_name(&((aentry_T *)gap->ga_data)[i]); if (s != NULL) { if (fullname) { diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 5f7d0b0614..227c0fec4d 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -578,7 +578,7 @@ static char *(p_ssop_values[]) = { # define SSOP_BLANK 0x080 # define SSOP_GLOBALS 0x100 # define SSOP_SLASH 0x200 -# define SSOP_UNIX 0x400 /* Deprecated, not used. */ +# define SSOP_UNIX 0x400 // Deprecated, not used. # define SSOP_SESDIR 0x800 # define SSOP_CURDIR 0x1000 # define SSOP_FOLDS 0x2000 -- cgit From c4f4719ced9101195a84b350249ab2a105de627c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 05:36:20 -0800 Subject: cleanup/ex_docmd.c: remove most put_eol() calls --- src/nvim/ex_docmd.c | 121 ++++++++++++++++++++++++---------------------------- 1 file changed, 55 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 47c42c001e..79cf1794b1 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9151,12 +9151,13 @@ static int makeopens(FILE *fd, char_u *dirnow) PUTLINE_FAIL("exe \"cd \" . escape(expand(\":p:h\"), ' ')"); } else if (ssop_flags & SSOP_CURDIR) { sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); - if (fputs("cd ", fd) < 0 - || ses_put_fname(fd, sname, &ssop_flags) == FAIL - || put_eol(fd) == FAIL) { + char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); + if (fprintf(fd, "cd %s\n", fname_esc) < 0) { + xfree(fname_esc); xfree(sname); return FAIL; } + xfree(fname_esc); xfree(sname); } @@ -9196,11 +9197,11 @@ static int makeopens(FILE *fd, char_u *dirnow) } if (ssop_flags & SSOP_RESIZE) { - /* Note: after the restore we still check it worked!*/ - if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64, - (int64_t)Rows, (int64_t)Columns) < 0 - || put_eol(fd) == FAIL) + // Note: after the restore we still check it worked! + if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", + (int64_t)Rows, (int64_t)Columns) < 0) { return FAIL; + } } int restore_stal = FALSE; @@ -9343,8 +9344,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // // Restore cursor to the current window if it's not the first one. // - if (cnr > 1 && (fprintf(fd, "%dwincmd w", cnr) < 0 - || put_eol(fd) == FAIL)) { + if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { return FAIL; } @@ -9360,8 +9360,7 @@ static int makeopens(FILE *fd, char_u *dirnow) if (tp->tp_localdir) { if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL - || fputs(" | endif", fd) < 0 - || put_eol(fd) == FAIL) { + || fputs(" | endif\n", fd) < 0) { return FAIL; } did_lcd = true; @@ -9396,15 +9395,15 @@ static int makeopens(FILE *fd, char_u *dirnow) } // Re-apply options. - if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 - " winminheight=%" PRId64 " winminwidth=%" PRId64 - " shortmess=%s", + if (fprintf(fd, + "set winheight=%" PRId64 " winwidth=%" PRId64 + " winminheight=%" PRId64 " winminwidth=%" PRId64 + " shortmess=%s\n", (int64_t)p_wh, (int64_t)p_wiw, (int64_t)p_wmh, (int64_t)p_wmw, - p_shm) < 0 - || put_eol(fd) == FAIL) { + p_shm) < 0) { return FAIL; } @@ -9438,10 +9437,9 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) if (wp->w_height + wp->w_status_height < topframe->fr_height && (fprintf(fd, "exe '%dresize ' . ((&lines * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")", + " + %" PRId64 ") / %" PRId64 ")\n", n, (int64_t)wp->w_height, - (int64_t)Rows / 2, (int64_t)Rows) < 0 - || put_eol(fd) == FAIL)) { + (int64_t)Rows / 2, (int64_t)Rows) < 0)) { return FAIL; } @@ -9449,10 +9447,9 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) if (wp->w_width < Columns && (fprintf(fd, "exe 'vert %dresize ' . ((&columns * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")", + " + %" PRId64 ") / %" PRId64 ")\n", n, (int64_t)wp->w_width, (int64_t)Columns / 2, - (int64_t)Columns) < 0 - || put_eol(fd) == FAIL)) { + (int64_t)Columns) < 0)) { return FAIL; } } @@ -9490,8 +9487,7 @@ static int ses_win_rec(FILE *fd, frame_T *fr) // Go back to the first window. if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k" : "%dwincmd h", count) < 0 - || put_eol(fd) == FAIL)) { + ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { return FAIL; } @@ -9560,11 +9556,11 @@ static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) int r; if (wp->w_curswant == MAXCOL) { - r = fprintf(fd, "%snormal! $", spaces); + r = fprintf(fd, "%snormal! $\n", spaces); } else { - r = fprintf(fd, "%snormal! 0%d|", spaces, wp->w_virtcol + 1); + r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); } - return r < 0 || put_eol(fd) == FAIL ? FAIL : OK; + return r >= 0; } /* @@ -9608,18 +9604,19 @@ put_view( * arguments may have been deleted, check if the index is valid. */ if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) && flagp == &ssop_flags) { - if (fprintf(fd, "%" PRId64 "argu", (int64_t)wp->w_arg_idx + 1) < 0 - || put_eol(fd) == FAIL) { + if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { return FAIL; } did_next = true; } - /* Edit the file. Skip this when ":next" already did it. */ + // Edit the file. Skip this when ":next" already did it. if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { - /* - * Load the file. - */ + char *fname_esc = + ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); + // + // Load the file. + // if (wp->w_buffer->b_ffname != NULL && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) ) { @@ -9629,49 +9626,41 @@ put_view( // Note, if a buffer for that file already exists, use :badd to // edit that buffer, to not lose folding information (:edit resets // folds in other buffers) - if (fputs("if bufexists(\"", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs("\") | buffer ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs(" | else | edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL - || fputs(" | endif", fd) < 0 - || put_eol(fd) == FAIL) { - return FAIL; - } - - char *fname_esc = - ses_escape_fname(ses_get_fname(wp->w_buffer, flagp, false), flagp); - // Fixup :terminal buffer name. if (fprintf(fd, + "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" + // Fixup :terminal buffer name. #7836 "if &buftype ==# 'terminal'\n" " silent file %s\n" "endif\n", + fname_esc, + fname_esc, + fname_esc, fname_esc) < 0) { xfree(fname_esc); return FAIL; } - xfree(fname_esc); } else { // No file in this buffer, just make it empty. PUTLINE_FAIL("enew"); if (wp->w_buffer->b_ffname != NULL) { // The buffer does have a name, but it's not a file name. - if (fputs("file ", fd) < 0 - || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) { + if (fprintf(fd, "file %s\n", fname_esc) < 0) { + xfree(fname_esc); return FAIL; } } do_cursor = false; } + xfree(fname_esc); } /* * Local mappings and abbreviations. */ if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - && makemap(fd, wp->w_buffer) == FAIL) + && makemap(fd, wp->w_buffer) == FAIL) { return FAIL; + } /* * Local options. Need to go to the window temporarily. @@ -9692,8 +9681,9 @@ put_view( f = OK; curwin = save_curwin; curbuf = curwin->w_buffer; - if (f == FAIL) + if (f == FAIL) { return FAIL; + } // // Save Folds when 'buftype' is empty and for help files. @@ -9736,8 +9726,7 @@ put_view( " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" "if s:c > 0\n" " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" - "else\n" - , + "else\n", (int64_t)wp->w_virtcol + 1, (int64_t)(wp->w_virtcol - wp->w_leftcol), (int64_t)(wp->w_width / 2), @@ -9761,7 +9750,7 @@ put_view( && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { if (fputs("lcd ", fd) < 0 || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL - || put_eol(fd) == FAIL) { + || fprintf(fd, "\n") < 0) { return FAIL; } did_lcd = true; @@ -9785,10 +9774,9 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, char_u *buf = NULL; char_u *s; - if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) { + if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { return FAIL; } - PUTLINE_FAIL("%argdel"); for (int i = 0; i < gap->ga_len; i++) { // NULL file names are skipped (only happens when out of memory). s = alist_name(&((aentry_T *)gap->ga_data)[i]); @@ -9798,11 +9786,13 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); s = buf; } - if (fputs("$argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL - || put_eol(fd) == FAIL) { + char *fname_esc = ses_escape_fname((char *)s, flagp); + if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { + xfree(fname_esc); xfree(buf); return FAIL; } + xfree(fname_esc); xfree(buf); } } @@ -9810,7 +9800,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, } /// Gets the buffer name for `buf`. -static char *ses_get_fname(buf_T *buf, unsigned *flagp, bool add_eol) +static char *ses_get_fname(buf_T *buf, unsigned *flagp) { // Use the short file name if the current directory is known at the time // the session file will be sourced. @@ -9832,9 +9822,9 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp, bool add_eol) /// Returns FAIL if writing fails. static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) { - char *name = ses_get_fname(buf, flagp, add_eol); + char *name = ses_get_fname(buf, flagp); if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL - || (add_eol && put_eol(fd) == FAIL)) { + || (add_eol && fprintf(fd, "\n") < 0)) { return FAIL; } return OK; @@ -9939,8 +9929,7 @@ static char *get_view_file(int c) } -/// TODO(justinmk): remove this. Formerly used to choose CRLF or LF for session -// files, but that's useless--instead we always write LF. +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. int put_eol(FILE *fd) { if (putc('\n', fd) < 0) { @@ -9949,10 +9938,10 @@ int put_eol(FILE *fd) return OK; } -// TODO(justinmk): remove this, not needed after 823750fef315. +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. int put_line(FILE *fd, char *s) { - if (0 > fprintf(fd, "%s\n", s)) { + if (fprintf(fd, "%s\n", s) < 0) { return FAIL; } return OK; -- cgit From 1c3ca4f18fdc403813d8959b49626ac1c99e2c59 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 26 Jan 2020 14:26:01 -0800 Subject: mksession: always unix slashes "/" for filepaths --- src/nvim/ex_docmd.c | 16 ++++++++++------ src/nvim/option_defs.h | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 79cf1794b1..d87dd29f88 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -8089,6 +8089,10 @@ static void close_redir(void) do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// - SSOP_UNIX: line-endings are always LF +/// - SSOP_SLASH: filenames are always written with "/" slash static void ex_mkrc(exarg_T *eap) { FILE *fd; @@ -9112,6 +9116,8 @@ char_u *expand_sfile(char_u *arg) /// Writes commands for restoring the current buffers, for :mksession. /// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// /// @param dirnow Current directory name /// @param fd File descriptor to write to /// @@ -9840,12 +9846,10 @@ static char *ses_escape_fname(char *name, unsigned *flagp) char *p; char *sname = (char *)home_replace_save(NULL, (char_u *)name); - if (*flagp & SSOP_SLASH) { - // change all backslashes to forward slashes - for (p = sname; *p != NUL; MB_PTR_ADV(p)) { - if (*p == '\\') { - *p = '/'; - } + // Always SSOP_SLASH: change all backslashes to forward slashes. + for (p = sname; *p != NUL; MB_PTR_ADV(p)) { + if (*p == '\\') { + *p = '/'; } } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 227c0fec4d..c5d8b134c4 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -577,8 +577,8 @@ static char *(p_ssop_values[]) = { # define SSOP_HELP 0x040 # define SSOP_BLANK 0x080 # define SSOP_GLOBALS 0x100 -# define SSOP_SLASH 0x200 -# define SSOP_UNIX 0x400 // Deprecated, not used. +# define SSOP_SLASH 0x200 // Deprecated, always set. +# define SSOP_UNIX 0x400 // Deprecated, always set. # define SSOP_SESDIR 0x800 # define SSOP_CURDIR 0x1000 # define SSOP_FOLDS 0x2000 -- cgit From e673a0df1a0391b61a58cf3579754f7abd17e158 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 26 Jan 2020 21:16:34 -0500 Subject: vim-patch:8.2.0152: restoring ctrl_x_mode is not needed Problem: Restoring ctrl_x_mode is not needed. Solution: Remove restoring the old value, it's changed again soon. https://github.com/vim/vim/commit/da812e282a4e2d6d8c9604a3a2a38396437dfe20 --- src/nvim/edit.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 68fa99484c..9c22f09059 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3594,17 +3594,12 @@ static bool ins_compl_prep(int c) auto_format(FALSE, TRUE); - { - const int new_mode = ctrl_x_mode; - - // Trigger the CompleteDone event to give scripts a chance to - // act upon the completion. Do this before clearing the info, - // and restore ctrl_x_mode, so that complete_info() can be - // used. - ctrl_x_mode = prev_mode; - ins_apply_autocmds(EVENT_COMPLETEDONE); - ctrl_x_mode = new_mode; - } + // Trigger the CompleteDone event to give scripts a chance to + // act upon the completion. Do this before clearing the info, + // and restore ctrl_x_mode, so that complete_info() can be + // used. + ctrl_x_mode = prev_mode; + ins_apply_autocmds(EVENT_COMPLETEDONE); ins_compl_free(); compl_started = false; -- cgit From 5ede2766c8d994f9e299288d46cefdd12ffee47f Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 26 Jan 2020 21:34:32 -0500 Subject: vim-patch:8.2.0158: triggering CompleteDone earlier is not backwards compatible Problem: Triggering CompleteDone earlier is not backwards compatible. (Daniel Hahler) Solution: Add CompleteDonePre instead. https://github.com/vim/vim/commit/3f169ce17e8b779d105c96138a8b4246f2d270b9 --- src/nvim/auevents.lua | 1 + src/nvim/edit.c | 12 +++++++----- src/nvim/testdir/test_ins_complete.vim | 11 +++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 96e170a9e1..4391d997a7 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -31,6 +31,7 @@ return { 'ColorSchemePre', -- before loading a colorscheme 'CompleteChanged', -- after popup menu changed 'CompleteDone', -- after finishing insert complete + 'CompleteDonePre', -- idem, before clearing info 'CursorHold', -- cursor in same position for a while 'CursorHoldI', -- idem, in Insert mode 'CursorMoved', -- cursor was moved diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 9c22f09059..e253905057 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3594,12 +3594,11 @@ static bool ins_compl_prep(int c) auto_format(FALSE, TRUE); - // Trigger the CompleteDone event to give scripts a chance to - // act upon the completion. Do this before clearing the info, - // and restore ctrl_x_mode, so that complete_info() can be - // used. + // Trigger the CompleteDonePre event to give scripts a chance to + // act upon the completion before clearing the info, and restore + // ctrl_x_mode, so that complete_info() can be used. ctrl_x_mode = prev_mode; - ins_apply_autocmds(EVENT_COMPLETEDONE); + ins_apply_autocmds(EVENT_COMPLETEDONEPRE); ins_compl_free(); compl_started = false; @@ -3625,6 +3624,9 @@ static bool ins_compl_prep(int c) */ if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) do_c_expr_indent(); + // Trigger the CompleteDone event to give scripts a chance to act + // upon the end of completion. + ins_apply_autocmds(EVENT_COMPLETEDONE); } } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) /* Trigger the CompleteDone event to give scripts a chance to act diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index e6d427db05..f4e6b698c6 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -130,7 +130,7 @@ func s:CompleteDone_CheckCompletedItemNone() let s:called_completedone = 1 endfunc -function! s:CompleteDone_CheckCompletedItemDict() +func s:CompleteDone_CheckCompletedItemDict(pre) call assert_equal( 'aword', v:completed_item[ 'word' ] ) call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) @@ -138,10 +138,12 @@ function! s:CompleteDone_CheckCompletedItemDict() call assert_equal( 'W', v:completed_item[ 'kind' ] ) call assert_equal( 'test', v:completed_item[ 'user_data' ] ) - call assert_equal('function', complete_info().mode) + if a:pre + call assert_equal('function', complete_info().mode) + endif let s:called_completedone = 1 -endfunction +endfunc func Test_CompleteDoneNone() throw 'skipped: Nvim does not support v:none' @@ -161,7 +163,8 @@ func Test_CompleteDoneNone() endfunc func Test_CompleteDoneDict() - au CompleteDone * :call CompleteDone_CheckCompletedItemDict() + au CompleteDonePre * :call CompleteDone_CheckCompletedItemDict(1) + au CompleteDone * :call CompleteDone_CheckCompletedItemDict(0) set completefunc=CompleteDone_CompleteFuncDict execute "normal a\\\" -- cgit From 75e85622493cfbd357d2d87d988dc0ab79fd8326 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 27 Jan 2020 01:10:23 -0800 Subject: refactor: move session functions to ex_session.c --- src/nvim/eval.c | 1 + src/nvim/ex_docmd.c | 1002 +---------------------------------------------- src/nvim/ex_session.c | 1034 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/ex_session.h | 13 + src/nvim/fold.c | 1 + src/nvim/getchar.c | 1 + src/nvim/option.c | 1 + 7 files changed, 1053 insertions(+), 1000 deletions(-) create mode 100644 src/nvim/ex_session.c create mode 100644 src/nvim/ex_session.h (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 55b18d5f4f..5c6e475ac7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -70,6 +70,7 @@ #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" +#include "nvim/ex_session.h" #include "nvim/sha256.h" #include "nvim/sign.h" #include "nvim/spell.h" diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index d87dd29f88..12bee3ab86 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1,9 +1,7 @@ // 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 -/* - * ex_docmd.c: functions for executing an Ex command line. - */ +// ex_docmd.c: functions for executing an Ex command line. #include #include @@ -40,6 +38,7 @@ #include "nvim/menu.h" #include "nvim/message.h" #include "nvim/misc1.h" +#include "nvim/ex_session.h" #include "nvim/keymap.h" #include "nvim/file_search.h" #include "nvim/garray.h" @@ -79,9 +78,6 @@ static int quitmore = 0; static bool ex_pressedreturn = false; -/// Whether ":lcd" or ":tcd" was produced for a session. -static int did_lcd; - typedef struct ucmd { char_u *uc_name; // The command name uint32_t uc_argt; // The argument type @@ -8085,162 +8081,6 @@ static void close_redir(void) } } -#define PUTLINE_FAIL(s) \ - do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) - -/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". -/// -/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. -/// - SSOP_UNIX: line-endings are always LF -/// - SSOP_SLASH: filenames are always written with "/" slash -static void ex_mkrc(exarg_T *eap) -{ - FILE *fd; - int failed = false; - int view_session = false; // :mkview, :mksession - int using_vdir = false; // using 'viewdir'? - char *viewFile = NULL; - unsigned *flagp; - - if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { - view_session = TRUE; - } - - /* Use the short file name until ":lcd" is used. We also don't use the - * short file name when 'acd' is set, that is checked later. */ - did_lcd = FALSE; - - char *fname; - // ":mkview" or ":mkview 9": generate file name with 'viewdir' - if (eap->cmdidx == CMD_mkview - && (*eap->arg == NUL - || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { - eap->forceit = true; - fname = get_view_file(*eap->arg); - if (fname == NULL) { - return; - } - viewFile = fname; - using_vdir = true; - } else if (*eap->arg != NUL) { - fname = (char *) eap->arg; - } else if (eap->cmdidx == CMD_mkvimrc) { - fname = VIMRC_FILE; - } else if (eap->cmdidx == CMD_mksession) { - fname = SESSION_FILE; - } else { - fname = EXRC_FILE; - } - - /* When using 'viewdir' may have to create the directory. */ - if (using_vdir && !os_isdir(p_vdir)) { - vim_mkdir_emsg((const char *)p_vdir, 0755); - } - - fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); - if (fd != NULL) { - if (eap->cmdidx == CMD_mkview) - flagp = &vop_flags; - else - flagp = &ssop_flags; - - // Write the version command for :mkvimrc - if (eap->cmdidx == CMD_mkvimrc) { - (void)put_line(fd, "version 6.0"); - } - - if (eap->cmdidx == CMD_mksession) { - if (put_line(fd, "let SessionLoad = 1") == FAIL) - failed = TRUE; - } - - if (!view_session || (eap->cmdidx == CMD_mksession - && (*flagp & SSOP_OPTIONS))) { - failed |= (makemap(fd, NULL) == FAIL - || makeset(fd, OPT_GLOBAL, false) == FAIL); - } - - if (!failed && view_session) { - if (put_line(fd, - "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") - == FAIL) - failed = TRUE; - if (eap->cmdidx == CMD_mksession) { - char_u *dirnow; /* current directory */ - - dirnow = xmalloc(MAXPATHL); - /* - * Change to session file's dir. - */ - if (os_dirname(dirnow, MAXPATHL) == FAIL - || os_chdir((char *)dirnow) != 0) - *dirnow = NUL; - if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { - if (vim_chdirfile((char_u *) fname) == OK) { - shorten_fnames(true); - } - } else if (*dirnow != NUL - && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { - if (os_chdir((char *)globaldir) == 0) - shorten_fnames(TRUE); - } - - failed |= (makeopens(fd, dirnow) == FAIL); - - /* restore original dir */ - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != - NULL))) { - if (os_chdir((char *)dirnow) != 0) - EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - /* restore original dir */ - if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) - || ((ssop_flags & SSOP_CURDIR) && globaldir != - NULL))) { - if (os_chdir((char *)dirnow) != 0) - EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - } - } - xfree(dirnow); - } else { - failed |= (put_view(fd, curwin, !using_vdir, flagp, - -1) == FAIL); - } - if (fprintf(fd, - "%s", - "let &so = s:so_save | let &siso = s:siso_save\n" - "doautoall SessionLoadPost\n") - < 0) { - failed = true; - } - if (eap->cmdidx == CMD_mksession) { - if (fprintf(fd, "unlet SessionLoad\n") < 0) { - failed = true; - } - } - } - if (put_line(fd, "\" vim: set ft=vim :") == FAIL) - failed = TRUE; - - failed |= fclose(fd); - - if (failed) { - EMSG(_(e_write)); - } else if (eap->cmdidx == CMD_mksession) { - // successful session write - set v:this_session - char *const tbuf = xmalloc(MAXPATHL); - if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { - set_vim_var_string(VV_THIS_SESSION, tbuf, -1); - } - xfree(tbuf); - } - } - - xfree(viewFile); -} - /// Try creating a directory, give error message on failure /// /// @param[in] name Directory to create. @@ -9113,844 +8953,6 @@ char_u *expand_sfile(char_u *arg) return result; } - -/// Writes commands for restoring the current buffers, for :mksession. -/// -/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. -/// -/// @param dirnow Current directory name -/// @param fd File descriptor to write to -/// -/// @return FAIL on error, OK otherwise. -static int makeopens(FILE *fd, char_u *dirnow) -{ - int only_save_windows = TRUE; - int nr; - int restore_size = true; - win_T *wp; - char_u *sname; - win_T *edited_win = NULL; - int tabnr; - win_T *tab_firstwin; - frame_T *tab_topframe; - int cur_arg_idx = 0; - int next_arg_idx = 0; - - if (ssop_flags & SSOP_BUFFERS) - only_save_windows = FALSE; /* Save ALL buffers */ - - // Begin by setting v:this_session, and then other sessionable variables. - PUTLINE_FAIL("let v:this_session=expand(\":p\")"); - if (ssop_flags & SSOP_GLOBALS) { - if (store_session_globals(fd) == FAIL) { - return FAIL; - } - } - - // Close all windows but one. - PUTLINE_FAIL("silent only"); - - // - // Now a :cd command to the session directory or the current directory - // - if (ssop_flags & SSOP_SESDIR) { - PUTLINE_FAIL("exe \"cd \" . escape(expand(\":p:h\"), ' ')"); - } else if (ssop_flags & SSOP_CURDIR) { - sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); - char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); - if (fprintf(fd, "cd %s\n", fname_esc) < 0) { - xfree(fname_esc); - xfree(sname); - return FAIL; - } - xfree(fname_esc); - xfree(sname); - } - - if (fprintf(fd, - "%s", - // If there is an empty, unnamed buffer we will wipe it out later. - // Remember the buffer number. - "if expand('%') == '' && !&modified && line('$') <= 1" - " && getline(1) == ''\n" - " let s:wipebuf = bufnr('%')\n" - "endif\n" - // Now save the current files, current buffer first. - "set shortmess=aoO\n") < 0) { - return FAIL; - } - - // Now put the other buffers into the buffer list. - FOR_ALL_BUFFERS(buf) { - if (!(only_save_windows && buf->b_nwindows == 0) - && !(buf->b_help && !(ssop_flags & SSOP_HELP)) - && buf->b_fname != NULL - && buf->b_p_bl) { - if (fprintf(fd, "badd +%" PRId64 " ", - buf->b_wininfo == NULL - ? (int64_t)1L - : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 - || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { - return FAIL; - } - } - } - - /* the global argument list */ - if (ses_arglist(fd, "argglobal", &global_alist.al_ga, - !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { - return FAIL; - } - - if (ssop_flags & SSOP_RESIZE) { - // Note: after the restore we still check it worked! - if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", - (int64_t)Rows, (int64_t)Columns) < 0) { - return FAIL; - } - } - - int restore_stal = FALSE; - // When there are two or more tabpages and 'showtabline' is 1 the tabline - // will be displayed when creating the next tab. That resizes the windows - // in the first tab, which may cause problems. Set 'showtabline' to 2 - // temporarily to avoid that. - if (p_stal == 1 && first_tabpage->tp_next != NULL) { - PUTLINE_FAIL("set stal=2"); - restore_stal = true; - } - - // - // For each tab: - // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. - // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. - // - tab_firstwin = firstwin; // First window in tab page "tabnr". - tab_topframe = topframe; - for (tabnr = 1;; tabnr++) { - tabpage_T *tp = find_tabpage(tabnr); - if (tp == NULL) { - break; // done all tab pages - } - - int need_tabnew = false; - int cnr = 1; - - if ((ssop_flags & SSOP_TABPAGES)) { - if (tp == curtab) { - tab_firstwin = firstwin; - tab_topframe = topframe; - } else { - tab_firstwin = tp->tp_firstwin; - tab_topframe = tp->tp_topframe; - } - if (tabnr > 1) - need_tabnew = TRUE; - } - - // - // Before creating the window layout, try loading one file. If this - // is aborted we don't end up with a number of useless windows. - // This may have side effects! (e.g., compressed or network file). - // - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp) - && wp->w_buffer->b_ffname != NULL - && !bt_help(wp->w_buffer) - && !bt_nofile(wp->w_buffer) - ) { - if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 - || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { - return FAIL; - } - need_tabnew = false; - if (!wp->w_arg_idx_invalid) { - edited_win = wp; - } - break; - } - } - - // If no file got edited create an empty tab page. - if (need_tabnew && put_line(fd, "tabnew") == FAIL) { - return FAIL; - } - - // - // Save current window layout. - // - PUTLINE_FAIL("set splitbelow splitright"); - if (ses_win_rec(fd, tab_topframe) == FAIL) { - return FAIL; - } - if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { - return FAIL; - } - if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { - return FAIL; - } - - // - // Check if window sizes can be restored (no windows omitted). - // Remember the window number of the current window after restoring. - // - nr = 0; - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp)) - ++nr; - else - restore_size = FALSE; - if (curwin == wp) - cnr = nr; - } - - // Go to the first window. - PUTLINE_FAIL("wincmd t"); - - // If more than one window, see if sizes can be restored. - // First set 'winheight' and 'winwidth' to 1 to avoid the windows being - // resized when moving between windows. - // Do this before restoring the view, so that the topline and the - // cursor can be set. This is done again below. - // winminheight and winminwidth need to be set to avoid an error if the - // user has set winheight or winwidth. - if (fprintf(fd, - "set winminheight=0\n" - "set winheight=1\n" - "set winminwidth=0\n" - "set winwidth=1\n") < 0) { - return FAIL; - } - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { - return FAIL; - } - - // - // Restore the view of the window (options, file, cursor, etc.). - // - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) { - continue; - } - if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx) - == FAIL) { - return FAIL; - } - if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { - return FAIL; - } - next_arg_idx = wp->w_arg_idx; - } - - // The argument index in the first tab page is zero, need to set it in - // each window. For further tab pages it's the window where we do - // "tabedit". - cur_arg_idx = next_arg_idx; - - // - // Restore cursor to the current window if it's not the first one. - // - if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { - return FAIL; - } - - // - // Restore window sizes again after jumping around in windows, because - // the current window has a minimum size while others may not. - // - if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { - return FAIL; - } - - // Take care of tab-local working directories if applicable - if (tp->tp_localdir) { - if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 - || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL - || fputs(" | endif\n", fd) < 0) { - return FAIL; - } - did_lcd = true; - } - - // Don't continue in another tab page when doing only the current one - // or when at the last tab page. - if (!(ssop_flags & SSOP_TABPAGES)) { - break; - } - } - - if (ssop_flags & SSOP_TABPAGES) { - if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { - return FAIL; - } - } - if (restore_stal && put_line(fd, "set stal=1") == FAIL) { - return FAIL; - } - - // - // Wipe out an empty unnamed buffer we started in. - // - if (fprintf(fd, "%s", - "if exists('s:wipebuf') " - "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" - " silent exe 'bwipe ' . s:wipebuf\n" - "endif\n" - "unlet! s:wipebuf\n") < 0) { - return FAIL; - } - - // Re-apply options. - if (fprintf(fd, - "set winheight=%" PRId64 " winwidth=%" PRId64 - " winminheight=%" PRId64 " winminwidth=%" PRId64 - " shortmess=%s\n", - (int64_t)p_wh, - (int64_t)p_wiw, - (int64_t)p_wmh, - (int64_t)p_wmw, - p_shm) < 0) { - return FAIL; - } - - // - // Lastly, execute the x.vim file if it exists. - // - if (fprintf(fd, "%s", - "let s:sx = expand(\":p:r\").\"x.vim\"\n" - "if file_readable(s:sx)\n" - " exe \"source \" . fnameescape(s:sx)\n" - "endif\n") < 0) { - return FAIL; - } - - return OK; -} - -static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) -{ - int n = 0; - win_T *wp; - - if (restore_size && (ssop_flags & SSOP_WINSIZE)) { - for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (!ses_do_win(wp)) { - continue; - } - n++; - - // restore height when not full height - if (wp->w_height + wp->w_status_height < topframe->fr_height - && (fprintf(fd, - "exe '%dresize ' . ((&lines * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")\n", - n, (int64_t)wp->w_height, - (int64_t)Rows / 2, (int64_t)Rows) < 0)) { - return FAIL; - } - - // restore width when not full width - if (wp->w_width < Columns - && (fprintf(fd, - "exe 'vert %dresize ' . ((&columns * %" PRId64 - " + %" PRId64 ") / %" PRId64 ")\n", - n, (int64_t)wp->w_width, (int64_t)Columns / 2, - (int64_t)Columns) < 0)) { - return FAIL; - } - } - } else { - // Just equalize window sizes. - PUTLINE_FAIL("wincmd ="); - } - return OK; -} - -// Write commands to "fd" to recursively create windows for frame "fr", -// horizontally and vertically split. -// After the commands the last window in the frame is the current window. -// Returns FAIL when writing the commands to "fd" fails. -static int ses_win_rec(FILE *fd, frame_T *fr) -{ - frame_T *frc; - int count = 0; - - if (fr->fr_layout != FR_LEAF) { - // Find first frame that's not skipped and then create a window for - // each following one (first frame is already there). - frc = ses_skipframe(fr->fr_child); - if (frc != NULL) - while ((frc = ses_skipframe(frc->fr_next)) != NULL) { - // Make window as big as possible so that we have lots of room - // to split. - if (fprintf(fd, "%s%s", - "wincmd _ | wincmd |\n", - (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { - return FAIL; - } - count++; - } - - // Go back to the first window. - if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { - return FAIL; - } - - // Recursively create frames/windows in each window of this column or row. - frc = ses_skipframe(fr->fr_child); - while (frc != NULL) { - ses_win_rec(fd, frc); - frc = ses_skipframe(frc->fr_next); - // Go to next window. - if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { - return FAIL; - } - } - } - return OK; -} - -// Skip frames that don't contain windows we want to save in the Session. -// Returns NULL when there none. -static frame_T *ses_skipframe(frame_T *fr) -{ - frame_T *frc; - - FOR_ALL_FRAMES(frc, fr) { - if (ses_do_frame(frc)) { - break; - } - } - return frc; -} - -// Return true if frame "fr" has a window somewhere that we want to save in -// the Session. -static bool ses_do_frame(const frame_T *fr) - FUNC_ATTR_NONNULL_ARG(1) -{ - const frame_T *frc; - - if (fr->fr_layout == FR_LEAF) { - return ses_do_win(fr->fr_win); - } - FOR_ALL_FRAMES(frc, fr->fr_child) { - if (ses_do_frame(frc)) { - return true; - } - } - return false; -} - -/// Return non-zero if window "wp" is to be stored in the Session. -static int ses_do_win(win_T *wp) -{ - if (wp->w_buffer->b_fname == NULL - // When 'buftype' is "nofile" can't restore the window contents. - || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { - return ssop_flags & SSOP_BLANK; - } - if (bt_help(wp->w_buffer)) { - return ssop_flags & SSOP_HELP; - } - return true; -} - -static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) -{ - int r; - - if (wp->w_curswant == MAXCOL) { - r = fprintf(fd, "%snormal! $\n", spaces); - } else { - r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); - } - return r >= 0; -} - -/* - * Write commands to "fd" to restore the view of a window. - * Caller must make sure 'scrolloff' is zero. - */ -static int -put_view( - FILE *fd, - win_T *wp, - int add_edit, /* add ":edit" command to view */ - unsigned *flagp, /* vop_flags or ssop_flags */ - int current_arg_idx /* current argument index of the window, use - * -1 if unknown */ -) -{ - win_T *save_curwin; - int f; - int do_cursor; - int did_next = FALSE; - - /* Always restore cursor position for ":mksession". For ":mkview" only - * when 'viewoptions' contains "cursor". */ - do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); - - /* - * Local argument list. - */ - if (wp->w_alist == &global_alist) { - PUTLINE_FAIL("argglobal"); - } else { - if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, - flagp == &vop_flags - || !(*flagp & SSOP_CURDIR) - || wp->w_localdir != NULL, flagp) == FAIL) { - return FAIL; - } - } - - /* Only when part of a session: restore the argument index. Some - * arguments may have been deleted, check if the index is valid. */ - if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) - && flagp == &ssop_flags) { - if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { - return FAIL; - } - did_next = true; - } - - // Edit the file. Skip this when ":next" already did it. - if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { - char *fname_esc = - ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); - // - // Load the file. - // - if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) - ) { - // Editing a file in this buffer: use ":edit file". - // This may have side effects! (e.g., compressed or network file). - // - // Note, if a buffer for that file already exists, use :badd to - // edit that buffer, to not lose folding information (:edit resets - // folds in other buffers) - if (fprintf(fd, - "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" - // Fixup :terminal buffer name. #7836 - "if &buftype ==# 'terminal'\n" - " silent file %s\n" - "endif\n", - fname_esc, - fname_esc, - fname_esc, - fname_esc) < 0) { - xfree(fname_esc); - return FAIL; - } - } else { - // No file in this buffer, just make it empty. - PUTLINE_FAIL("enew"); - if (wp->w_buffer->b_ffname != NULL) { - // The buffer does have a name, but it's not a file name. - if (fprintf(fd, "file %s\n", fname_esc) < 0) { - xfree(fname_esc); - return FAIL; - } - } - do_cursor = false; - } - xfree(fname_esc); - } - - /* - * Local mappings and abbreviations. - */ - if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - && makemap(fd, wp->w_buffer) == FAIL) { - return FAIL; - } - - /* - * Local options. Need to go to the window temporarily. - * Store only local values when using ":mkview" and when ":mksession" is - * used and 'sessionoptions' doesn't include "nvim/options". - * Some folding options are always stored when "folds" is included, - * otherwise the folds would not be restored correctly. - */ - save_curwin = curwin; - curwin = wp; - curbuf = curwin->w_buffer; - if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) - f = makeset(fd, OPT_LOCAL, - flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); - else if (*flagp & SSOP_FOLDS) - f = makefoldset(fd); - else - f = OK; - curwin = save_curwin; - curbuf = curwin->w_buffer; - if (f == FAIL) { - return FAIL; - } - - // - // Save Folds when 'buftype' is empty and for help files. - // - if ((*flagp & SSOP_FOLDS) - && wp->w_buffer->b_ffname != NULL - && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) - ) { - if (put_folds(fd, wp) == FAIL) - return FAIL; - } - - // - // Set the cursor after creating folds, since that moves the cursor. - // - if (do_cursor) { - // Restore the cursor line in the file and relatively in the - // window. Don't use "G", it changes the jumplist. - if (fprintf(fd, - "let s:l = %" PRId64 " - ((%" PRId64 - " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" - "if s:l < 1 | let s:l = 1 | endif\n" - "exe s:l\n" - "normal! zt\n" - "%" PRId64 "\n", - (int64_t)wp->w_cursor.lnum, - (int64_t)(wp->w_cursor.lnum - wp->w_topline), - (int64_t)(wp->w_height_inner / 2), - (int64_t)wp->w_height_inner, - (int64_t)wp->w_cursor.lnum) < 0) { - return FAIL; - } - // Restore the cursor column and left offset when not wrapping. - if (wp->w_cursor.col == 0) { - PUTLINE_FAIL("normal! 0"); - } else { - if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { - if (fprintf(fd, - "let s:c = %" PRId64 " - ((%" PRId64 - " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" - "if s:c > 0\n" - " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" - "else\n", - (int64_t)wp->w_virtcol + 1, - (int64_t)(wp->w_virtcol - wp->w_leftcol), - (int64_t)(wp->w_width / 2), - (int64_t)wp->w_width, - (int64_t)wp->w_virtcol + 1) < 0 - || put_view_curpos(fd, wp, " ") == FAIL - || put_line(fd, "endif") == FAIL) { - return FAIL; - } - } else if (put_view_curpos(fd, wp, "") == FAIL) { - return FAIL; - } - } - } - - // - // Local directory, if the current flag is not view options or the "curdir" - // option is included. - // - if (wp->w_localdir != NULL - && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { - if (fputs("lcd ", fd) < 0 - || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL - || fprintf(fd, "\n") < 0) { - return FAIL; - } - did_lcd = true; - } - - return OK; -} - -/// Writes an :argument list to the session file. -/// -/// @param fd -/// @param cmd -/// @param gap -/// @param fullname true: use full path name -/// @param flagp -/// -/// @returns FAIL if writing fails. -static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, - unsigned *flagp) -{ - char_u *buf = NULL; - char_u *s; - - if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { - return FAIL; - } - for (int i = 0; i < gap->ga_len; i++) { - // NULL file names are skipped (only happens when out of memory). - s = alist_name(&((aentry_T *)gap->ga_data)[i]); - if (s != NULL) { - if (fullname) { - buf = xmalloc(MAXPATHL); - (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); - s = buf; - } - char *fname_esc = ses_escape_fname((char *)s, flagp); - if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { - xfree(fname_esc); - xfree(buf); - return FAIL; - } - xfree(fname_esc); - xfree(buf); - } - } - return OK; -} - -/// Gets the buffer name for `buf`. -static char *ses_get_fname(buf_T *buf, unsigned *flagp) -{ - // Use the short file name if the current directory is known at the time - // the session file will be sourced. - // Don't do this for ":mkview", we don't know the current directory. - // Don't do this after ":lcd", we don't keep track of what the current - // directory is. - if (buf->b_sfname != NULL - && flagp == &ssop_flags - && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) - && !p_acd - && !did_lcd) { - return (char *)buf->b_sfname; - } - return (char *)buf->b_ffname; -} - -/// Write a buffer name to the session file. -/// Also ends the line, if "add_eol" is true. -/// Returns FAIL if writing fails. -static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) -{ - char *name = ses_get_fname(buf, flagp); - if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL - || (add_eol && fprintf(fd, "\n") < 0)) { - return FAIL; - } - return OK; -} - -// Escapes a filename for session writing. -// Takes care of "slash" flag in 'sessionoptions' and escapes special -// characters. -// -// Returns allocated string or NULL. -static char *ses_escape_fname(char *name, unsigned *flagp) -{ - char *p; - char *sname = (char *)home_replace_save(NULL, (char_u *)name); - - // Always SSOP_SLASH: change all backslashes to forward slashes. - for (p = sname; *p != NUL; MB_PTR_ADV(p)) { - if (*p == '\\') { - *p = '/'; - } - } - - // Escape special characters. - p = vim_strsave_fnameescape(sname, false); - xfree(sname); - return p; -} - -// Write a file name to the session file. -// Takes care of the "slash" option in 'sessionoptions' and escapes special -// characters. -// Returns FAIL if writing fails. -static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) -{ - char *p = ses_escape_fname((char *)name, flagp); - bool retval = fputs(p, fd) < 0 ? FAIL : OK; - xfree(p); - return retval; -} - -/* - * ":loadview [nr]" - */ -static void ex_loadview(exarg_T *eap) -{ - char *fname = get_view_file(*eap->arg); - if (fname != NULL) { - if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { - EMSG2(_(e_notopen), fname); - } - xfree(fname); - } -} - -/// Get the name of the view file for the current buffer. -static char *get_view_file(int c) -{ - if (curbuf->b_ffname == NULL) { - EMSG(_(e_noname)); - return NULL; - } - char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); - - // We want a file name without separators, because we're not going to make - // a directory. - // "normal" path separator -> "=+" - // "=" -> "==" - // ":" path separator -> "=-" - size_t len = 0; - for (char *p = sname; *p; p++) { - if (*p == '=' || vim_ispathsep(*p)) { - ++len; - } - } - char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); - STRCPY(retval, p_vdir); - add_pathsep(retval); - char *s = retval + strlen(retval); - for (char *p = sname; *p; p++) { - if (*p == '=') { - *s++ = '='; - *s++ = '='; - } else if (vim_ispathsep(*p)) { - *s++ = '='; -#if defined(BACKSLASH_IN_FILENAME) - if (*p == ':') - *s++ = '-'; - else -#endif - *s++ = '+'; - } else - *s++ = *p; - } - *s++ = '='; - *s++ = c; - strcpy(s, ".vim"); - - xfree(sname); - return retval; -} - - -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. -int put_eol(FILE *fd) -{ - if (putc('\n', fd) < 0) { - return FAIL; - } - return OK; -} - -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. -int put_line(FILE *fd, char *s) -{ - if (fprintf(fd, "%s\n", s) < 0) { - return FAIL; - } - return OK; -} - /* * ":rshada" and ":wshada". */ diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c new file mode 100644 index 0000000000..5b0a432215 --- /dev/null +++ b/src/nvim/ex_session.c @@ -0,0 +1,1034 @@ +// 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 + +// Functions for creating a session file, i.e. implementing: +// :mkexrc +// :mkvimrc +// :mkview +// :mksession + +#include +#include +#include +#include +#include + +#include "nvim/vim.h" +#include "nvim/globals.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/cursor.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/ex_session.h" +#include "nvim/file_search.h" +#include "nvim/fileio.h" +#include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/keymap.h" +#include "nvim/misc1.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/path.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ex_session.c.generated.h" +#endif + +/// Whether ":lcd" or ":tcd" was produced for a session. +static int did_lcd; + +#define PUTLINE_FAIL(s) \ + do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0) + +static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces) +{ + int r; + + if (wp->w_curswant == MAXCOL) { + r = fprintf(fd, "%snormal! $\n", spaces); + } else { + r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1); + } + return r >= 0; +} + +static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) +{ + int n = 0; + win_T *wp; + + if (restore_size && (ssop_flags & SSOP_WINSIZE)) { + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) { + continue; + } + n++; + + // restore height when not full height + if (wp->w_height + wp->w_status_height < topframe->fr_height + && (fprintf(fd, + "exe '%dresize ' . ((&lines * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")\n", + n, (int64_t)wp->w_height, + (int64_t)Rows / 2, (int64_t)Rows) < 0)) { + return FAIL; + } + + // restore width when not full width + if (wp->w_width < Columns + && (fprintf(fd, + "exe 'vert %dresize ' . ((&columns * %" PRId64 + " + %" PRId64 ") / %" PRId64 ")\n", + n, (int64_t)wp->w_width, (int64_t)Columns / 2, + (int64_t)Columns) < 0)) { + return FAIL; + } + } + } else { + // Just equalize window sizes. + PUTLINE_FAIL("wincmd ="); + } + return OK; +} + +// Write commands to "fd" to recursively create windows for frame "fr", +// horizontally and vertically split. +// After the commands the last window in the frame is the current window. +// Returns FAIL when writing the commands to "fd" fails. +static int ses_win_rec(FILE *fd, frame_T *fr) +{ + frame_T *frc; + int count = 0; + + if (fr->fr_layout != FR_LEAF) { + // Find first frame that's not skipped and then create a window for + // each following one (first frame is already there). + frc = ses_skipframe(fr->fr_child); + if (frc != NULL) + while ((frc = ses_skipframe(frc->fr_next)) != NULL) { + // Make window as big as possible so that we have lots of room + // to split. + if (fprintf(fd, "%s%s", + "wincmd _ | wincmd |\n", + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { + return FAIL; + } + count++; + } + + // Go back to the first window. + if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL + ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { + return FAIL; + } + + // Recursively create frames/windows in each window of this column or row. + frc = ses_skipframe(fr->fr_child); + while (frc != NULL) { + ses_win_rec(fd, frc); + frc = ses_skipframe(frc->fr_next); + // Go to next window. + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { + return FAIL; + } + } + } + return OK; +} + +// Skip frames that don't contain windows we want to save in the Session. +// Returns NULL when there none. +static frame_T *ses_skipframe(frame_T *fr) +{ + frame_T *frc; + + FOR_ALL_FRAMES(frc, fr) { + if (ses_do_frame(frc)) { + break; + } + } + return frc; +} + +// Return true if frame "fr" has a window somewhere that we want to save in +// the Session. +static bool ses_do_frame(const frame_T *fr) + FUNC_ATTR_NONNULL_ARG(1) +{ + const frame_T *frc; + + if (fr->fr_layout == FR_LEAF) { + return ses_do_win(fr->fr_win); + } + FOR_ALL_FRAMES(frc, fr->fr_child) { + if (ses_do_frame(frc)) { + return true; + } + } + return false; +} + +/// Return non-zero if window "wp" is to be stored in the Session. +static int ses_do_win(win_T *wp) +{ + if (wp->w_buffer->b_fname == NULL + // When 'buftype' is "nofile" can't restore the window contents. + || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { + return ssop_flags & SSOP_BLANK; + } + if (bt_help(wp->w_buffer)) { + return ssop_flags & SSOP_HELP; + } + return true; +} + +/// Writes an :argument list to the session file. +/// +/// @param fd +/// @param cmd +/// @param gap +/// @param fullname true: use full path name +/// @param flagp +/// +/// @returns FAIL if writing fails. +static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, + unsigned *flagp) +{ + char_u *buf = NULL; + char_u *s; + + if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { + return FAIL; + } + for (int i = 0; i < gap->ga_len; i++) { + // NULL file names are skipped (only happens when out of memory). + s = alist_name(&((aentry_T *)gap->ga_data)[i]); + if (s != NULL) { + if (fullname) { + buf = xmalloc(MAXPATHL); + (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); + s = buf; + } + char *fname_esc = ses_escape_fname((char *)s, flagp); + if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { + xfree(fname_esc); + xfree(buf); + return FAIL; + } + xfree(fname_esc); + xfree(buf); + } + } + return OK; +} + +/// Gets the buffer name for `buf`. +static char *ses_get_fname(buf_T *buf, unsigned *flagp) +{ + // Use the short file name if the current directory is known at the time + // the session file will be sourced. + // Don't do this for ":mkview", we don't know the current directory. + // Don't do this after ":lcd", we don't keep track of what the current + // directory is. + if (buf->b_sfname != NULL + && flagp == &ssop_flags + && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) + && !p_acd + && !did_lcd) { + return (char *)buf->b_sfname; + } + return (char *)buf->b_ffname; +} + +/// Write a buffer name to the session file. +/// Also ends the line, if "add_eol" is true. +/// Returns FAIL if writing fails. +static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) +{ + char *name = ses_get_fname(buf, flagp); + if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL + || (add_eol && fprintf(fd, "\n") < 0)) { + return FAIL; + } + return OK; +} + +// Escapes a filename for session writing. +// Takes care of "slash" flag in 'sessionoptions' and escapes special +// characters. +// +// Returns allocated string or NULL. +static char *ses_escape_fname(char *name, unsigned *flagp) +{ + char *p; + char *sname = (char *)home_replace_save(NULL, (char_u *)name); + + // Always SSOP_SLASH: change all backslashes to forward slashes. + for (p = sname; *p != NUL; MB_PTR_ADV(p)) { + if (*p == '\\') { + *p = '/'; + } + } + + // Escape special characters. + p = vim_strsave_fnameescape(sname, false); + xfree(sname); + return p; +} + +// Write a file name to the session file. +// Takes care of the "slash" option in 'sessionoptions' and escapes special +// characters. +// Returns FAIL if writing fails. +static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +{ + char *p = ses_escape_fname((char *)name, flagp); + bool retval = fputs(p, fd) < 0 ? FAIL : OK; + xfree(p); + return retval; +} + +// Write commands to "fd" to restore the view of a window. +// Caller must make sure 'scrolloff' is zero. +static int put_view( + FILE *fd, + win_T *wp, + int add_edit, /* add ":edit" command to view */ + unsigned *flagp, /* vop_flags or ssop_flags */ + int current_arg_idx /* current argument index of the window, use + * -1 if unknown */ +) +{ + win_T *save_curwin; + int f; + int do_cursor; + int did_next = FALSE; + + /* Always restore cursor position for ":mksession". For ":mkview" only + * when 'viewoptions' contains "cursor". */ + do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); + + /* + * Local argument list. + */ + if (wp->w_alist == &global_alist) { + PUTLINE_FAIL("argglobal"); + } else { + if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, + flagp == &vop_flags + || !(*flagp & SSOP_CURDIR) + || wp->w_localdir != NULL, flagp) == FAIL) { + return FAIL; + } + } + + /* Only when part of a session: restore the argument index. Some + * arguments may have been deleted, check if the index is valid. */ + if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) + && flagp == &ssop_flags) { + if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { + return FAIL; + } + did_next = true; + } + + // Edit the file. Skip this when ":next" already did it. + if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { + char *fname_esc = + ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); + // + // Load the file. + // + if (wp->w_buffer->b_ffname != NULL + && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal) + ) { + // Editing a file in this buffer: use ":edit file". + // This may have side effects! (e.g., compressed or network file). + // + // Note, if a buffer for that file already exists, use :badd to + // edit that buffer, to not lose folding information (:edit resets + // folds in other buffers) + if (fprintf(fd, + "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" + // Fixup :terminal buffer name. #7836 + "if &buftype ==# 'terminal'\n" + " silent file %s\n" + "endif\n", + fname_esc, + fname_esc, + fname_esc, + fname_esc) < 0) { + xfree(fname_esc); + return FAIL; + } + } else { + // No file in this buffer, just make it empty. + PUTLINE_FAIL("enew"); + if (wp->w_buffer->b_ffname != NULL) { + // The buffer does have a name, but it's not a file name. + if (fprintf(fd, "file %s\n", fname_esc) < 0) { + xfree(fname_esc); + return FAIL; + } + } + do_cursor = false; + } + xfree(fname_esc); + } + + /* + * Local mappings and abbreviations. + */ + if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + && makemap(fd, wp->w_buffer) == FAIL) { + return FAIL; + } + + /* + * Local options. Need to go to the window temporarily. + * Store only local values when using ":mkview" and when ":mksession" is + * used and 'sessionoptions' doesn't include "nvim/options". + * Some folding options are always stored when "folds" is included, + * otherwise the folds would not be restored correctly. + */ + save_curwin = curwin; + curwin = wp; + curbuf = curwin->w_buffer; + if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + f = makeset(fd, OPT_LOCAL, + flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); + else if (*flagp & SSOP_FOLDS) + f = makefoldset(fd); + else + f = OK; + curwin = save_curwin; + curbuf = curwin->w_buffer; + if (f == FAIL) { + return FAIL; + } + + // + // Save Folds when 'buftype' is empty and for help files. + // + if ((*flagp & SSOP_FOLDS) + && wp->w_buffer->b_ffname != NULL + && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) + ) { + if (put_folds(fd, wp) == FAIL) + return FAIL; + } + + // + // Set the cursor after creating folds, since that moves the cursor. + // + if (do_cursor) { + // Restore the cursor line in the file and relatively in the + // window. Don't use "G", it changes the jumplist. + if (fprintf(fd, + "let s:l = %" PRId64 " - ((%" PRId64 + " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:l < 1 | let s:l = 1 | endif\n" + "exe s:l\n" + "normal! zt\n" + "%" PRId64 "\n", + (int64_t)wp->w_cursor.lnum, + (int64_t)(wp->w_cursor.lnum - wp->w_topline), + (int64_t)(wp->w_height_inner / 2), + (int64_t)wp->w_height_inner, + (int64_t)wp->w_cursor.lnum) < 0) { + return FAIL; + } + // Restore the cursor column and left offset when not wrapping. + if (wp->w_cursor.col == 0) { + PUTLINE_FAIL("normal! 0"); + } else { + if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { + if (fprintf(fd, + "let s:c = %" PRId64 " - ((%" PRId64 + " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n" + "if s:c > 0\n" + " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n" + "else\n", + (int64_t)wp->w_virtcol + 1, + (int64_t)(wp->w_virtcol - wp->w_leftcol), + (int64_t)(wp->w_width / 2), + (int64_t)wp->w_width, + (int64_t)wp->w_virtcol + 1) < 0 + || put_view_curpos(fd, wp, " ") == FAIL + || put_line(fd, "endif") == FAIL) { + return FAIL; + } + } else if (put_view_curpos(fd, wp, "") == FAIL) { + return FAIL; + } + } + } + + // + // Local directory, if the current flag is not view options or the "curdir" + // option is included. + // + if (wp->w_localdir != NULL + && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { + if (fputs("lcd ", fd) < 0 + || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL + || fprintf(fd, "\n") < 0) { + return FAIL; + } + did_lcd = true; + } + + return OK; +} + +/// Writes commands for restoring the current buffers, for :mksession. +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// +/// @param dirnow Current directory name +/// @param fd File descriptor to write to +/// +/// @return FAIL on error, OK otherwise. +static int makeopens(FILE *fd, char_u *dirnow) +{ + int only_save_windows = TRUE; + int nr; + int restore_size = true; + win_T *wp; + char_u *sname; + win_T *edited_win = NULL; + int tabnr; + win_T *tab_firstwin; + frame_T *tab_topframe; + int cur_arg_idx = 0; + int next_arg_idx = 0; + + if (ssop_flags & SSOP_BUFFERS) + only_save_windows = FALSE; /* Save ALL buffers */ + + // Begin by setting v:this_session, and then other sessionable variables. + PUTLINE_FAIL("let v:this_session=expand(\":p\")"); + if (ssop_flags & SSOP_GLOBALS) { + if (store_session_globals(fd) == FAIL) { + return FAIL; + } + } + + // Close all windows but one. + PUTLINE_FAIL("silent only"); + + // + // Now a :cd command to the session directory or the current directory + // + if (ssop_flags & SSOP_SESDIR) { + PUTLINE_FAIL("exe \"cd \" . escape(expand(\":p:h\"), ' ')"); + } else if (ssop_flags & SSOP_CURDIR) { + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); + char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); + if (fprintf(fd, "cd %s\n", fname_esc) < 0) { + xfree(fname_esc); + xfree(sname); + return FAIL; + } + xfree(fname_esc); + xfree(sname); + } + + if (fprintf(fd, + "%s", + // If there is an empty, unnamed buffer we will wipe it out later. + // Remember the buffer number. + "if expand('%') == '' && !&modified && line('$') <= 1" + " && getline(1) == ''\n" + " let s:wipebuf = bufnr('%')\n" + "endif\n" + // Now save the current files, current buffer first. + "set shortmess=aoO\n") < 0) { + return FAIL; + } + + // Now put the other buffers into the buffer list. + FOR_ALL_BUFFERS(buf) { + if (!(only_save_windows && buf->b_nwindows == 0) + && !(buf->b_help && !(ssop_flags & SSOP_HELP)) + && buf->b_fname != NULL + && buf->b_p_bl) { + if (fprintf(fd, "badd +%" PRId64 " ", + buf->b_wininfo == NULL + ? (int64_t)1L + : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { + return FAIL; + } + } + } + + /* the global argument list */ + if (ses_arglist(fd, "argglobal", &global_alist.al_ga, + !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { + return FAIL; + } + + if (ssop_flags & SSOP_RESIZE) { + // Note: after the restore we still check it worked! + if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n", + (int64_t)Rows, (int64_t)Columns) < 0) { + return FAIL; + } + } + + int restore_stal = FALSE; + // When there are two or more tabpages and 'showtabline' is 1 the tabline + // will be displayed when creating the next tab. That resizes the windows + // in the first tab, which may cause problems. Set 'showtabline' to 2 + // temporarily to avoid that. + if (p_stal == 1 && first_tabpage->tp_next != NULL) { + PUTLINE_FAIL("set stal=2"); + restore_stal = true; + } + + // + // For each tab: + // - Put windows for each tab, when "tabpages" is in 'sessionoptions'. + // - Don't use goto_tabpage(), it may change CWD and trigger autocommands. + // + tab_firstwin = firstwin; // First window in tab page "tabnr". + tab_topframe = topframe; + for (tabnr = 1;; tabnr++) { + tabpage_T *tp = find_tabpage(tabnr); + if (tp == NULL) { + break; // done all tab pages + } + + int need_tabnew = false; + int cnr = 1; + + if ((ssop_flags & SSOP_TABPAGES)) { + if (tp == curtab) { + tab_firstwin = firstwin; + tab_topframe = topframe; + } else { + tab_firstwin = tp->tp_firstwin; + tab_topframe = tp->tp_topframe; + } + if (tabnr > 1) + need_tabnew = TRUE; + } + + // + // Before creating the window layout, try loading one file. If this + // is aborted we don't end up with a number of useless windows. + // This may have side effects! (e.g., compressed or network file). + // + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp) + && wp->w_buffer->b_ffname != NULL + && !bt_help(wp->w_buffer) + && !bt_nofile(wp->w_buffer) + ) { + if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 + || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) { + return FAIL; + } + need_tabnew = false; + if (!wp->w_arg_idx_invalid) { + edited_win = wp; + } + break; + } + } + + // If no file got edited create an empty tab page. + if (need_tabnew && put_line(fd, "tabnew") == FAIL) { + return FAIL; + } + + // + // Save current window layout. + // + PUTLINE_FAIL("set splitbelow splitright"); + if (ses_win_rec(fd, tab_topframe) == FAIL) { + return FAIL; + } + if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) { + return FAIL; + } + if (!p_spr && put_line(fd, "set nosplitright") == FAIL) { + return FAIL; + } + + // + // Check if window sizes can be restored (no windows omitted). + // Remember the window number of the current window after restoring. + // + nr = 0; + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp)) + ++nr; + else + restore_size = FALSE; + if (curwin == wp) + cnr = nr; + } + + // Go to the first window. + PUTLINE_FAIL("wincmd t"); + + // If more than one window, see if sizes can be restored. + // First set 'winheight' and 'winwidth' to 1 to avoid the windows being + // resized when moving between windows. + // Do this before restoring the view, so that the topline and the + // cursor can be set. This is done again below. + // winminheight and winminwidth need to be set to avoid an error if the + // user has set winheight or winwidth. + if (fprintf(fd, + "set winminheight=0\n" + "set winheight=1\n" + "set winminwidth=0\n" + "set winwidth=1\n") < 0) { + return FAIL; + } + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { + return FAIL; + } + + // + // Restore the view of the window (options, file, cursor, etc.). + // + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) { + continue; + } + if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx) + == FAIL) { + return FAIL; + } + if (nr > 1 && put_line(fd, "wincmd w") == FAIL) { + return FAIL; + } + next_arg_idx = wp->w_arg_idx; + } + + // The argument index in the first tab page is zero, need to set it in + // each window. For further tab pages it's the window where we do + // "tabedit". + cur_arg_idx = next_arg_idx; + + // + // Restore cursor to the current window if it's not the first one. + // + if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) { + return FAIL; + } + + // + // Restore window sizes again after jumping around in windows, because + // the current window has a minimum size while others may not. + // + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) { + return FAIL; + } + + // Take care of tab-local working directories if applicable + if (tp->tp_localdir) { + if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 + || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL + || fputs(" | endif\n", fd) < 0) { + return FAIL; + } + did_lcd = true; + } + + // Don't continue in another tab page when doing only the current one + // or when at the last tab page. + if (!(ssop_flags & SSOP_TABPAGES)) { + break; + } + } + + if (ssop_flags & SSOP_TABPAGES) { + if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) { + return FAIL; + } + } + if (restore_stal && put_line(fd, "set stal=1") == FAIL) { + return FAIL; + } + + // + // Wipe out an empty unnamed buffer we started in. + // + if (fprintf(fd, "%s", + "if exists('s:wipebuf') " + "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n" + " silent exe 'bwipe ' . s:wipebuf\n" + "endif\n" + "unlet! s:wipebuf\n") < 0) { + return FAIL; + } + + // Re-apply options. + if (fprintf(fd, + "set winheight=%" PRId64 " winwidth=%" PRId64 + " winminheight=%" PRId64 " winminwidth=%" PRId64 + " shortmess=%s\n", + (int64_t)p_wh, + (int64_t)p_wiw, + (int64_t)p_wmh, + (int64_t)p_wmw, + p_shm) < 0) { + return FAIL; + } + + // + // Lastly, execute the x.vim file if it exists. + // + if (fprintf(fd, "%s", + "let s:sx = expand(\":p:r\").\"x.vim\"\n" + "if file_readable(s:sx)\n" + " exe \"source \" . fnameescape(s:sx)\n" + "endif\n") < 0) { + return FAIL; + } + + return OK; +} + +/// ":loadview [nr]" +void ex_loadview(exarg_T *eap) +{ + char *fname = get_view_file(*eap->arg); + if (fname != NULL) { + if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { + EMSG2(_(e_notopen), fname); + } + xfree(fname); + } +} + +/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". +/// +/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled. +/// - SSOP_UNIX: line-endings are always LF +/// - SSOP_SLASH: filenames are always written with "/" slash +void ex_mkrc(exarg_T *eap) +{ + FILE *fd; + int failed = false; + int view_session = false; // :mkview, :mksession + int using_vdir = false; // using 'viewdir'? + char *viewFile = NULL; + unsigned *flagp; + + if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { + view_session = TRUE; + } + + /* Use the short file name until ":lcd" is used. We also don't use the + * short file name when 'acd' is set, that is checked later. */ + did_lcd = FALSE; + + char *fname; + // ":mkview" or ":mkview 9": generate file name with 'viewdir' + if (eap->cmdidx == CMD_mkview + && (*eap->arg == NUL + || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) { + eap->forceit = true; + fname = get_view_file(*eap->arg); + if (fname == NULL) { + return; + } + viewFile = fname; + using_vdir = true; + } else if (*eap->arg != NUL) { + fname = (char *) eap->arg; + } else if (eap->cmdidx == CMD_mkvimrc) { + fname = VIMRC_FILE; + } else if (eap->cmdidx == CMD_mksession) { + fname = SESSION_FILE; + } else { + fname = EXRC_FILE; + } + + /* When using 'viewdir' may have to create the directory. */ + if (using_vdir && !os_isdir(p_vdir)) { + vim_mkdir_emsg((const char *)p_vdir, 0755); + } + + fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); + if (fd != NULL) { + if (eap->cmdidx == CMD_mkview) + flagp = &vop_flags; + else + flagp = &ssop_flags; + + // Write the version command for :mkvimrc + if (eap->cmdidx == CMD_mkvimrc) { + (void)put_line(fd, "version 6.0"); + } + + if (eap->cmdidx == CMD_mksession) { + if (put_line(fd, "let SessionLoad = 1") == FAIL) + failed = TRUE; + } + + if (!view_session || (eap->cmdidx == CMD_mksession + && (*flagp & SSOP_OPTIONS))) { + failed |= (makemap(fd, NULL) == FAIL + || makeset(fd, OPT_GLOBAL, false) == FAIL); + } + + if (!failed && view_session) { + if (put_line(fd, + "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") + == FAIL) + failed = TRUE; + if (eap->cmdidx == CMD_mksession) { + char_u *dirnow; /* current directory */ + + dirnow = xmalloc(MAXPATHL); + /* + * Change to session file's dir. + */ + if (os_dirname(dirnow, MAXPATHL) == FAIL + || os_chdir((char *)dirnow) != 0) + *dirnow = NUL; + if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { + if (vim_chdirfile((char_u *) fname) == OK) { + shorten_fnames(true); + } + } else if (*dirnow != NUL + && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { + if (os_chdir((char *)globaldir) == 0) + shorten_fnames(TRUE); + } + + failed |= (makeopens(fd, dirnow) == FAIL); + + /* restore original dir */ + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (os_chdir((char *)dirnow) != 0) + EMSG(_(e_prev_dir)); + shorten_fnames(TRUE); + /* restore original dir */ + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (os_chdir((char *)dirnow) != 0) + EMSG(_(e_prev_dir)); + shorten_fnames(TRUE); + } + } + xfree(dirnow); + } else { + failed |= (put_view(fd, curwin, !using_vdir, flagp, + -1) == FAIL); + } + if (fprintf(fd, + "%s", + "let &so = s:so_save | let &siso = s:siso_save\n" + "doautoall SessionLoadPost\n") + < 0) { + failed = true; + } + if (eap->cmdidx == CMD_mksession) { + if (fprintf(fd, "unlet SessionLoad\n") < 0) { + failed = true; + } + } + } + if (put_line(fd, "\" vim: set ft=vim :") == FAIL) + failed = TRUE; + + failed |= fclose(fd); + + if (failed) { + EMSG(_(e_write)); + } else if (eap->cmdidx == CMD_mksession) { + // successful session write - set v:this_session + char *const tbuf = xmalloc(MAXPATHL); + if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) { + set_vim_var_string(VV_THIS_SESSION, tbuf, -1); + } + xfree(tbuf); + } + } + + xfree(viewFile); +} + +/// Get the name of the view file for the current buffer. +static char *get_view_file(int c) +{ + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return NULL; + } + char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); + + // We want a file name without separators, because we're not going to make + // a directory. + // "normal" path separator -> "=+" + // "=" -> "==" + // ":" path separator -> "=-" + size_t len = 0; + for (char *p = sname; *p; p++) { + if (*p == '=' || vim_ispathsep(*p)) { + ++len; + } + } + char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); + STRCPY(retval, p_vdir); + add_pathsep(retval); + char *s = retval + strlen(retval); + for (char *p = sname; *p; p++) { + if (*p == '=') { + *s++ = '='; + *s++ = '='; + } else if (vim_ispathsep(*p)) { + *s++ = '='; +#if defined(BACKSLASH_IN_FILENAME) + if (*p == ':') + *s++ = '-'; + else +#endif + *s++ = '+'; + } else + *s++ = *p; + } + *s++ = '='; + assert(c >= CHAR_MIN && c <= CHAR_MAX); + *s++ = (char)c; + strcpy(s, ".vim"); + + xfree(sname); + return retval; +} + +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +int put_eol(FILE *fd) +{ + if (putc('\n', fd) < 0) { + return FAIL; + } + return OK; +} + +// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +int put_line(FILE *fd, char *s) +{ + if (fprintf(fd, "%s\n", s) < 0) { + return FAIL; + } + return OK; +} diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h new file mode 100644 index 0000000000..8d3ea5b91a --- /dev/null +++ b/src/nvim/ex_session.h @@ -0,0 +1,13 @@ +#ifndef NVIM_EX_SESSION_H +#define NVIM_EX_SESSION_H + +#include + +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ex_session.h.generated.h" +#endif + +#endif // NVIM_EX_SESSION_H + diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 0b14a6affb..331d7f9e29 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -19,6 +19,7 @@ #include "nvim/diff.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_session.h" #include "nvim/func_attr.h" #include "nvim/indent.h" #include "nvim/buffer_updates.h" diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 81666bf5d6..419c6328ee 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -41,6 +41,7 @@ #include "nvim/option.h" #include "nvim/regexp.h" #include "nvim/screen.h" +#include "nvim/ex_session.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/ui.h" diff --git a/src/nvim/option.c b/src/nvim/option.c index 52742c8b64..cecfd7146c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -66,6 +66,7 @@ #include "nvim/path.h" #include "nvim/popupmnu.h" #include "nvim/regexp.h" +#include "nvim/ex_session.h" #include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/spellfile.h" -- cgit From a4b9417c78c40e0e6191de30a6626675297f2899 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 28 Jan 2020 00:16:36 -0800 Subject: lint --- src/nvim/ex_session.c | 183 +++++++++++++++++++++++++++----------------------- 1 file changed, 98 insertions(+), 85 deletions(-) (limited to 'src') diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 5b0a432215..b8f2927480 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -112,7 +112,7 @@ static int ses_win_rec(FILE *fd, frame_T *fr) // Find first frame that's not skipped and then create a window for // each following one (first frame is already there). frc = ses_skipframe(fr->fr_child); - if (frc != NULL) + if (frc != NULL) { while ((frc = ses_skipframe(frc->fr_next)) != NULL) { // Make window as big as possible so that we have lots of room // to split. @@ -123,6 +123,7 @@ static int ses_win_rec(FILE *fd, frame_T *fr) } count++; } + } // Go back to the first window. if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL @@ -214,7 +215,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, if (s != NULL) { if (fullname) { buf = xmalloc(MAXPATHL); - (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE); + (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, false); s = buf; } char *fname_esc = ses_escape_fname((char *)s, flagp); @@ -301,24 +302,23 @@ static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) static int put_view( FILE *fd, win_T *wp, - int add_edit, /* add ":edit" command to view */ - unsigned *flagp, /* vop_flags or ssop_flags */ - int current_arg_idx /* current argument index of the window, use - * -1 if unknown */ -) + int add_edit, // add ":edit" command to view + unsigned *flagp, // vop_flags or ssop_flags + int current_arg_idx // current argument index of the window, use +) // -1 if unknown { win_T *save_curwin; int f; int do_cursor; - int did_next = FALSE; + int did_next = false; - /* Always restore cursor position for ":mksession". For ":mkview" only - * when 'viewoptions' contains "cursor". */ + // Always restore cursor position for ":mksession". For ":mkview" only + // when 'viewoptions' contains "cursor". do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); - /* - * Local argument list. - */ + // + // Local argument list. + // if (wp->w_alist == &global_alist) { PUTLINE_FAIL("argglobal"); } else { @@ -330,8 +330,8 @@ static int put_view( } } - /* Only when part of a session: restore the argument index. Some - * arguments may have been deleted, check if the index is valid. */ + // Only when part of a session: restore the argument index. Some + // arguments may have been deleted, check if the index is valid. if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) && flagp == &ssop_flags) { if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) { @@ -384,31 +384,32 @@ static int put_view( xfree(fname_esc); } - /* - * Local mappings and abbreviations. - */ + // + // Local mappings and abbreviations. + // if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) && makemap(fd, wp->w_buffer) == FAIL) { return FAIL; } - /* - * Local options. Need to go to the window temporarily. - * Store only local values when using ":mkview" and when ":mksession" is - * used and 'sessionoptions' doesn't include "nvim/options". - * Some folding options are always stored when "folds" is included, - * otherwise the folds would not be restored correctly. - */ + // + // Local options. Need to go to the window temporarily. + // Store only local values when using ":mkview" and when ":mksession" is + // used and 'sessionoptions' doesn't include "nvim/options". + // Some folding options are always stored when "folds" is included, + // otherwise the folds would not be restored correctly. + // save_curwin = curwin; curwin = wp; curbuf = curwin->w_buffer; - if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) { f = makeset(fd, OPT_LOCAL, - flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); - else if (*flagp & SSOP_FOLDS) + flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); + } else if (*flagp & SSOP_FOLDS) { f = makefoldset(fd); - else + } else { f = OK; + } curwin = save_curwin; curbuf = curwin->w_buffer; if (f == FAIL) { @@ -422,8 +423,9 @@ static int put_view( && wp->w_buffer->b_ffname != NULL && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer)) ) { - if (put_folds(fd, wp) == FAIL) + if (put_folds(fd, wp) == FAIL) { return FAIL; + } } // @@ -499,7 +501,7 @@ static int put_view( /// @return FAIL on error, OK otherwise. static int makeopens(FILE *fd, char_u *dirnow) { - int only_save_windows = TRUE; + int only_save_windows = true; int nr; int restore_size = true; win_T *wp; @@ -511,8 +513,9 @@ static int makeopens(FILE *fd, char_u *dirnow) int cur_arg_idx = 0; int next_arg_idx = 0; - if (ssop_flags & SSOP_BUFFERS) - only_save_windows = FALSE; /* Save ALL buffers */ + if (ssop_flags & SSOP_BUFFERS) { + only_save_windows = false; // Save ALL buffers + } // Begin by setting v:this_session, and then other sessionable variables. PUTLINE_FAIL("let v:this_session=expand(\":p\")"); @@ -571,7 +574,7 @@ static int makeopens(FILE *fd, char_u *dirnow) } } - /* the global argument list */ + // the global argument list if (ses_arglist(fd, "argglobal", &global_alist.al_ga, !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { return FAIL; @@ -585,7 +588,7 @@ static int makeopens(FILE *fd, char_u *dirnow) } } - int restore_stal = FALSE; + int restore_stal = false; // When there are two or more tabpages and 'showtabline' is 1 the tabline // will be displayed when creating the next tab. That resizes the windows // in the first tab, which may cause problems. Set 'showtabline' to 2 @@ -619,8 +622,9 @@ static int makeopens(FILE *fd, char_u *dirnow) tab_firstwin = tp->tp_firstwin; tab_topframe = tp->tp_topframe; } - if (tabnr > 1) - need_tabnew = TRUE; + if (tabnr > 1) { + need_tabnew = true; + } } // @@ -671,12 +675,14 @@ static int makeopens(FILE *fd, char_u *dirnow) // nr = 0; for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { - if (ses_do_win(wp)) - ++nr; - else - restore_size = FALSE; - if (curwin == wp) + if (ses_do_win(wp)) { + nr++; + } else { + restore_size = false; + } + if (curwin == wp) { cnr = nr; + } } // Go to the first window. @@ -807,7 +813,7 @@ void ex_loadview(exarg_T *eap) { char *fname = get_view_file(*eap->arg); if (fname != NULL) { - if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) { + if (do_source((char_u *)fname, false, DOSO_NONE) == FAIL) { EMSG2(_(e_notopen), fname); } xfree(fname); @@ -829,12 +835,12 @@ void ex_mkrc(exarg_T *eap) unsigned *flagp; if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { - view_session = TRUE; + view_session = true; } - /* Use the short file name until ":lcd" is used. We also don't use the - * short file name when 'acd' is set, that is checked later. */ - did_lcd = FALSE; + // Use the short file name until ":lcd" is used. We also don't use the + // short file name when 'acd' is set, that is checked later. + did_lcd = false; char *fname; // ":mkview" or ":mkview 9": generate file name with 'viewdir' @@ -849,7 +855,7 @@ void ex_mkrc(exarg_T *eap) viewFile = fname; using_vdir = true; } else if (*eap->arg != NUL) { - fname = (char *) eap->arg; + fname = (char *)eap->arg; } else if (eap->cmdidx == CMD_mkvimrc) { fname = VIMRC_FILE; } else if (eap->cmdidx == CMD_mksession) { @@ -858,17 +864,18 @@ void ex_mkrc(exarg_T *eap) fname = EXRC_FILE; } - /* When using 'viewdir' may have to create the directory. */ + // When using 'viewdir' may have to create the directory. if (using_vdir && !os_isdir(p_vdir)) { vim_mkdir_emsg((const char *)p_vdir, 0755); } - fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN); + fd = open_exfile((char_u *)fname, eap->forceit, WRITEBIN); if (fd != NULL) { - if (eap->cmdidx == CMD_mkview) + if (eap->cmdidx == CMD_mkview) { flagp = &vop_flags; - else + } else { flagp = &ssop_flags; + } // Write the version command for :mkvimrc if (eap->cmdidx == CMD_mkvimrc) { @@ -876,8 +883,9 @@ void ex_mkrc(exarg_T *eap) } if (eap->cmdidx == CMD_mksession) { - if (put_line(fd, "let SessionLoad = 1") == FAIL) - failed = TRUE; + if (put_line(fd, "let SessionLoad = 1") == FAIL) { + failed = true; + } } if (!view_session || (eap->cmdidx == CMD_mksession @@ -888,51 +896,55 @@ void ex_mkrc(exarg_T *eap) if (!failed && view_session) { if (put_line(fd, - "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") - == FAIL) - failed = TRUE; + "let s:so_save = &so | let s:siso_save = &siso" + " | set so=0 siso=0") == FAIL) { + failed = true; + } if (eap->cmdidx == CMD_mksession) { - char_u *dirnow; /* current directory */ + char_u *dirnow; // current directory dirnow = xmalloc(MAXPATHL); - /* - * Change to session file's dir. - */ + // + // Change to session file's dir. + // if (os_dirname(dirnow, MAXPATHL) == FAIL - || os_chdir((char *)dirnow) != 0) + || os_chdir((char *)dirnow) != 0) { *dirnow = NUL; + } if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { - if (vim_chdirfile((char_u *) fname) == OK) { + if (vim_chdirfile((char_u *)fname) == OK) { shorten_fnames(true); } } else if (*dirnow != NUL && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { - if (os_chdir((char *)globaldir) == 0) - shorten_fnames(TRUE); + if (os_chdir((char *)globaldir) == 0) { + shorten_fnames(true); + } } failed |= (makeopens(fd, dirnow) == FAIL); - /* restore original dir */ + // restore original dir if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) || ((ssop_flags & SSOP_CURDIR) && globaldir != NULL))) { - if (os_chdir((char *)dirnow) != 0) + if (os_chdir((char *)dirnow) != 0) { EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); - /* restore original dir */ + } + shorten_fnames(true); + // restore original dir if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) || ((ssop_flags & SSOP_CURDIR) && globaldir != NULL))) { - if (os_chdir((char *)dirnow) != 0) + if (os_chdir((char *)dirnow) != 0) { EMSG(_(e_prev_dir)); - shorten_fnames(TRUE); + } + shorten_fnames(true); } } xfree(dirnow); } else { - failed |= (put_view(fd, curwin, !using_vdir, flagp, - -1) == FAIL); + failed |= (put_view(fd, curwin, !using_vdir, flagp, -1) == FAIL); } if (fprintf(fd, "%s", @@ -947,8 +959,9 @@ void ex_mkrc(exarg_T *eap) } } } - if (put_line(fd, "\" vim: set ft=vim :") == FAIL) - failed = TRUE; + if (put_line(fd, "\" vim: set ft=vim :") == FAIL) { + failed = true; + } failed |= fclose(fd); @@ -978,13 +991,13 @@ static char *get_view_file(int c) // We want a file name without separators, because we're not going to make // a directory. - // "normal" path separator -> "=+" - // "=" -> "==" - // ":" path separator -> "=-" + // "normal" path separator -> "=+" + // "=" -> "==" + // ":" path separator -> "=-" size_t len = 0; for (char *p = sname; *p; p++) { if (*p == '=' || vim_ispathsep(*p)) { - ++len; + len++; } } char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); @@ -998,18 +1011,18 @@ static char *get_view_file(int c) } else if (vim_ispathsep(*p)) { *s++ = '='; #if defined(BACKSLASH_IN_FILENAME) - if (*p == ':') - *s++ = '-'; - else -#endif + *s++ = (*p == ':') ? '-' : '+'; +#else *s++ = '+'; - } else +#endif + } else { *s++ = *p; + } } *s++ = '='; assert(c >= CHAR_MIN && c <= CHAR_MAX); *s++ = (char)c; - strcpy(s, ".vim"); + xstrlcpy(s, ".vim", 5); xfree(sname); return retval; -- cgit From bfe84adb5a097488fa723f9917253bd6f0fbf662 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Tue, 28 Jan 2020 12:49:29 +0100 Subject: options: winhighlight: fix incorrect string equality test --- src/nvim/option.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/option.c b/src/nvim/option.c index cecfd7146c..37c0928d86 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3842,7 +3842,8 @@ static bool parse_winhl_opt(win_T *wp) w_hl_id_normal = hl_id; } else { for (hlf = 0; hlf < (int)HLF_COUNT; hlf++) { - if (strncmp(hlf_names[hlf], p, nlen) == 0) { + if (strlen(hlf_names[hlf]) == nlen + && strncmp(hlf_names[hlf], p, nlen) == 0) { w_hl_ids[hlf] = hl_id; break; } -- cgit From 0b49cb67f29ecff6f7eea16063dee7e39114169a Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Jan 2020 02:02:11 -0500 Subject: vim-patch:8.2.0171: fix use of uninitialized buffer #11786 Problem: Coverity warning for using uninitialized buffer. Solution: Check the skip flag. https://github.com/vim/vim/commit/9a5e5a3e33bb86ba5209278e83ec60790f80d15c --- src/nvim/eval.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5c6e475ac7..b3cc3282d5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -22132,11 +22132,12 @@ ret_free: static char_u * trans_function_name( char_u **pp, - int skip, // only find the end, don't evaluate + bool skip, // only find the end, don't evaluate int flags, funcdict_T *fdp, // return: info about dictionary used partial_T **partial // return: partial of a FuncRef ) + FUNC_ATTR_NONNULL_ARG(1) { char_u *name = NULL; const char_u *start; @@ -22323,7 +22324,7 @@ trans_function_name( } name = xmalloc(len + lead + 1); - if (lead > 0){ + if (!skip && lead > 0) { name[0] = K_SPECIAL; name[1] = KS_EXTRA; name[2] = (int)KE_SNR; -- cgit From ca08d5c19117aa6a9b2ec288903e46823348ad07 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Jan 2020 20:30:00 -0500 Subject: vim-patch:8.1.0445: setting 'term' does not store location for termcap options Problem: Setting 'term' does not store location for termcap options. Solution: Set the script context for termcap options that are changed when 'term' is set. https://github.com/vim/vim/commit/35bc7d6c52f516b60d683bf9f0f57266d86e25b9 --- src/nvim/testdir/test_options.vim | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 6fcc372591..f9e4c2af85 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -277,6 +277,18 @@ func Test_set_errors() call assert_fails('set t_foo=', 'E846:') endfunc +" Must be executed before other tests that set 'term'. +func Test_000_term_option_verbose() + let verb_cm = execute('verbose set t_cm') + call assert_notmatch('Last set from', verb_cm) + + let term_save = &term + set term=ansi + let verb_cm = execute('verbose set t_cm') + call assert_match('Last set from.*test_options.vim', verb_cm) + let &term = term_save +endfunc + func Test_set_ttytype() " Nvim does not support 'ttytype'. if !has('nvim') && !has('gui_running') && has('unix') -- cgit From f719b8898ba8aa18f0411be1924eea132a3b103e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Jan 2020 20:30:48 -0500 Subject: vim-patch:8.1.0446: options test fails in the GUI Problem: Options test fails in the GUI. Solution: Don't try changing 'term' in the GUI. https://github.com/vim/vim/commit/4f888757257795969f2ab2e6fc3544a5bef3cdea --- src/nvim/testdir/test_options.vim | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index f9e4c2af85..29d391c232 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -279,6 +279,9 @@ endfunc " Must be executed before other tests that set 'term'. func Test_000_term_option_verbose() + if has('nvim') || has('gui_running') + return + endif let verb_cm = execute('verbose set t_cm') call assert_notmatch('Last set from', verb_cm) -- cgit From 31f31b40a8af67a3a55e85fa5dfa63d5a5999acc Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Jan 2020 20:44:23 -0500 Subject: vim-patch:8.2.0077: settagstack() cannot truncate at current index Problem: settagstack() cannot truncate at current index. Solution: Add the "t" action. (Yegappan Lakshmanan, closes vim/vim#5417) https://github.com/vim/vim/commit/271fa08a35b8d320d3a40db4ddae83b698fdd4fb --- src/nvim/eval.c | 3 ++- src/nvim/tag.c | 34 ++++++++++++++++++++++++++-------- src/nvim/testdir/test_tagjump.vim | 22 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b3cc3282d5..47c094f49d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16242,7 +16242,8 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (actstr == NULL) { return; } - if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL) { + if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't') + && actstr[1] == NUL) { action = *actstr; } else { EMSG2(_(e_invact2), actstr); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a412ed0276..07f29977aa 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -3378,11 +3378,15 @@ static void tagstack_set_curidx(win_T *wp, int curidx) } // Set the tag stack entries of the specified window. -// 'action' is set to either 'a' for append or 'r' for replace. -int set_tagstack(win_T *wp, dict_T *d, int action) +// 'action' is set to one of: +// 'a' for append +// 'r' for replace +// 't' for truncate +int set_tagstack(win_T *wp, const dict_T *d, int action) + FUNC_ATTR_NONNULL_ARG(1) { dictitem_T *di; - list_T *l; + list_T *l = NULL; // not allowed to alter the tag stack entries from inside tagfunc if (tfu_in_use) { @@ -3395,16 +3399,30 @@ int set_tagstack(win_T *wp, dict_T *d, int action) return FAIL; } l = di->di_tv.vval.v_list; + } - if (action == 'r') { + if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { + tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); + } + if (action == 't') { // truncate the stack + taggy_T *const tagstack = wp->w_tagstack; + const int tagstackidx = wp->w_tagstackidx; + int tagstacklen = wp->w_tagstacklen; + // delete all the tag stack entries above the current entry + while (tagstackidx < tagstacklen) { + tagstack_clear_entry(&tagstack[--tagstacklen]); + } + wp->w_tagstacklen = tagstacklen; + } + + if (l != NULL) { + if (action == 'r') { // replace the stack tagstack_clear(wp); } tagstack_push_items(wp, l); - } - - if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { - tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); + // set the current index after the last entry + wp->w_tagstackidx = wp->w_tagstacklen; } return OK; diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index fe98ef1ae2..5fd71d8bfc 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -340,6 +340,28 @@ func Test_getsettagstack() \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a') call assert_equal('abc', gettagstack().items[19].tagname) + " truncate the tag stack + call settagstack(1, + \ {'curidx' : 9, + \ 'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't') + let t = gettagstack() + call assert_equal(9, t.length) + call assert_equal(10, t.curidx) + + " truncate the tag stack without pushing any new items + call settagstack(1, {'curidx' : 5}, 't') + let t = gettagstack() + call assert_equal(4, t.length) + call assert_equal(5, t.curidx) + + " truncate an empty tag stack and push new items + call settagstack(1, {'items' : []}) + call settagstack(1, + \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't') + let t = gettagstack() + call assert_equal(1, t.length) + call assert_equal(2, t.curidx) + " Tag with multiple matches call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "two\tXfile1\t1", -- cgit From b7447a909fc668b023e86ca40c0e2738dd2e5350 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 29 Jan 2020 21:01:59 -0500 Subject: vim-patch:8.2.0177: memory leak in get_tags() Problem: Memory leak in get_tags(). Solution: Free matches when finding a pseudo-tag line. (Dominique Pelle, closes vim/vim#5553) https://github.com/vim/vim/commit/70b3e706b40fc2c84c1f9f33fa64945a481df395 --- src/nvim/tag.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 07f29977aa..57bb43c846 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -3158,9 +3158,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) is_static = test_for_static(&tp); - /* Skip pseudo-tag lines. */ - if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) + // Skip pseudo-tag lines. + if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) { + xfree(matches[i]); continue; + } dict = tv_dict_alloc(); tv_list_append_dict(list, dict); -- cgit From 2538e615130c7f4baa1029d0be2bc2d7f66cdd7e Mon Sep 17 00:00:00 2001 From: Axel Forsman Date: Thu, 30 Jan 2020 07:34:34 +0100 Subject: Fix shift change callbacks reading bad cursor (#11782) Sloppy code inherited from Vim caused user scripts to be able to observe the cursor line in an invalid intermediary state, due to Neovim change callbacks being unbuffered unlike Vim listeners. Manifested in Vimscript executed from the callback possibly erroring when `:call`:ing any function, due to the implicit range `curwin->w_cursor.lnum,curwin->w_cursor.lnum` failing validation. Fixed by deferring the call to `changed_lines()` until after `curwin->w_cursor.lnum` gets its correct value. --- src/nvim/ops.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/nvim/ops.c b/src/nvim/ops.c index db5c98ed78..6d327c0814 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -221,8 +221,6 @@ void op_shift(oparg_T *oap, int curs_top, int amount) ++curwin->w_cursor.lnum; } - changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); - if (oap->motion_type == kMTBlockWise) { curwin->w_cursor.lnum = oap->start.lnum; curwin->w_cursor.col = block_col; @@ -262,8 +260,11 @@ void op_shift(oparg_T *oap, int curs_top, int amount) curbuf->b_op_start = oap->start; curbuf->b_op_end.lnum = oap->end.lnum; curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); - if (curbuf->b_op_end.col > 0) - --curbuf->b_op_end.col; + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; + } + + changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); } // Shift the current line one shiftwidth left (if left != 0) or right -- cgit From 63983316bd05f189cb98907024f911435c1475fd Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 30 Jan 2020 21:28:51 -0500 Subject: vim-patch:8.2.0014: test69 and test95 are old style Problem: Test69 and test95 are old style. Solution: Convert to new style tests. (Yegappan Lakshmanan, closes vim/vim#5365) https://github.com/vim/vim/commit/afc13bd8271819c7871ff2ae2cfebb22190a0d39 --- src/nvim/testdir/test_regexp_utf8.vim | 141 ++++++++++++ src/nvim/testdir/test_textformat.vim | 423 ++++++++++++++++++++++++++++++++++ 2 files changed, 564 insertions(+) (limited to 'src') diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index e06c7d6368..ecd0e8d56b 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -192,3 +192,144 @@ func Test_optmatch_toolong() set re=0 endfunc +" Test for regexp patterns with multi-byte support, using utf-8. +func Test_multibyte_chars() + " tl is a List of Lists with: + " 2: test auto/old/new 0: test auto/old 1: test auto/new + " regexp pattern + " text to test the pattern on + " expected match (optional) + " expected submatch 1 (optional) + " expected submatch 2 (optional) + " etc. + " When there is no match use only the first two items. + let tl = [] + + " Multi-byte character tests. These will fail unless vim is compiled + " with Multibyte (FEAT_MBYTE) or BIG/HUGE features. + call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna']) + call add(tl, [2, '[[=a=]]\+', 'ddaãâbcd', 'aãâ']) " equivalence classes + call add(tl, [2, '[^ม ]\+', 'มม oijasoifjos ifjoisj f osij j มมมมม abcd', 'oijasoifjos']) + call add(tl, [2, ' [^ ]\+', 'start มabcdม ', ' มabcdม']) + call add(tl, [2, '[ม[:alpha:][=a=]]\+', '879 aiaãมâมaiuvna ', 'aiaãมâมaiuvna']) + + " this is not a normal "i" but 0xec + call add(tl, [2, '\p\+', 'ìa', 'ìa']) + call add(tl, [2, '\p*', 'aあ', 'aあ']) + + " Test recognition of some character classes + call add(tl, [2, '\i\+', '&*¨xx ', 'xx']) + call add(tl, [2, '\f\+', '&*Ÿfname ', 'fname']) + + " Test composing character matching + call add(tl, [2, '.ม', 'xม่x yมy', 'yม']) + call add(tl, [2, '.ม่', 'xม่x yมy', 'xม่']) + call add(tl, [2, "\u05b9", " x\u05b9 ", "x\u05b9"]) + call add(tl, [2, ".\u05b9", " x\u05b9 ", "x\u05b9"]) + call add(tl, [2, "\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, ".\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, "\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, ".\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, "\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"]) + call add(tl, [2, ".\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"]) + call add(tl, [2, "\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"]) + call add(tl, [2, ".\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"]) + call add(tl, [1, "\u05b9\u05bb", " y\u05b9 x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, ".\u05b9\u05bb", " y\u05bb x\u05b9\u05bb ", "x\u05b9\u05bb"]) + call add(tl, [2, "a", "ca\u0300t"]) + call add(tl, [2, "ca", "ca\u0300t"]) + call add(tl, [2, "a\u0300", "ca\u0300t", "a\u0300"]) + call add(tl, [2, 'a\%C', "ca\u0300t", "a\u0300"]) + call add(tl, [2, 'ca\%C', "ca\u0300t", "ca\u0300"]) + call add(tl, [2, 'ca\%Ct', "ca\u0300t", "ca\u0300t"]) + + " Test \Z + call add(tl, [2, 'ú\Z', 'x']) + call add(tl, [2, 'יהוה\Z', 'יהוה', 'יהוה']) + call add(tl, [2, 'יְהוָה\Z', 'יהוה', 'יהוה']) + call add(tl, [2, 'יהוה\Z', 'יְהוָה', 'יְהוָה']) + call add(tl, [2, 'יְהוָה\Z', 'יְהוָה', 'יְהוָה']) + call add(tl, [2, 'יְ\Z', 'וְיַ', 'יַ']) + call add(tl, [2, "ק\u200d\u05b9x\\Z", "xק\u200d\u05b9xy", "ק\u200d\u05b9x"]) + call add(tl, [2, "ק\u200d\u05b9x\\Z", "xק\u200dxy", "ק\u200dx"]) + call add(tl, [2, "ק\u200dx\\Z", "xק\u200d\u05b9xy", "ק\u200d\u05b9x"]) + call add(tl, [2, "ק\u200dx\\Z", "xק\u200dxy", "ק\u200dx"]) + call add(tl, [2, "\u05b9\\Z", "xyz"]) + call add(tl, [2, "\\Z\u05b9", "xyz"]) + call add(tl, [2, "\u05b9\\Z", "xy\u05b9z", "y\u05b9"]) + call add(tl, [2, "\\Z\u05b9", "xy\u05b9z", "y\u05b9"]) + call add(tl, [1, "\u05b9\\+\\Z", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"]) + call add(tl, [1, "\\Z\u05b9\\+", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"]) + + " Combining different tests and features + call add(tl, [2, '[^[=a=]]\+', 'ddaãâbcd', 'dd']) + + " Run the tests + for t in tl + let re = t[0] + let pat = t[1] + let text = t[2] + let matchidx = 3 + for engine in [0, 1, 2] + if engine == 2 && re == 0 || engine == 1 && re == 1 + continue + endif + let ®expengine = engine + try + let l = matchlist(text, pat) + catch + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . + \ '\", caused an exception: \"' . v:exception . '\"') + endtry + " check the match itself + if len(l) == 0 && len(t) > matchidx + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . + \ '\", did not match, expected: \"' . t[matchidx] . '\"') + elseif len(l) > 0 && len(t) == matchidx + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . '\", match: \"' . l[0] . + \ '\", expected no match') + elseif len(t) > matchidx && l[0] != t[matchidx] + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . '\", match: \"' . l[0] . + \ '\", expected: \"' . t[matchidx] . '\"') + else + " Test passed + endif + if len(l) > 0 + " check all the nine submatches + for i in range(1, 9) + if len(t) <= matchidx + i + let e = '' + else + let e = t[matchidx + i] + endif + if l[i] != e + call assert_report('Error ' . engine . ': pat: \"' . pat . + \ '\", text: \"' . text . '\", submatch ' . i . + \ ': \"' . l[i] . '\", expected: \"' . e . '\"') + endif + endfor + unlet i + endif + endfor + endfor + set regexpengine& +endfunc + +" check that 'ambiwidth' does not change the meaning of \p +func Test_ambiwidth() + set regexpengine=1 ambiwidth=single + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine=1 ambiwidth=double + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine=2 ambiwidth=single + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine=2 ambiwidth=double + call assert_equal(0, match("\u00EC", '\p')) + set regexpengine& ambiwidth& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 13fb50b985..1f87fbd31a 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -489,3 +489,426 @@ func Test_format_list_auto() bwipe! set fo& ai& bs& endfunc + +" Test for formatting multi-byte text with 'fo=t' +func Test_tw_2_fo_t() + new + let t =<< trim END + { + XYZ + abc XYZ + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=t + let t =<< trim END + XYZ + abc XYZ + END + exe "normal gqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + XYZ + abc + XYZ + + XYZ + abc + XYZ + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm' and 'tw=1' +func Test_tw_1_fo_tm() + new + let t =<< trim END + { + X + Xa + X a + XY + X Y + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=1 fo=tm + let t =<< trim END + X + Xa + X a + XY + X Y + END + exe "normal gqgqjgqgqjgqgqjgqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + X + a + X + a + X + Y + X + Y + + X + X + a + X + a + X + Y + X + Y + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm' and 'tw=2' +func Test_tw_2_fo_tm() + new + let t =<< trim END + { + X + Xa + X a + XY + X Y + aX + abX + abcX + abX c + abXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=tm + let t =<< trim END + X + Xa + X a + XY + X Y + aX + abX + abcX + abX c + abXY + END + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + X + a + X + a + X + Y + X + Y + a + X + ab + X + abc + X + ab + X + c + ab + X + Y + + X + X + a + X + a + X + Y + X + Y + a + X + ab + X + abc + X + ab + X + c + ab + X + Y + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'autoindent'. +func Test_tw_2_fo_tm_ai() + new + let t =<< trim END + { + X + Xa + } + END + call setline(1, t) + call cursor(2, 1) + + set ai tw=2 fo=tm + let t =<< trim END + X + Xa + END + exe "normal gqgqjgqgq" + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + X + a + + X + X + a + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& ai& + bwipe! +endfunc + +" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'noai'. +func Test_tw_2_fo_tm_noai() + new + let t =<< trim END + { + X + Xa + } + END + call setline(1, t) + call cursor(2, 1) + + set noai tw=2 fo=tm + exe "normal gqgqjgqgqo\n X\n Xa" + + let expected =<< trim END + { + X + X + a + + X + X + a + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& ai& + bwipe! +endfunc + +func Test_tw_2_fo_cqm_com() + new + let t =<< trim END + { + X + Xa + XaY + XY + XYZ + X Y + X YZ + XX + XXa + XXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=cqm comments=n:X + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + let t =<< trim END + X + Xa + XaY + XY + XYZ + X Y + X YZ + XX + XXa + XXY + END + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + Xa + Xa + XY + XY + XY + XZ + X Y + X Y + X Z + XX + XXa + XXY + + X + Xa + Xa + XY + XY + XY + XZ + X Y + X Y + X Z + XX + XXa + XXY + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& comments& + bwipe! +endfunc + +func Test_tw_2_fo_tm_replace() + new + let t =<< trim END + { + + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=tm + exe "normal RXa" + + let expected =<< trim END + { + X + a + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& + bwipe! +endfunc + +" Test for 'matchpairs' with multibyte chars +func Test_mps() + new + let t =<< trim END + { + ‘ two three ’ four + } + END + call setline(1, t) + call cursor(2, 1) + + exe "set mps+=\u2018:\u2019" + normal d% + + let expected =<< trim END + { + four + } + END + call assert_equal(expected, getline(1, '$')) + + set mps& + bwipe! +endfunc + +" Test for ra on multi-byte characters +func Test_ra_multibyte() + new + let t =<< trim END + ra test + abba + aab + END + call setline(1, t) + call cursor(1, 1) + + normal jVjra + + let expected =<< trim END + ra test + aaaa + aaa + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + +" Test for 'whichwrap' with multi-byte character +func Test_whichwrap() + new + let t =<< trim END + á + x + END + call setline(1, t) + call cursor(2, 1) + + set whichwrap+=h + normal dh + set whichwrap-=h + + let expected =<< trim END + áx + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + +func Test_substitute() + call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab -- cgit From ef11b800ac379dd7171c090f5f9f995aedae9bd2 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 30 Jan 2020 21:30:33 -0500 Subject: vim-patch:8.2.0016: test name used twice, option not restored properly Problem: Test name used twice, option not restored properly. Solution: Rename function, restore option with "&". https://github.com/vim/vim/commit/a48e78e11f2b647183fd12f569020756b17d7683 --- src/nvim/testdir/test_textformat.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 1f87fbd31a..75673adf0a 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -886,7 +886,7 @@ func Test_ra_multibyte() endfunc " Test for 'whichwrap' with multi-byte character -func Test_whichwrap() +func Test_whichwrap_multi_byte() new let t =<< trim END á @@ -897,7 +897,7 @@ func Test_whichwrap() set whichwrap+=h normal dh - set whichwrap-=h + set whichwrap& let expected =<< trim END áx -- cgit From 14a8b3b98c245087ef431070195f3a2fa3db16c0 Mon Sep 17 00:00:00 2001 From: Hye Sung Jung Date: Fri, 31 Jan 2020 00:56:34 -0600 Subject: doc: fix typos [ci skip] #11787 --- src/nvim/eval/encode.c | 2 +- src/nvim/eval/typval_encode.c.h | 2 +- src/nvim/ex_getln.c | 2 +- src/nvim/fileio.c | 12 ++++++------ src/nvim/grid_defs.h | 2 +- src/nvim/shada.c | 12 ++++++------ src/nvim/syntax.c | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 6074e4ee69..138f638eb2 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -248,7 +248,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, /// @param[out] read_bytes Is set to amount of bytes read. /// /// @return OK when reading was finished, FAIL in case of error (i.e. list item -/// was not a string), NOTDONE if reading was successfull, but there are +/// was not a string), NOTDONE if reading was successful, but there are /// more bytes to read. int encode_read_from_list(ListReaderState *const state, char *const buf, const size_t nbuf, size_t *const read_bytes) diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 289c3ee99c..af21a6fbe3 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -173,7 +173,7 @@ /// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK /// @brief Macros used to check special dictionary key /// -/// @param label Label for goto in case check was not successfull. +/// @param label Label for goto in case check was not successful. /// @param key typval_T key to check. /// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index e6b7bfaebf..f6833874ea 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3040,7 +3040,7 @@ void cmdline_screen_cleared(void) } } -/// called by ui_flush, do what redraws neccessary to keep cmdline updated. +/// called by ui_flush, do what redraws necessary to keep cmdline updated. void cmdline_ui_flush(void) { if (!ui_has(kUICmdline)) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7a46957151..84c328f0c4 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6587,7 +6587,7 @@ static int autocmd_nested = FALSE; /// Execute autocommands for "event" and file name "fname". /// -/// @param event event that occured +/// @param event event that occurred /// @param fname filename, NULL or empty means use actual file name /// @param fname_io filename to use for on cmdline /// @param force When true, ignore autocmd_busy @@ -6604,7 +6604,7 @@ bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force, /// Like apply_autocmds(), but with extra "eap" argument. This takes care of /// setting v:filearg. /// -/// @param event event that occured +/// @param event event that occurred /// @param fname NULL or empty means use actual file name /// @param fname_io fname to use for on cmdline /// @param force When true, ignore autocmd_busy @@ -6624,7 +6624,7 @@ static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, /// conditional, no autocommands are executed. If otherwise the autocommands /// cause the script to be aborted, retval is set to FAIL. /// -/// @param event event that occured +/// @param event event that occurred /// @param fname NULL or empty means use actual file name /// @param fname_io fname to use for on cmdline /// @param force When true, ignore autocmd_busy @@ -6684,7 +6684,7 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// Execute autocommands for "event" and file name "fname". /// -/// @param event event that occured +/// @param event event that occurred /// @param fname filename, NULL or empty means use actual file name /// @param fname_io filename to use for on cmdline, /// NULL means use `fname`. @@ -7197,8 +7197,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) /// To account for buffer-local autocommands, function needs to know /// in which buffer the file will be opened. /// -/// @param event event that occured. -/// @param sfname filename the event occured in. +/// @param event event that occurred. +/// @param sfname filename the event occurred in. /// @param buf buffer the file is open in bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 9e588d0387..c6687c8da9 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -18,7 +18,7 @@ typedef int16_t sattr_T; /// chars[] contains the UTF-8 text that is currently displayed on the grid. /// It is stored as a single block of cells. When redrawing a part of the grid, /// the new state can be compared with the existing state of the grid. This way -/// we can avoid sending bigger updates than neccessary to the Ul layer. +/// we can avoid sending bigger updates than necessary to the Ul layer. /// /// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can /// contain up to MAX_MCO composing characters after the base character. diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2306da94c6..19a14f340b 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -177,7 +177,7 @@ typedef enum { /// Possible results when reading ShaDa file typedef enum { - kSDReadStatusSuccess, ///< Reading was successfull. + kSDReadStatusSuccess, ///< Reading was successful. kSDReadStatusFinished, ///< Nothing more to read. kSDReadStatusReadError, ///< Failed to read from file. kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file. @@ -186,11 +186,11 @@ typedef enum { /// Possible results of shada_write function. typedef enum { - kSDWriteSuccessfull, ///< Writing was successfull. - kSDWriteReadNotShada, ///< Writing was successfull, but when reading it + kSDWriteSuccessfull, ///< Writing was successful. + kSDWriteReadNotShada, ///< Writing was successful, but when reading it ///< attempted to read file that did not look like ///< a ShaDa file. - kSDWriteFailed, ///< Writing was not successfull (e.g. because there + kSDWriteFailed, ///< Writing was not successful (e.g. because there ///< was no space left on device). kSDWriteIgnError, ///< Writing resulted in a error which can be ignored ///< (e.g. when trying to dump a function reference or @@ -3005,7 +3005,7 @@ shada_write_exit: /// location is used. /// @param[in] nomerge If true then old file is ignored. /// -/// @return OK if writing was successfull, FAIL otherwise. +/// @return OK if writing was successful, FAIL otherwise. int shada_write_file(const char *const file, bool nomerge) { if (shada_disabled()) { @@ -3341,7 +3341,7 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, /// @param[in] sd_reader Structure containing file reader definition. /// @param[out] result Location where result is saved. /// -/// @return kSDReadStatusSuccess if reading was successfull, +/// @return kSDReadStatusSuccess if reading was successful, /// kSDReadStatusNotShaDa if there were not enough bytes to read or /// kSDReadStatusReadError if reading failed for whatever reason. static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index bcf133afda..ddb9188371 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3981,7 +3981,7 @@ static void add_keyword(char_u *const name, STRLEN(kp->keyword), hash); // even though it looks like only the kp->keyword member is - // being used here, vim uses some pointer trickery to get the orignal + // being used here, vim uses some pointer trickery to get the original // struct again later by using knowledge of the offset of the keyword // field in the struct. See the definition of the HI2KE macro. if (HASHITEM_EMPTY(hi)) { -- cgit From 26199fedca0c90bdfb1b3279de2ad737fd308e52 Mon Sep 17 00:00:00 2001 From: Alkeryn Date: Sat, 1 Feb 2020 23:15:36 +0100 Subject: vim-patch:8.2.0190: detect Kotlin files [ci skip] #11796 Problem: Kotlin files are not recognized. Solution: Detect Kotlin files. (Alkeryn, closes vim/vim#5560) https://github.com/vim/vim/commit/ab067a21b9622513ed75f4801b001606eeaf2474 --- src/nvim/testdir/test_filetype.vim | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 2334cc95a7..968bf01c15 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -234,6 +234,7 @@ let s:filename_checks = { \ 'kconfig': ['Kconfig', 'Kconfig.debug'], \ 'kivy': ['file.kv'], \ 'kix': ['file.kix'], + \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'], \ 'kscript': ['file.ks'], \ 'kwt': ['file.k'], \ 'lace': ['file.ace', 'file.ACE'], -- cgit From 9c1a31927d946e6e3d25cb31cbf62e2ba1c0e8f2 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 1 Feb 2020 12:39:52 -0500 Subject: vim-patch:8.2.0161: not recognizing .gv file as dot filetype Problem: Not recognizing .gv file as dot filetype. Solution: Add *.gv to dot pattern. (closes vim/vim#5544) https://github.com/vim/vim/commit/f8ddb25789a6af530e69f499907979dfbff1c1ea --- src/nvim/testdir/test_filetype.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 968bf01c15..7290cceb0b 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -139,7 +139,7 @@ let s:filename_checks = { \ 'dockerfile': ['Dockerfile', 'file.Dockerfile'], \ 'dosbatch': ['file.bat', 'file.sys'], \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'], - \ 'dot': ['file.dot'], + \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'], \ 'dsl': ['file.dsl'], \ 'dtd': ['file.dtd'], -- cgit From d6625349f50bb7326a39728d182d58808a6f0f29 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sat, 1 Feb 2020 18:53:01 -0500 Subject: vim-patch:8.1.0140: recording into a register has focus events Problem: Recording into a register has focus events. (Michael Naumann) Solution: Don't record K_FOCUSGAINED and K_FOCUSLOST. (closes vim/vim#3143) https://github.com/vim/vim/commit/972bfddc6b3f52ae0865ad8c0bf6089bc8a9883a --- src/nvim/getchar.c | 38 ++++++++++++++++++++++++++------------ src/nvim/keymap.h | 4 ++-- 2 files changed, 28 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 419c6328ee..bf72f03729 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1096,26 +1096,40 @@ void del_typebuf(int len, int offset) * Write typed characters to script file. * If recording is on put the character in the recordbuffer. */ -static void gotchars(char_u *chars, size_t len) +static void gotchars(const char_u *chars, size_t len) + FUNC_ATTR_NONNULL_ALL { - char_u *s = chars; - int c; + const char_u *s = chars; + static char_u buf[4] = { 0 }; + static size_t buflen = 0; + size_t todo = len; - // remember how many chars were last recorded - if (reg_recording != 0) { - last_recorded_len += len; - } + while (todo--) { + buf[buflen++] = *s++; + + // When receiving a special key sequence, store it until we have all + // the bytes and we can decide what to do with it. + if (buflen == 1 && buf[0] == K_SPECIAL) { + continue; + } + if (buflen == 2) { + continue; + } - while (len--) { // Handle one byte at a time; no translation to be done. - c = *s++; - updatescript(c); + for (size_t i = 0; i < buflen; i++) { + updatescript(buf[i]); + } if (reg_recording != 0) { - char buf[2] = { (char)c, NUL }; - add_buff(&recordbuff, buf, 1L); + buf[buflen] = NUL; + add_buff(&recordbuff, (char *)buf, (ptrdiff_t)buflen); + // remember how many chars were last recorded + last_recorded_len += buflen; } + buflen = 0; } + may_sync_undo(); /* output "debug mode" message next time in debug mode */ diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index cc02a6fb4f..d3e887badc 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -240,8 +240,8 @@ enum key_extra { , KE_DROP = 95 // DnD data is available // , KE_CURSORHOLD = 96 // CursorHold event , KE_NOP = 97 // no-op: does nothing - , KE_FOCUSGAINED = 98 // focus gained - , KE_FOCUSLOST = 99 // focus lost + // , KE_FOCUSGAINED = 98 // focus gained + // , KE_FOCUSLOST = 99 // focus lost // , KE_MOUSEMOVE = 100 // mouse moved with no button down // , KE_CANCEL = 101 // return from vgetc , KE_EVENT = 102 // event -- cgit From 5032bc85144f929e0a3fef0df694ae08b3c52d14 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Sun, 2 Feb 2020 03:10:48 -0500 Subject: vim-patch:8.1.1269: MS-Windows GUI: multibyte chars with a 0x80 byte do not work Problem: MS-Windows GUI: multibyte chars with a 0x80 byte do not work when compiled with VIMDLL. Solution: Adjust the condition for fixing the input buffer. (Ken Takata, closes vim/vim#4330) https://github.com/vim/vim/commit/ed5ab2a95972b5ef588bdafab9f197e1dcf0c1df --- src/nvim/getchar.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index bf72f03729..d70d04cb4b 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2495,12 +2495,11 @@ int inchar( return fix_input_buffer(buf, len); } -/* - * Fix typed characters for use by vgetc() and check_termcode(). - * buf[] must have room to triple the number of bytes! - * Returns the new length. - */ +// Fix typed characters for use by vgetc() and check_termcode(). +// "buf[]" must have room to triple the number of bytes! +// Returns the new length. int fix_input_buffer(char_u *buf, int len) + FUNC_ATTR_NONNULL_ALL { if (!using_script()) { // Should not escape K_SPECIAL/CSI reading input from the user because vim -- cgit From 459a362cc140644d104de326258f9dfe75dbdcdf Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 1 Feb 2020 16:16:36 +0100 Subject: extmarks: fix crash due to invalid column values in inccommand preview This used to use -1 and MAXCOL values. Make sure in range values are used. --- src/nvim/extmark.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index d60723c755..60ff5c3af9 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -699,21 +699,30 @@ void bufhl_add_hl_pos_offset(buf_T *buf, // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) { + int end_off = 0; if (pos_start.lnum < lnum && lnum < pos_end.lnum) { - hl_start = offset-1; - hl_end = MAXCOL; + // TODO(bfredl): This is quite ad-hoc, but the space between |num| and + // text being highlighted is the indication of \n being part of the + // substituted text. But it would be more consistent to highlight + // a space _after_ the previous line instead (like highlight EOL list + // char) + hl_start = MAX(offset-1, 0); + end_off = 1; + hl_end = 0; } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) { hl_start = pos_start.col + offset; - hl_end = MAXCOL; + end_off = 1; + hl_end = 0; } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) { - hl_start = offset-1; + hl_start = MAX(offset-1, 0); hl_end = pos_end.col + offset; } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) { hl_start = pos_start.col + offset; hl_end = pos_end.col + offset; } (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id, - (int)lnum-1, hl_start, (int)lnum-1, hl_end, + (int)lnum-1, hl_start, + (int)lnum-1+end_off, hl_end, VIRTTEXT_EMPTY); } } -- cgit From d1d5f5103e2225749f31aa918a17089082029eae Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 2 Feb 2020 21:46:51 -0800 Subject: refactor: move various things to os/shell.c - No code changes - Rename mch_expand_wildcards => os_expand_wildcards --- src/nvim/os/shell.c | 498 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/os_unix.c | 506 ---------------------------------------------------- src/nvim/path.c | 8 +- 3 files changed, 502 insertions(+), 510 deletions(-) (limited to 'src') diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index d1de18d5b3..ecca88f656 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -9,13 +9,17 @@ #include #include "nvim/ascii.h" +#include "nvim/fileio.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/event/loop.h" #include "nvim/event/libuv_process.h" #include "nvim/event/rstream.h" +#include "nvim/ex_cmds.h" +#include "nvim/misc1.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" +#include "nvim/path.h" #include "nvim/types.h" #include "nvim/main.h" #include "nvim/vim.h" @@ -32,6 +36,8 @@ #define NS_1_SECOND 1000000000U // 1 second, in nanoseconds #define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data. +#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" + typedef struct { char *data; size_t cap, len; @@ -41,6 +47,498 @@ typedef struct { # include "os/shell.c.generated.h" #endif +static void save_patterns(int num_pat, char_u **pat, int *num_file, + char_u ***file) +{ + *file = xmalloc((size_t)num_pat * sizeof(char_u *)); + for (int i = 0; i < num_pat; i++) { + char_u *s = vim_strsave(pat[i]); + // Be compatible with expand_filename(): halve the number of + // backslashes. + backslash_halve(s); + (*file)[i] = s; + } + *num_file = num_pat; +} + +static bool have_wildcard(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (path_has_wildcard(file[i])) { + return true; + } + } + return false; +} + +static bool have_dollars(int num, char_u **file) +{ + for (int i = 0; i < num; i++) { + if (vim_strchr(file[i], '$') != NULL) { + return true; + } + } + return false; +} + +/// Performs wildcard pattern matching using the shell. +/// +/// @param num_pat is the number of input patterns. +/// @param pat is an array of pointers to input patterns. +/// @param[out] num_file is pointer to number of matched file names. +/// Set to the number of pointers in *file. +/// @param[out] file is pointer to array of pointers to matched file names. +/// Memory pointed to by the initial value of *file will +/// not be freed. +/// Set to NULL if FAIL is returned. Otherwise points to +/// allocated memory. +/// @param flags is a combination of EW_* flags used in +/// expand_wildcards(). +/// If matching fails but EW_NOTFOUND is set in flags or +/// there are no wildcards, the patterns from pat are +/// copied into *file. +/// +/// @returns OK for success or FAIL for error. +int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, + char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3) + FUNC_ATTR_NONNULL_ARG(4) +{ + int i; + size_t len; + char_u *p; + bool dir; + char_u *extra_shell_arg = NULL; + ShellOpts shellopts = kShellOptExpand | kShellOptSilent; + int j; + char_u *tempname; + char_u *command; + FILE *fd; + char_u *buffer; +#define STYLE_ECHO 0 /* use "echo", the default */ +#define STYLE_GLOB 1 /* use "glob", for csh */ +#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */ +#define STYLE_PRINT 3 /* use "print -N", for zsh */ +#define STYLE_BT 4 /* `cmd` expansion, execute the pattern + * directly */ + int shell_style = STYLE_ECHO; + int check_spaces; + static bool did_find_nul = false; + bool ampersent = false; + // vimglob() function to define for Posix shell + static char *sh_vimglob_func = + "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; + + bool is_fish_shell = +#if defined(UNIX) + STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; +#else + false; +#endif + + *num_file = 0; // default: no files found + *file = NULL; + + // If there are no wildcards, just copy the names to allocated memory. + // Saves a lot of time, because we don't have to start a new shell. + if (!have_wildcard(num_pat, pat)) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + + // Don't allow any shell command in the sandbox. + if (sandbox != 0 && check_secure()) { + return FAIL; + } + + // Don't allow the use of backticks in secure and restricted mode. + if (secure || restricted) { + for (i = 0; i < num_pat; i++) { + if (vim_strchr(pat[i], '`') != NULL + && (check_restricted() || check_secure())) { + return FAIL; + } + } + } + + // get a name for the temp file + if ((tempname = vim_tempname()) == NULL) { + EMSG(_(e_notmp)); + return FAIL; + } + + // Let the shell expand the patterns and write the result into the temp + // file. + // STYLE_BT: NL separated + // If expanding `cmd` execute it directly. + // STYLE_GLOB: NUL separated + // If we use *csh, "glob" will work better than "echo". + // STYLE_PRINT: NL or NUL separated + // If we use *zsh, "print -N" will work better than "glob". + // STYLE_VIMGLOB: NL separated + // If we use *sh*, we define "vimglob()". + // STYLE_ECHO: space separated. + // A shell we don't know, stay safe and use "echo". + if (num_pat == 1 && *pat[0] == '`' + && (len = STRLEN(pat[0])) > 2 + && *(pat[0] + len - 1) == '`') { + shell_style = STYLE_BT; + } else if ((len = STRLEN(p_sh)) >= 3) { + if (STRCMP(p_sh + len - 3, "csh") == 0) { + shell_style = STYLE_GLOB; + } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { + shell_style = STYLE_PRINT; + } + } + if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh), + "sh") != NULL) + shell_style = STYLE_VIMGLOB; + + // Compute the length of the command. We need 2 extra bytes: for the + // optional '&' and for the NUL. + // Worst case: "unset nonomatch; print -N >" plus two is 29 + len = STRLEN(tempname) + 29; + if (shell_style == STYLE_VIMGLOB) + len += STRLEN(sh_vimglob_func); + + for (i = 0; i < num_pat; i++) { + // Count the length of the patterns in the same way as they are put in + // "command" below. + len++; // add space + for (j = 0; pat[i][j] != NUL; j++) { + if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + len++; // may add a backslash + } + len++; + } + } + + if (is_fish_shell) { + len += sizeof("egin;"" end") - 1; + } + + command = xmalloc(len); + + // Build the shell command: + // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell + // recognizes this). + // - Add the shell command to print the expanded names. + // - Add the temp file name. + // - Add the file name patterns. + if (shell_style == STYLE_BT) { + // change `command; command& ` to (command; command ) + if (is_fish_shell) { + STRCPY(command, "begin; "); + } else { + STRCPY(command, "("); + } + STRCAT(command, pat[0] + 1); // exclude first backtick + p = command + STRLEN(command) - 1; + if (is_fish_shell) { + *p-- = ';'; + STRCAT(command, " end"); + } else { + *p-- = ')'; // remove last backtick + } + while (p > command && ascii_iswhite(*p)) { + p--; + } + if (*p == '&') { // remove trailing '&' + ampersent = true; + *p = ' '; + } + STRCAT(command, ">"); + } else { + if (flags & EW_NOTFOUND) + STRCPY(command, "set nonomatch; "); + else + STRCPY(command, "unset nonomatch; "); + if (shell_style == STYLE_GLOB) + STRCAT(command, "glob >"); + else if (shell_style == STYLE_PRINT) + STRCAT(command, "print -N >"); + else if (shell_style == STYLE_VIMGLOB) + STRCAT(command, sh_vimglob_func); + else + STRCAT(command, "echo >"); + } + + STRCAT(command, tempname); + + if (shell_style != STYLE_BT) { + for (i = 0; i < num_pat; i++) { + // Put a backslash before special + // characters, except inside ``. + bool intick = false; + + p = command + STRLEN(command); + *p++ = ' '; + for (j = 0; pat[i][j] != NUL; j++) { + if (pat[i][j] == '`') { + intick = !intick; + } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { + // Remove a backslash, take char literally. But keep + // backslash inside backticks, before a special character + // and before a backtick. + if (intick + || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL + || pat[i][j + 1] == '`') { + *p++ = '\\'; + } + j++; + } else if (!intick + && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') + && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + // Put a backslash before a special character, but not + // when inside ``. And not for $var when EW_KEEPDOLLAR is + // set. + *p++ = '\\'; + } + + // Copy one character. + *p++ = pat[i][j]; + } + *p = NUL; + } + } + + if (flags & EW_SILENT) { + shellopts |= kShellOptHideMess; + } + + if (ampersent) { + STRCAT(command, "&"); // put the '&' after the redirection + } + + // Using zsh -G: If a pattern has no matches, it is just deleted from + // the argument list, otherwise zsh gives an error message and doesn't + // expand any other pattern. + if (shell_style == STYLE_PRINT) { + extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option + + // If we use -f then shell variables set in .cshrc won't get expanded. + // vi can do it, so we will too, but it is only necessary if there is a "$" + // in one of the patterns, otherwise we can still use the fast option. + } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { + extra_shell_arg = (char_u *)"-f"; // Use csh fast option + } + + // execute the shell command + i = call_shell( + command, + shellopts, + extra_shell_arg + ); + + // When running in the background, give it some time to create the temp + // file, but don't wait for it to finish. + if (ampersent) { + os_delay(10L, true); + } + + xfree(command); + + if (i) { // os_call_shell() failed + os_remove((char *)tempname); + xfree(tempname); + // With interactive completion, the error message is not printed. + if (!(flags & EW_SILENT)) { + msg_putchar('\n'); // clear bottom line quickly + cmdline_row = Rows - 1; // continue on last line + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + + // If a `cmd` expansion failed, don't list `cmd` as a match, even when + // EW_NOTFOUND is given + if (shell_style == STYLE_BT) { + return FAIL; + } + goto notfound; + } + + // read the names from the file into memory + fd = fopen((char *)tempname, READBIN); + if (fd == NULL) { + // Something went wrong, perhaps a file name with a special char. + if (!(flags & EW_SILENT)) { + MSG(_(e_wildexpand)); + msg_start(); // don't overwrite this message + } + xfree(tempname); + goto notfound; + } + int fseek_res = fseek(fd, 0L, SEEK_END); + if (fseek_res < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } + int64_t templen = ftell(fd); // get size of temp file + if (templen < 0) { + xfree(tempname); + fclose(fd); + return FAIL; + } +#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T + assert(templen <= (long long)SIZE_MAX); +#endif + len = (size_t)templen; + fseek(fd, 0L, SEEK_SET); + buffer = xmalloc(len + 1); + // fread() doesn't terminate buffer with NUL; + // appropriate termination (not always NUL) is done below. + size_t readlen = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (readlen != len) { + // unexpected read error + EMSG2(_(e_notread), tempname); + xfree(tempname); + xfree(buffer); + return FAIL; + } + xfree(tempname); + + // file names are separated with Space + if (shell_style == STYLE_ECHO) { + buffer[len] = '\n'; // make sure the buffer ends in NL + p = buffer; + for (i = 0; *p != '\n'; i++) { // count number of entries + while (*p != ' ' && *p != '\n') { + p++; + } + p = skipwhite(p); // skip to next entry + } + // file names are separated with NL + } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { + buffer[len] = NUL; // make sure the buffer ends in NUL + p = buffer; + for (i = 0; *p != NUL; i++) { // count number of entries + while (*p != '\n' && *p != NUL) { + p++; + } + if (*p != NUL) { + p++; + } + p = skipwhite(p); // skip leading white space + } + // file names are separated with NUL + } else { + // Some versions of zsh use spaces instead of NULs to separate + // results. Only do this when there is no NUL before the end of the + // buffer, otherwise we would never be able to use file names with + // embedded spaces when zsh does use NULs. + // When we found a NUL once, we know zsh is OK, set did_find_nul and + // don't check for spaces again. + check_spaces = false; + if (shell_style == STYLE_PRINT && !did_find_nul) { + // If there is a NUL, set did_find_nul, else set check_spaces + buffer[len] = NUL; + if (len && (int)STRLEN(buffer) < (int)len) + did_find_nul = true; + else + check_spaces = true; + } + + // Make sure the buffer ends with a NUL. For STYLE_PRINT there + // already is one, for STYLE_GLOB it needs to be added. + if (len && buffer[len - 1] == NUL) { + len--; + } else { + buffer[len] = NUL; + } + i = 0; + for (p = buffer; p < buffer + len; p++) { + if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry + i++; + *p = NUL; + } + } + if (len) { + i++; // count last entry + } + } + assert(buffer[len] == NUL || buffer[len] == '\n'); + + if (i == 0) { + // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". + // /bin/sh will happily expand it to nothing rather than returning an + // error; and hey, it's good to check anyway -- webb. + xfree(buffer); + goto notfound; + } + *num_file = i; + *file = xmalloc(sizeof(char_u *) * (size_t)i); + + // Isolate the individual file names. + p = buffer; + for (i = 0; i < *num_file; ++i) { + (*file)[i] = p; + // Space or NL separates + if (shell_style == STYLE_ECHO || shell_style == STYLE_BT + || shell_style == STYLE_VIMGLOB) { + while (!(shell_style == STYLE_ECHO && *p == ' ') + && *p != '\n' && *p != NUL) { + p++; + } + if (p == buffer + len) { // last entry + *p = NUL; + } else { + *p++ = NUL; + p = skipwhite(p); // skip to next entry + } + } else { // NUL separates + while (*p && p < buffer + len) { // skip entry + p++; + } + p++; // skip NUL + } + } + + // Move the file names to allocated memory. + for (j = 0, i = 0; i < *num_file; i++) { + // Require the files to exist. Helps when using /bin/sh + if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { + continue; + } + + // check if this entry should be included + dir = (os_isdir((*file)[i])); + if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) + continue; + + // Skip files that are not executable if we check for that. + if (!dir && (flags & EW_EXEC) + && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { + continue; + } + + p = xmalloc(STRLEN((*file)[i]) + 1 + dir); + STRCPY(p, (*file)[i]); + if (dir) { + add_pathsep((char *)p); // add '/' to a directory name + } + (*file)[j++] = p; + } + xfree(buffer); + *num_file = j; + + if (*num_file == 0) { // rejected all entries + XFREE_CLEAR(*file); + goto notfound; + } + + return OK; + +notfound: + if (flags & EW_NOTFOUND) { + save_patterns(num_pat, pat, num_file, file); + return OK; + } + return FAIL; + +} + /// Builds the argument vector for running the user-configured 'shell' (p_sh) /// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.: /// diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 0f44df2188..847e342ff1 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -1,13 +1,6 @@ // 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 -/* - * os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...) - * - * A lot of this file was originally written by Juergen Weigert and later - * changed beyond recognition. - */ - #include #include #include @@ -104,502 +97,3 @@ void mch_exit(int r) exit(r); } - -#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|" - -/// Does wildcard pattern matching using the shell. -/// -/// @param num_pat is the number of input patterns. -/// @param pat is an array of pointers to input patterns. -/// @param[out] num_file is pointer to number of matched file names. -/// Set to the number of pointers in *file. -/// @param[out] file is pointer to array of pointers to matched file names. -/// Memory pointed to by the initial value of *file will -/// not be freed. -/// Set to NULL if FAIL is returned. Otherwise points to -/// allocated memory. -/// @param flags is a combination of EW_* flags used in -/// expand_wildcards(). -/// If matching fails but EW_NOTFOUND is set in flags or -/// there are no wildcards, the patterns from pat are -/// copied into *file. -/// -/// @returns OK for success or FAIL for error. -int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, - char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3) - FUNC_ATTR_NONNULL_ARG(4) -{ - int i; - size_t len; - char_u *p; - bool dir; - char_u *extra_shell_arg = NULL; - ShellOpts shellopts = kShellOptExpand | kShellOptSilent; - int j; - char_u *tempname; - char_u *command; - FILE *fd; - char_u *buffer; -#define STYLE_ECHO 0 /* use "echo", the default */ -#define STYLE_GLOB 1 /* use "glob", for csh */ -#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */ -#define STYLE_PRINT 3 /* use "print -N", for zsh */ -#define STYLE_BT 4 /* `cmd` expansion, execute the pattern - * directly */ - int shell_style = STYLE_ECHO; - int check_spaces; - static bool did_find_nul = false; - bool ampersent = false; - // vimglob() function to define for Posix shell - static char *sh_vimglob_func = - "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; - - bool is_fish_shell = -#if defined(UNIX) - STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; -#else - false; -#endif - - *num_file = 0; // default: no files found - *file = NULL; - - // If there are no wildcards, just copy the names to allocated memory. - // Saves a lot of time, because we don't have to start a new shell. - if (!have_wildcard(num_pat, pat)) { - save_patterns(num_pat, pat, num_file, file); - return OK; - } - - // Don't allow any shell command in the sandbox. - if (sandbox != 0 && check_secure()) { - return FAIL; - } - - // Don't allow the use of backticks in secure and restricted mode. - if (secure || restricted) { - for (i = 0; i < num_pat; i++) { - if (vim_strchr(pat[i], '`') != NULL - && (check_restricted() || check_secure())) { - return FAIL; - } - } - } - - // get a name for the temp file - if ((tempname = vim_tempname()) == NULL) { - EMSG(_(e_notmp)); - return FAIL; - } - - // Let the shell expand the patterns and write the result into the temp - // file. - // STYLE_BT: NL separated - // If expanding `cmd` execute it directly. - // STYLE_GLOB: NUL separated - // If we use *csh, "glob" will work better than "echo". - // STYLE_PRINT: NL or NUL separated - // If we use *zsh, "print -N" will work better than "glob". - // STYLE_VIMGLOB: NL separated - // If we use *sh*, we define "vimglob()". - // STYLE_ECHO: space separated. - // A shell we don't know, stay safe and use "echo". - if (num_pat == 1 && *pat[0] == '`' - && (len = STRLEN(pat[0])) > 2 - && *(pat[0] + len - 1) == '`') { - shell_style = STYLE_BT; - } else if ((len = STRLEN(p_sh)) >= 3) { - if (STRCMP(p_sh + len - 3, "csh") == 0) { - shell_style = STYLE_GLOB; - } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { - shell_style = STYLE_PRINT; - } - } - if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh), - "sh") != NULL) - shell_style = STYLE_VIMGLOB; - - // Compute the length of the command. We need 2 extra bytes: for the - // optional '&' and for the NUL. - // Worst case: "unset nonomatch; print -N >" plus two is 29 - len = STRLEN(tempname) + 29; - if (shell_style == STYLE_VIMGLOB) - len += STRLEN(sh_vimglob_func); - - for (i = 0; i < num_pat; i++) { - // Count the length of the patterns in the same way as they are put in - // "command" below. - len++; // add space - for (j = 0; pat[i][j] != NUL; j++) { - if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { - len++; // may add a backslash - } - len++; - } - } - - if (is_fish_shell) { - len += sizeof("egin;"" end") - 1; - } - - command = xmalloc(len); - - // Build the shell command: - // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell - // recognizes this). - // - Add the shell command to print the expanded names. - // - Add the temp file name. - // - Add the file name patterns. - if (shell_style == STYLE_BT) { - // change `command; command& ` to (command; command ) - if (is_fish_shell) { - STRCPY(command, "begin; "); - } else { - STRCPY(command, "("); - } - STRCAT(command, pat[0] + 1); // exclude first backtick - p = command + STRLEN(command) - 1; - if (is_fish_shell) { - *p-- = ';'; - STRCAT(command, " end"); - } else { - *p-- = ')'; // remove last backtick - } - while (p > command && ascii_iswhite(*p)) { - p--; - } - if (*p == '&') { // remove trailing '&' - ampersent = true; - *p = ' '; - } - STRCAT(command, ">"); - } else { - if (flags & EW_NOTFOUND) - STRCPY(command, "set nonomatch; "); - else - STRCPY(command, "unset nonomatch; "); - if (shell_style == STYLE_GLOB) - STRCAT(command, "glob >"); - else if (shell_style == STYLE_PRINT) - STRCAT(command, "print -N >"); - else if (shell_style == STYLE_VIMGLOB) - STRCAT(command, sh_vimglob_func); - else - STRCAT(command, "echo >"); - } - - STRCAT(command, tempname); - - if (shell_style != STYLE_BT) { - for (i = 0; i < num_pat; i++) { - // Put a backslash before special - // characters, except inside ``. - bool intick = false; - - p = command + STRLEN(command); - *p++ = ' '; - for (j = 0; pat[i][j] != NUL; j++) { - if (pat[i][j] == '`') { - intick = !intick; - } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) { - // Remove a backslash, take char literally. But keep - // backslash inside backticks, before a special character - // and before a backtick. - if (intick - || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL - || pat[i][j + 1] == '`') { - *p++ = '\\'; - } - j++; - } else if (!intick - && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') - && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { - // Put a backslash before a special character, but not - // when inside ``. And not for $var when EW_KEEPDOLLAR is - // set. - *p++ = '\\'; - } - - // Copy one character. - *p++ = pat[i][j]; - } - *p = NUL; - } - } - - if (flags & EW_SILENT) { - shellopts |= kShellOptHideMess; - } - - if (ampersent) { - STRCAT(command, "&"); // put the '&' after the redirection - } - - // Using zsh -G: If a pattern has no matches, it is just deleted from - // the argument list, otherwise zsh gives an error message and doesn't - // expand any other pattern. - if (shell_style == STYLE_PRINT) { - extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option - - // If we use -f then shell variables set in .cshrc won't get expanded. - // vi can do it, so we will too, but it is only necessary if there is a "$" - // in one of the patterns, otherwise we can still use the fast option. - } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { - extra_shell_arg = (char_u *)"-f"; // Use csh fast option - } - - // execute the shell command - i = call_shell( - command, - shellopts, - extra_shell_arg - ); - - // When running in the background, give it some time to create the temp - // file, but don't wait for it to finish. - if (ampersent) { - os_delay(10L, true); - } - - xfree(command); - - if (i) { // os_call_shell() failed - os_remove((char *)tempname); - xfree(tempname); - // With interactive completion, the error message is not printed. - if (!(flags & EW_SILENT)) { - msg_putchar('\n'); // clear bottom line quickly - cmdline_row = Rows - 1; // continue on last line - MSG(_(e_wildexpand)); - msg_start(); // don't overwrite this message - } - - // If a `cmd` expansion failed, don't list `cmd` as a match, even when - // EW_NOTFOUND is given - if (shell_style == STYLE_BT) { - return FAIL; - } - goto notfound; - } - - // read the names from the file into memory - fd = fopen((char *)tempname, READBIN); - if (fd == NULL) { - // Something went wrong, perhaps a file name with a special char. - if (!(flags & EW_SILENT)) { - MSG(_(e_wildexpand)); - msg_start(); // don't overwrite this message - } - xfree(tempname); - goto notfound; - } - int fseek_res = fseek(fd, 0L, SEEK_END); - if (fseek_res < 0) { - xfree(tempname); - fclose(fd); - return FAIL; - } - int64_t templen = ftell(fd); // get size of temp file - if (templen < 0) { - xfree(tempname); - fclose(fd); - return FAIL; - } -#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T - assert(templen <= (long long)SIZE_MAX); -#endif - len = (size_t)templen; - fseek(fd, 0L, SEEK_SET); - buffer = xmalloc(len + 1); - // fread() doesn't terminate buffer with NUL; - // appropriate termination (not always NUL) is done below. - size_t readlen = fread((char *)buffer, 1, len, fd); - fclose(fd); - os_remove((char *)tempname); - if (readlen != len) { - // unexpected read error - EMSG2(_(e_notread), tempname); - xfree(tempname); - xfree(buffer); - return FAIL; - } - xfree(tempname); - - // file names are separated with Space - if (shell_style == STYLE_ECHO) { - buffer[len] = '\n'; // make sure the buffer ends in NL - p = buffer; - for (i = 0; *p != '\n'; i++) { // count number of entries - while (*p != ' ' && *p != '\n') { - p++; - } - p = skipwhite(p); // skip to next entry - } - // file names are separated with NL - } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { - buffer[len] = NUL; // make sure the buffer ends in NUL - p = buffer; - for (i = 0; *p != NUL; i++) { // count number of entries - while (*p != '\n' && *p != NUL) { - p++; - } - if (*p != NUL) { - p++; - } - p = skipwhite(p); // skip leading white space - } - // file names are separated with NUL - } else { - // Some versions of zsh use spaces instead of NULs to separate - // results. Only do this when there is no NUL before the end of the - // buffer, otherwise we would never be able to use file names with - // embedded spaces when zsh does use NULs. - // When we found a NUL once, we know zsh is OK, set did_find_nul and - // don't check for spaces again. - check_spaces = false; - if (shell_style == STYLE_PRINT && !did_find_nul) { - // If there is a NUL, set did_find_nul, else set check_spaces - buffer[len] = NUL; - if (len && (int)STRLEN(buffer) < (int)len) - did_find_nul = true; - else - check_spaces = true; - } - - // Make sure the buffer ends with a NUL. For STYLE_PRINT there - // already is one, for STYLE_GLOB it needs to be added. - if (len && buffer[len - 1] == NUL) { - len--; - } else { - buffer[len] = NUL; - } - i = 0; - for (p = buffer; p < buffer + len; p++) { - if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry - i++; - *p = NUL; - } - } - if (len) { - i++; // count last entry - } - } - assert(buffer[len] == NUL || buffer[len] == '\n'); - - if (i == 0) { - // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I". - // /bin/sh will happily expand it to nothing rather than returning an - // error; and hey, it's good to check anyway -- webb. - xfree(buffer); - goto notfound; - } - *num_file = i; - *file = xmalloc(sizeof(char_u *) * (size_t)i); - - // Isolate the individual file names. - p = buffer; - for (i = 0; i < *num_file; ++i) { - (*file)[i] = p; - // Space or NL separates - if (shell_style == STYLE_ECHO || shell_style == STYLE_BT - || shell_style == STYLE_VIMGLOB) { - while (!(shell_style == STYLE_ECHO && *p == ' ') - && *p != '\n' && *p != NUL) { - p++; - } - if (p == buffer + len) { // last entry - *p = NUL; - } else { - *p++ = NUL; - p = skipwhite(p); // skip to next entry - } - } else { // NUL separates - while (*p && p < buffer + len) { // skip entry - p++; - } - p++; // skip NUL - } - } - - // Move the file names to allocated memory. - for (j = 0, i = 0; i < *num_file; i++) { - // Require the files to exist. Helps when using /bin/sh - if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { - continue; - } - - // check if this entry should be included - dir = (os_isdir((*file)[i])); - if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) - continue; - - // Skip files that are not executable if we check for that. - if (!dir && (flags & EW_EXEC) - && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { - continue; - } - - p = xmalloc(STRLEN((*file)[i]) + 1 + dir); - STRCPY(p, (*file)[i]); - if (dir) { - add_pathsep((char *)p); // add '/' to a directory name - } - (*file)[j++] = p; - } - xfree(buffer); - *num_file = j; - - if (*num_file == 0) { // rejected all entries - XFREE_CLEAR(*file); - goto notfound; - } - - return OK; - -notfound: - if (flags & EW_NOTFOUND) { - save_patterns(num_pat, pat, num_file, file); - return OK; - } - return FAIL; - -} - - -static void save_patterns(int num_pat, char_u **pat, int *num_file, - char_u ***file) -{ - int i; - char_u *s; - - *file = xmalloc((size_t)num_pat * sizeof(char_u *)); - - for (i = 0; i < num_pat; i++) { - s = vim_strsave(pat[i]); - // Be compatible with expand_filename(): halve the number of - // backslashes. - backslash_halve(s); - (*file)[i] = s; - } - *num_file = num_pat; -} - -static bool have_wildcard(int num, char_u **file) -{ - int i; - - for (i = 0; i < num; i++) - if (path_has_wildcard(file[i])) - return true; - return false; -} - -static bool have_dollars(int num, char_u **file) -{ - int i; - - for (i = 0; i < num; i++) - if (vim_strchr(file[i], '$') != NULL) - return true; - return false; -} diff --git a/src/nvim/path.c b/src/nvim/path.c index 0142724a5b..31318f6bea 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1178,7 +1178,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, */ if (recursive) #ifdef SPECIAL_WILDCHAR - return mch_expand_wildcards(num_pat, pat, num_file, file, flags); + return os_expand_wildcards(num_pat, pat, num_file, file, flags); #else return FAIL; #endif @@ -1193,7 +1193,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, for (int i = 0; i < num_pat; i++) { if (has_special_wildchar(pat[i]) && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { - return mch_expand_wildcards(num_pat, pat, num_file, file, flags); + return os_expand_wildcards(num_pat, pat, num_file, file, flags); } } #endif @@ -1233,8 +1233,8 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, else if (has_env_var(p) || *p == '~') { xfree(p); ga_clear_strings(&ga); - i = mch_expand_wildcards(num_pat, pat, num_file, file, - flags | EW_KEEPDOLLAR); + i = os_expand_wildcards(num_pat, pat, num_file, file, + flags | EW_KEEPDOLLAR); recursive = false; return i; } -- cgit From efa5af904387d8ece68c84a568d60e071350c6cb Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 2 Feb 2020 21:56:47 -0800 Subject: refactor: rename mch_exit => os_exit - No code changes - Move it to main.c --- src/nvim/main.c | 51 +++++++++++++++++++++++++++++++----------- src/nvim/msgpack_rpc/channel.c | 2 +- src/nvim/os_unix.c | 25 --------------------- 3 files changed, 39 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index a816221a9e..bdaa20773c 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -207,7 +207,7 @@ void early_init(void) // Allocate the first window and buffer. // Can't do anything without it, exit when it fails. if (!win_alloc_first()) { - mch_exit(0); + os_exit(0); } init_yank(); // init yank buffers @@ -400,7 +400,7 @@ int main(int argc, char **argv) */ if (recoverymode && fname == NULL) { recover_names(NULL, TRUE, 0, NULL); - mch_exit(0); + os_exit(0); } // Set some option defaults after reading vimrc files. @@ -585,6 +585,31 @@ int main(int argc, char **argv) return 0; } +void os_exit(int r) + FUNC_ATTR_NORETURN +{ + exiting = true; + + ui_flush(); + ui_call_stop(); + ml_close_all(true); // remove all memfiles + + if (!event_teardown() && r == 0) { + r = 1; // Exit with error if main_loop did not teardown gracefully. + } + if (input_global_fd() >= 0) { + stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + } + + ILOG("Nvim exit: %d", r); + +#ifdef EXITFREE + free_all_mem(); +#endif + + exit(r); +} + /// Exit properly void getout(int exitval) FUNC_ATTR_NORETURN @@ -679,7 +704,7 @@ void getout(int exitval) garbage_collect(false); } - mch_exit(exitval); + os_exit(exitval); } /// Gets the integer value of a numeric command line argument if given, @@ -799,10 +824,10 @@ static void command_line_scan(mparm_T *parmp) // "--cmd " execute cmd before vimrc if (STRICMP(argv[0] + argv_idx, "help") == 0) { usage(); - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "version") == 0) { version(); - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, @@ -825,7 +850,7 @@ static void command_line_scan(mparm_T *parmp) if (ff_ret < 0) { msgpack_file_write_error(ff_ret); } - mch_exit(0); + os_exit(0); } else if (STRICMP(argv[0] + argv_idx, "headless") == 0) { headless_mode = true; } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) { @@ -891,7 +916,7 @@ static void command_line_scan(mparm_T *parmp) case '?': // "-?" give help message (for MS-Windows) case 'h': { // "-h" give help message usage(); - mch_exit(0); + os_exit(0); } case 'H': { // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; @@ -988,7 +1013,7 @@ static void command_line_scan(mparm_T *parmp) } case 'v': { version(); - mch_exit(0); + os_exit(0); } case 'V': { // "-V{N}" Verbose level // default is 10: a little bit verbose @@ -1116,7 +1141,7 @@ scripterror: _("Attempt to open script file again: \"%s %s\"\n"), argv[-1], argv[0]); mch_errmsg((const char *)IObuff); - mch_exit(2); + os_exit(2); } int error; if (strequal(argv[0], "-")) { @@ -1135,7 +1160,7 @@ scripterror: _("Cannot open for reading: \"%s\": %s\n"), argv[0], os_strerror(error)); mch_errmsg((const char *)IObuff); - mch_exit(2); + os_exit(2); } save_typebuf(); break; @@ -1173,7 +1198,7 @@ scripterror: mch_errmsg(_("Cannot open for script output: \"")); mch_errmsg(argv[0]); mch_errmsg("\"\n"); - mch_exit(2); + os_exit(2); } break; } @@ -1380,7 +1405,7 @@ static void handle_quickfix(mparm_T *paramp) vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); - mch_exit(3); + os_exit(3); } TIME_MSG("reading errorfile"); } @@ -1943,7 +1968,7 @@ static void mainerr(const char *errstr, const char *str) mch_errmsg(prgname); mch_errmsg(" -h\"\n"); - mch_exit(1); + os_exit(1); } /// Prints version information for "nvim -v" or "nvim --version". diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 0c874d7922..92ca29209e 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -564,7 +564,7 @@ void rpc_close(Channel *channel) static void exit_event(void **argv) { if (!exiting) { - mch_exit(0); + os_exit(0); } } diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 847e342ff1..be4bd9709b 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -72,28 +72,3 @@ void mch_free_acl(vim_acl_T aclent) return; } #endif - -void mch_exit(int r) - FUNC_ATTR_NORETURN -{ - exiting = true; - - ui_flush(); - ui_call_stop(); - ml_close_all(true); // remove all memfiles - - if (!event_teardown() && r == 0) { - r = 1; // Exit with error if main_loop did not teardown gracefully. - } - if (input_global_fd() >= 0) { - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) - } - - ILOG("Nvim exit: %d", r); - -#ifdef EXITFREE - free_all_mem(); -#endif - - exit(r); -} -- cgit From 2af04e199740d5d2eaab38e272ecf1f9146335f5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 2 Feb 2020 22:07:16 -0800 Subject: lint --- src/nvim/main.c | 94 +++++++++++++++++++++++------------------------------ src/nvim/os/shell.c | 55 ++++++++++++++++--------------- 2 files changed, 69 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/nvim/main.c b/src/nvim/main.c index bdaa20773c..56d9030a7f 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -293,8 +293,8 @@ int main(int argc, char **argv) if (params.diff_mode && params.window_count == -1) params.window_count = 0; /* open up to 3 windows */ - /* Don't redraw until much later. */ - ++RedrawingDisabled; + // Don't redraw until much later. + RedrawingDisabled++; setbuf(stdout, NULL); @@ -384,22 +384,16 @@ int main(int argc, char **argv) syn_maybe_on(); } - /* - * Read all the plugin files. - * Only when compiled with +eval, since most plugins need it. - */ + // Read all the plugin files. load_plugins(); // Decide about window layout for diff mode after reading vimrc. set_window_layout(¶ms); - /* - * Recovery mode without a file name: List swap files. - * This uses the 'dir' option, therefore it must be after the - * initializations. - */ + // Recovery mode without a file name: List swap files. + // Uses the 'dir' option, therefore it must be after the initializations. if (recoverymode && fname == NULL) { - recover_names(NULL, TRUE, 0, NULL); + recover_names(NULL, true, 0, NULL); os_exit(0); } @@ -431,17 +425,15 @@ int main(int argc, char **argv) set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } - /* - * "-q errorfile": Load the error file now. - * If the error file can't be read, exit before doing anything else. - */ + // "-q errorfile": Load the error file now. + // If the error file can't be read, exit before doing anything else. handle_quickfix(¶ms); - /* - * Start putting things on the screen. - * Scroll screen down before drawing over it - * Clear screen now, so file message will not be cleared. - */ + // + // Start putting things on the screen. + // Scroll screen down before drawing over it + // Clear screen now, so file message will not be cleared. + // starting = NO_BUFFERS; no_wait_return = false; if (!exmode_active) { @@ -473,27 +465,26 @@ int main(int argc, char **argv) no_wait_return = true; - /* - * Create the requested number of windows and edit buffers in them. - * Also does recovery if "recoverymode" set. - */ + // + // Create the requested number of windows and edit buffers in them. + // Also does recovery if "recoverymode" set. + // create_windows(¶ms); TIME_MSG("opening buffers"); - /* clear v:swapcommand */ + // Clear v:swapcommand set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); - /* Ex starts at last line of the file */ - if (exmode_active) + // Ex starts at last line of the file. + if (exmode_active) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); TIME_MSG("BufEnter autocommands"); setpcmark(); - /* - * When started with "-q errorfile" jump to first error now. - */ + // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { qf_jump(NULL, 0, 0, FALSE); TIME_MSG("jump to first error"); @@ -505,26 +496,23 @@ int main(int argc, char **argv) xfree(cwd); if (params.diff_mode) { - /* set options in each window for "nvim -d". */ + // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { diff_win_options(wp, TRUE); } } - /* - * Shorten any of the filenames, but only when absolute. - */ - shorten_fnames(FALSE); + // Shorten any of the filenames, but only when absolute. + shorten_fnames(false); - /* - * Need to jump to the tag before executing the '-c command'. - * Makes "vim -c '/return' -t main" work. - */ + // Need to jump to the tag before executing the '-c command'. + // Makes "vim -c '/return' -t main" work. handle_tag(params.tagname); - /* Execute any "+", "-c" and "-S" arguments. */ - if (params.n_commands > 0) + // Execute any "+", "-c" and "-S" arguments. + if (params.n_commands > 0) { exe_commands(¶ms); + } starting = 0; @@ -535,9 +523,10 @@ int main(int argc, char **argv) // 'autochdir' has been postponed. do_autochdir(); - /* start in insert mode */ - if (p_im) - need_start_insertmode = TRUE; + // start in insert mode + if (p_im) { + need_start_insertmode = true; + } set_vim_var_nr(VV_VIM_DID_ENTER, 1L); apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf); @@ -553,18 +542,19 @@ int main(int argc, char **argv) // main loop. set_reg_var(get_default_register_name()); - /* When a startup script or session file setup for diff'ing and - * scrollbind, sync the scrollbind now. */ + // When a startup script or session file setup for diff'ing and + // scrollbind, sync the scrollbind now. if (curwin->w_p_diff && curwin->w_p_scb) { update_topline(); check_scrollbind((linenr_T)0, 0L); TIME_MSG("diff scrollbinding"); } - /* If ":startinsert" command used, stuff a dummy command to be able to - * call normal_cmd(), which will then start Insert mode. */ - if (restart_edit != 0) + // If ":startinsert" command used, stuff a dummy command to be able to + // call normal_cmd(), which will then start Insert mode. + if (restart_edit != 0) { stuffcharReadbuff(K_NOP); + } // WORKAROUND(mhi): #3023 if (cb_flags & CB_UNNAMEDMASK) { @@ -574,9 +564,7 @@ int main(int argc, char **argv) TIME_MSG("before starting main loop"); ILOG("starting main loop"); - /* - * Call the main command loop. This never returns. - */ + // Main loop: never returns. normal_enter(false, false); #if defined(WIN32) && !defined(MAKE_LIB) diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index ecca88f656..3b8470182a 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -100,7 +100,8 @@ static bool have_dollars(int num, char_u **file) /// /// @returns OK for success or FAIL for error. int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, - char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3) + char_u ***file, int flags) + FUNC_ATTR_NONNULL_ARG(3) FUNC_ATTR_NONNULL_ARG(4) { int i; @@ -114,12 +115,11 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u *command; FILE *fd; char_u *buffer; -#define STYLE_ECHO 0 /* use "echo", the default */ -#define STYLE_GLOB 1 /* use "glob", for csh */ -#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */ -#define STYLE_PRINT 3 /* use "print -N", for zsh */ -#define STYLE_BT 4 /* `cmd` expansion, execute the pattern - * directly */ +#define STYLE_ECHO 0 // use "echo", the default +#define STYLE_GLOB 1 // use "glob", for csh +#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh +#define STYLE_PRINT 3 // use "print -N", for zsh +#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly int shell_style = STYLE_ECHO; int check_spaces; static bool did_find_nul = false; @@ -189,16 +189,18 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, shell_style = STYLE_PRINT; } } - if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh), - "sh") != NULL) + if (shell_style == STYLE_ECHO + && strstr((char *)path_tail(p_sh), "sh") != NULL) { shell_style = STYLE_VIMGLOB; + } // Compute the length of the command. We need 2 extra bytes: for the // optional '&' and for the NUL. // Worst case: "unset nonomatch; print -N >" plus two is 29 len = STRLEN(tempname) + 29; - if (shell_style == STYLE_VIMGLOB) + if (shell_style == STYLE_VIMGLOB) { len += STRLEN(sh_vimglob_func); + } for (i = 0; i < num_pat; i++) { // Count the length of the patterns in the same way as they are put in @@ -248,18 +250,20 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, } STRCAT(command, ">"); } else { - if (flags & EW_NOTFOUND) + if (flags & EW_NOTFOUND) { STRCPY(command, "set nonomatch; "); - else + } else { STRCPY(command, "unset nonomatch; "); - if (shell_style == STYLE_GLOB) + } + if (shell_style == STYLE_GLOB) { STRCAT(command, "glob >"); - else if (shell_style == STYLE_PRINT) + } else if (shell_style == STYLE_PRINT) { STRCAT(command, "print -N >"); - else if (shell_style == STYLE_VIMGLOB) + } else if (shell_style == STYLE_VIMGLOB) { STRCAT(command, sh_vimglob_func); - else + } else { STRCAT(command, "echo >"); + } } STRCAT(command, tempname); @@ -323,11 +327,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, } // execute the shell command - i = call_shell( - command, - shellopts, - extra_shell_arg - ); + i = call_shell(command, shellopts, extra_shell_arg); // When running in the background, give it some time to create the temp // file, but don't wait for it to finish. @@ -380,7 +380,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, return FAIL; } #if SIZEOF_LONG_LONG > SIZEOF_SIZE_T - assert(templen <= (long long)SIZE_MAX); + assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int) #endif len = (size_t)templen; fseek(fd, 0L, SEEK_SET); @@ -434,10 +434,11 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, if (shell_style == STYLE_PRINT && !did_find_nul) { // If there is a NUL, set did_find_nul, else set check_spaces buffer[len] = NUL; - if (len && (int)STRLEN(buffer) < (int)len) + if (len && (int)STRLEN(buffer) < (int)len) { did_find_nul = true; - else + } else { check_spaces = true; + } } // Make sure the buffer ends with a NUL. For STYLE_PRINT there @@ -472,7 +473,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, // Isolate the individual file names. p = buffer; - for (i = 0; i < *num_file; ++i) { + for (i = 0; i < *num_file; i++) { (*file)[i] = p; // Space or NL separates if (shell_style == STYLE_ECHO || shell_style == STYLE_BT @@ -504,8 +505,9 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, // check if this entry should be included dir = (os_isdir((*file)[i])); - if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) + if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { continue; + } // Skip files that are not executable if we check for that. if (!dir && (flags & EW_EXEC) @@ -536,7 +538,6 @@ notfound: return OK; } return FAIL; - } /// Builds the argument vector for running the user-configured 'shell' (p_sh) -- cgit From 7ce9a5c7da0fb5d6117cf9526c39e01faf7e908d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 25 Jan 2020 13:29:52 +0100 Subject: api: add nvim_get_runtime_file for finding runtime files --- src/clint.py | 6 +++++- src/nvim/api/vim.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/clint.py b/src/clint.py index 675b67ccef..12bada6aac 100755 --- a/src/clint.py +++ b/src/clint.py @@ -270,6 +270,8 @@ _line_length = 80 # This is set by --extensions flag. _valid_extensions = set(['c', 'h']) +_RE_COMMENTLINE = re.compile(r'^\s*//') + def ParseNolintSuppressions(filename, raw_line, linenum, error): """Updates the global list of error-suppressions. @@ -1358,7 +1360,9 @@ def CheckForOldStyleComments(filename, line, linenum, error): linenum: The number of the line to check. error: The function to call with any errors found. """ - if line.find('/*') >= 0 and line[-1] != '\\': + # hack: allow /* inside comment line. Could be extended to allow them inside + # any // comment. + if line.find('/*') >= 0 and line[-1] != '\\' and not _RE_COMMENTLINE.match(line): error(filename, linenum, 'readability/old_style_comment', 5, '/*-style comment found, it should be replaced with //-style. ' '/*-style comments are only allowed inside macros. ' diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 9c58ce853b..29ec0207ba 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -703,6 +703,35 @@ ArrayOf(String) nvim_list_runtime_paths(void) return rv; } +/// Find files in runtime directories +/// +/// 'name' can contain wildcards. For example +/// nvim_get_runtime_file("colors/*.vim", true) will return all color +/// scheme files. +/// +/// It is not an error to not find any files. An empty array is returned then. +/// +/// @param name pattern of files to search for +/// @param all whether to return all matches or only the first +/// @return list of absolute paths to the found files +ArrayOf(String) nvim_get_runtime_file(String name, Boolean all) + FUNC_API_SINCE(7) +{ + Array rv = ARRAY_DICT_INIT; + if (!name.data) { + return rv; + } + int flags = DIP_START | (all ? DIP_ALL : 0); + do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv); + return rv; +} + +static void find_runtime_cb(char_u *fname, void *cookie) +{ + Array *rv = (Array *)cookie; + ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); +} + /// Changes the global working directory. /// /// @param dir Directory path -- cgit From c5b812c9eab5397f4c898fe4b0a7a1f186fae82c Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 2 Feb 2020 23:29:33 +0100 Subject: env: try find library dir (like /usr[/local]/lib/nvim) and add it to &rtp --- src/nvim/globals.h | 1 + src/nvim/option.c | 28 ++++++++++++++++++++++++++++ src/nvim/os/env.c | 21 +++++++++++++++------ 3 files changed, 44 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 4741778c14..0d419d202c 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -833,6 +833,7 @@ enum { #ifdef HAVE_PATHDEF extern char *default_vim_dir; extern char *default_vimruntime_dir; +extern char *default_lib_dir; extern char_u *compiled_user; extern char_u *compiled_sys; #endif diff --git a/src/nvim/option.c b/src/nvim/option.c index 37c0928d86..15ff8414ce 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -498,6 +498,24 @@ static inline char *add_dir(char *dest, const char *const dir, return dest; } +char *get_lib_dir(void) +{ + // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty + // in an appimage build + if (strlen(default_lib_dir) != 0 + && os_isdir((const char_u *)default_lib_dir)) { + return xstrdup(default_lib_dir); + } + + // Find library path relative to the nvim binary: ../lib/nvim/ + char exe_name[MAXPATHL]; + vim_get_prefix_from_exepath(exe_name); + if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) { + return xstrdup(exe_name); + } + return NULL; +} + /// Sets &runtimepath to default value. /// /// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing @@ -508,6 +526,7 @@ static void set_runtimepath_default(void) char *const data_home = stdpaths_get_xdg_var(kXDGDataHome); char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome); char *const vimruntime = vim_getenv("VIMRUNTIME"); + char *const libdir = get_lib_dir(); char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs); char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs); #define SITE_SIZE (sizeof("site") - 1) @@ -515,6 +534,7 @@ static void set_runtimepath_default(void) size_t data_len = 0; size_t config_len = 0; size_t vimruntime_len = 0; + size_t libdir_len = 0; if (data_home != NULL) { data_len = strlen(data_home); if (data_len != 0) { @@ -544,6 +564,12 @@ static void set_runtimepath_default(void) rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1; } } + if (libdir != NULL) { + libdir_len = strlen(libdir); + if (libdir_len != 0) { + rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1; + } + } rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1, AFTER_SIZE + 1); rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1, @@ -562,6 +588,7 @@ static void set_runtimepath_default(void) true); rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone, NULL, 0, NULL, 0); + rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0); rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, "after", AFTER_SIZE, false); rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome, @@ -583,6 +610,7 @@ static void set_runtimepath_default(void) xfree(data_home); xfree(config_home); xfree(vimruntime); + xfree(libdir); } #undef NVIM_SIZE diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index ec266796a8..082ad58223 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -847,6 +847,20 @@ const void *vim_env_iter_rev(const char delim, } } + +/// @param[out] exe_name should be at least MAXPATHL in size +void vim_get_prefix_from_exepath(char *exe_name) +{ + // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]" + // but c_grammar.lua does not recognize it (yet). + xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), + MAXPATHL * sizeof(*exe_name)); + char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "nvim.exe" + path_end = (char *)path_tail((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "bin/" +} + /// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME, /// allowing the user to override the Nvim runtime directory at runtime. /// Result must be freed by the caller. @@ -902,12 +916,7 @@ char *vim_getenv(const char *name) char exe_name[MAXPATHL]; // Find runtime path relative to the nvim binary: ../share/nvim/runtime if (vim_path == NULL) { - xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), - sizeof(exe_name)); - char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "nvim.exe" - path_end = (char *)path_tail((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "bin/" + vim_get_prefix_from_exepath(exe_name); if (append_path( exe_name, "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR, -- cgit From 64807303df34318c075a6c9ba1e9ee350135748f Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Fri, 31 Jan 2020 23:40:02 +0100 Subject: build: include tree-sitter-c parser in bundled build --- src/nvim/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 089dd537e9..29427c7d08 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -546,6 +546,11 @@ else() endif() set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps) +file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${PROJECT_BINARY_DIR}/lib/nvim/) +install(DIRECTORY ${PROJECT_BINARY_DIR}/lib/nvim/ + DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/ + USE_SOURCE_PERMISSIONS) + add_library( libnvim STATIC -- cgit From 00c57c98dfb2df58875a3d9ad8fc557ec9a24cba Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 25 Jan 2020 13:43:41 +0100 Subject: treesitter: add standard &rtp/parser/ search path for parsers --- src/nvim/lua/executor.c | 5 ++++- src/nvim/lua/treesitter.c | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 242d4e18d1..9a8347cf19 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1025,9 +1025,12 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, create_tslua_parser); lua_setfield(lstate, -2, "_create_ts_parser"); - lua_pushcfunction(lstate, tslua_register_lang); + lua_pushcfunction(lstate, tslua_add_language); lua_setfield(lstate, -2, "_ts_add_language"); + lua_pushcfunction(lstate, tslua_has_language); + lua_setfield(lstate, -2, "_ts_has_language"); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 874fabd89f..a420f79ffd 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -119,7 +119,14 @@ void tslua_init(lua_State *L) build_meta(L, "treesitter_querycursor", querycursor_meta); } -int tslua_register_lang(lua_State *L) +int tslua_has_language(lua_State *L) +{ + const char *lang_name = luaL_checkstring(L, 1); + lua_pushboolean(L, pmap_has(cstr_t)(langs, lang_name)); + return 1; +} + +int tslua_add_language(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); -- cgit From ef2e6522c53d562928060a4872020fb8f32c8ff8 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 6 Feb 2020 13:41:57 +0100 Subject: tests: bail out on libdir just like $VIMRUNTIME, it cannot be calculated --- src/nvim/api/vim.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 29ec0207ba..30fc48fea5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -732,6 +732,11 @@ static void find_runtime_cb(char_u *fname, void *cookie) ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname))); } +String nvim__get_lib_dir(void) +{ + return cstr_as_string(get_lib_dir()); +} + /// Changes the global working directory. /// /// @param dir Directory path -- cgit From 712298e1d3a8567b9857fa90beaa76e8922452c5 Mon Sep 17 00:00:00 2001 From: Matthieu Coudron Date: Fri, 7 Feb 2020 17:47:58 +0100 Subject: build: allow to skip treesitter C parser install if USE_BUNDLED_TS_PARSERS is set to off, don't try to install the parser. Distribs can install treesitter parsers directly into $CMAKE_LIBRARY_PATH/nvim (and users anywhere in rtp). Also fix the URL. --- src/nvim/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 29427c7d08..b2ae3a814d 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -546,7 +546,11 @@ else() endif() set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps) -file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${PROJECT_BINARY_DIR}/lib/nvim/) +# install treesitter parser if bundled +if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) + file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${PROJECT_BINARY_DIR}/lib/nvim/) +endif() + install(DIRECTORY ${PROJECT_BINARY_DIR}/lib/nvim/ DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/ USE_SOURCE_PERMISSIONS) -- cgit From 70c212e4808ca36279c65b630ff1d231e75349d3 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 8 Feb 2020 20:48:22 +0100 Subject: vim-patch:8.2.0235: draw error when an empty group is removed from 'statusline' Problem: Draw error when an empty group is removed from 'statusline'. Solution: Do not use highlighting from a removed group. https://github.com/vim/vim/commit/dbe5d361feb65137099644329cf0ecfd4a945a14 --- src/nvim/buffer.c | 6 ++++++ src/nvim/testdir/test_statusline.vim | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) (limited to 'src') diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5083780719..19d0cac2d3 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3531,6 +3531,12 @@ int build_stl_str_hl( if (n == curitem && group_start_userhl == group_end_userhl) { out_p = t; group_len = 0; + // do not use the highlighting from the removed group + for (n = groupitems[groupdepth] + 1; n < curitem; n++) { + if (items[n].type == Highlight) { + items[n].type = Empty; + } + } } } diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 48ec777ffd..66b6e6c05c 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -369,3 +369,24 @@ func Test_statusline_visual() bwipe! x1 bwipe! x2 endfunc + +func Test_statusline_removed_group() + if !CanRunVimInTerminal() + throw 'Skipped: cannot make screendumps' + endif + + let lines =<< trim END + scriptencoding utf-8 + set laststatus=2 + let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡' + END + call writefile(lines, 'XTest_statusline') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 10, 'cols': 50}) + call term_wait(buf, 100) + call VerifyScreenDump(buf, 'Test_statusline_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_statusline') +endfunc -- cgit From ba3778f83b5de2ab670f0fbf5ed9e889e5aefe6e Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 9 Feb 2020 14:20:48 +0100 Subject: build: always create build/lib/nvim so the install command doesn't fail --- src/nvim/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index b2ae3a814d..aec258d2fc 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -23,6 +23,7 @@ endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) +set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/) set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua) set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua) set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua) @@ -546,12 +547,14 @@ else() endif() set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps) +file(MAKE_DIRECTORY ${BINARY_LIB_DIR}) + # install treesitter parser if bundled if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) - file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${PROJECT_BINARY_DIR}/lib/nvim/) + file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR}) endif() -install(DIRECTORY ${PROJECT_BINARY_DIR}/lib/nvim/ +install(DIRECTORY ${BINARY_LIB_DIR} DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/ USE_SOURCE_PERMISSIONS) -- cgit From 6c5bbf07d988ef55e5e8ba8d70b62c1f0885261b Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Mon, 10 Feb 2020 00:33:26 -0800 Subject: eval.c: factor out eval/funcs.c #11828 close #11828 ref #5081 cf. vim patch 7.4.2063 --- src/nvim/CMakeLists.txt | 5 +- src/nvim/eval.c | 13617 +++++----------------------------------------- src/nvim/eval.h | 116 +- src/nvim/eval/funcs.c | 10920 +++++++++++++++++++++++++++++++++++++ src/nvim/eval/funcs.h | 24 + src/nvim/globals.h | 8 + src/nvim/lua/executor.c | 2 +- 7 files changed, 12359 insertions(+), 12333 deletions(-) create mode 100644 src/nvim/eval/funcs.c create mode 100644 src/nvim/eval/funcs.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index aec258d2fc..2bfa193a63 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -150,6 +150,7 @@ set(CONV_SOURCES diff.c edit.c eval.c + eval/funcs.c ex_cmds.c ex_docmd.c fileio.c @@ -180,10 +181,10 @@ if(NOT MSVC) check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE) if(HAS_WSTATIC_IN_INLINE) set_source_files_properties( - eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") + eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") else() set_source_files_properties( - eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") + eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") endif() # tree-sitter: inlined external project, we don't maintain it. #10124 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 47c094f49d..0dceca671b 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5,111 +5,52 @@ * eval.c: Expression evaluation. */ -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include "nvim/assert.h" -#include "nvim/vim.h" -#include "nvim/ascii.h" #ifdef HAVE_LOCALE_H # include #endif -#include "nvim/eval.h" + +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" -#include "nvim/context.h" #include "nvim/cursor.h" -#include "nvim/diff.h" #include "nvim/edit.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval.h" +#include "nvim/eval/encode.h" +#include "nvim/eval/executor.h" +#include "nvim/eval/gc.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" +#include "nvim/ex_session.h" #include "nvim/fileio.h" -#include "nvim/os/fileio.h" -#include "nvim/func_attr.h" -#include "nvim/fold.h" #include "nvim/getchar.h" -#include "nvim/hashtab.h" -#include "nvim/iconv.h" -#include "nvim/if_cscope.h" -#include "nvim/indent_c.h" -#include "nvim/indent.h" +#include "nvim/lua/executor.h" #include "nvim/mark.h" -#include "nvim/math.h" -#include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/menu.h" -#include "nvim/message.h" #include "nvim/misc1.h" -#include "nvim/keymap.h" -#include "nvim/map.h" -#include "nvim/file_search.h" -#include "nvim/garray.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/os_unix.h" +#include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" #include "nvim/path.h" -#include "nvim/popupmnu.h" -#include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/ex_session.h" -#include "nvim/sha256.h" #include "nvim/sign.h" -#include "nvim/spell.h" -#include "nvim/state.h" -#include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/tag.h" #include "nvim/ui.h" -#include "nvim/main.h" -#include "nvim/mouse.h" -#include "nvim/terminal.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" -#include "nvim/eval/encode.h" -#include "nvim/eval/decode.h" -#include "nvim/os/os.h" -#include "nvim/event/libuv_process.h" -#include "nvim/os/pty_process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" -#include "nvim/event/time.h" -#include "nvim/os/time.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/server.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" -#include "nvim/os/dl.h" -#include "nvim/os/input.h" -#include "nvim/event/loop.h" -#include "nvim/lib/kvec.h" -#include "nvim/lib/khash.h" -#include "nvim/lib/queue.h" -#include "nvim/lua/executor.h" -#include "nvim/eval/typval.h" -#include "nvim/eval/executor.h" -#include "nvim/eval/gc.h" -#include "nvim/macros.h" + // TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead @@ -118,61 +59,9 @@ // Character used as separator in autoload function/variable names. #define AUTOLOAD_CHAR '#' -/* - * Structure returned by get_lval() and used by set_var_lval(). - * For a plain name: - * "name" points to the variable name. - * "exp_name" is NULL. - * "tv" is NULL - * For a magic braces name: - * "name" points to the expanded variable name. - * "exp_name" is non-NULL, to be freed later. - * "tv" is NULL - * For an index in a list: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the (first) list item value - * "li" points to the (first) list item - * "range", "n1", "n2" and "empty2" indicate what items are used. - * For an existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the dict item value - * "newkey" is NULL - * For a non-existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the Dictionary typval_T - * "newkey" is the key for the new item. - */ -typedef struct lval_S { - const char *ll_name; ///< Start of variable name (can be NULL). - size_t ll_name_len; ///< Length of the .ll_name. - char *ll_exp_name; ///< NULL or expanded name in allocated memory. - typval_T *ll_tv; ///< Typeval of item being used. If "newkey" - ///< isn't NULL it's the Dict to which to add the item. - listitem_T *ll_li; ///< The list item or NULL. - list_T *ll_list; ///< The list or NULL. - int ll_range; ///< TRUE when a [i:j] range was used. - long ll_n1; ///< First index for list. - long ll_n2; ///< Second index for list range. - int ll_empty2; ///< Second index is empty: [i:]. - dict_T *ll_dict; ///< The Dictionary or NULL. - dictitem_T *ll_di; ///< The dictitem or NULL. - char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL. -} lval_T; - static char *e_letunexp = N_("E18: Unexpected characters in :let"); static char *e_missbrac = N_("E111: Missing ']'"); -static char *e_listarg = N_("E686: Argument of %s must be a List"); -static char *e_listdictarg = N_( - "E712: Argument of %s must be a List or Dictionary"); -static char *e_listreq = N_("E714: List required"); -static char *e_dictreq = N_("E715: Dictionary required"); -static char *e_stringreq = N_("E928: String required"); -static char *e_toomanyarg = N_("E118: Too many arguments for function: %s"); -static char *e_dictkey = N_("E716: Key not present in Dictionary: %s"); static char *e_funcexts = N_( "E122: Function %s already exists, add ! to replace it"); static char *e_funcdict = N_("E717: Dictionary entry already exists"); @@ -181,8 +70,6 @@ static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); -static const char *e_readonlyvar = N_( - "E46: Cannot change read-only variable \"%.*s\""); // TODO(ZyX-I): move to eval/executor static char *e_letwrong = N_("E734: Wrong variable type for %s="); @@ -221,32 +108,6 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ -/// Describe data to return from find_some_match() -typedef enum { - kSomeMatch, ///< Data for match(). - kSomeMatchEnd, ///< Data for matchend(). - kSomeMatchList, ///< Data for matchlist(). - kSomeMatchStr, ///< Data for matchstr(). - kSomeMatchStrPos, ///< Data for matchstrpos(). -} SomeMatchType; - -/// trans_function_name() flags -typedef enum { - TFN_INT = 1, ///< May use internal function name - TFN_QUIET = 2, ///< Do not emit error messages. - TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading. - TFN_NO_DEREF = 8, ///< Do not dereference a Funcref. - TFN_READ_ONLY = 16, ///< Will not change the variable. -} TransFunctionNameFlags; - -/// get_lval() flags -typedef enum { - GLV_QUIET = TFN_QUIET, ///< Do not emit error messages. - GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading. - GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change - ///< the value (prevents error message). -} GetLvalFlags; - // flags used in uf_flags #define FC_ABORT 0x01 // abort function on error #define FC_RANGE 0x02 // function accepts range @@ -290,13 +151,6 @@ struct funccall_S { garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". }; -///< Structure used by trans_function_name() -typedef struct { - dict_T *fd_dict; ///< Dictionary used. - char_u *fd_newkey; ///< New key in "dict" in allocated memory. - dictitem_T *fd_di; ///< Dictionary item used. -} funcdict_T; - /* * Info used by a ":for" loop. */ @@ -446,71 +300,13 @@ static partial_T *vvlua_partial; /// v: hashtab #define vimvarht vimvardict.dv_hashtab -typedef struct { - TimeWatcher tw; - int timer_id; - int repeat_count; - int refcount; - int emsg_count; ///< Errors in a repeating timer. - long timeout; - bool stopped; - bool paused; - Callback callback; -} timer_T; - -typedef void (*FunPtr)(void); - -/// Prototype of C function that implements VimL function -typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); - -/// Structure holding VimL function definition -typedef struct fst { - char *name; ///< Name of the function. - uint8_t min_argc; ///< Minimal number of arguments. - uint8_t max_argc; ///< Maximal number of arguments. - VimLFunc func; ///< Function implementation. - FunPtr data; ///< Userdata for function implementation. -} VimLFuncDef; - -KHASH_MAP_INIT_STR(functions, VimLFuncDef) - -/// Type of assert_* check being performed -typedef enum -{ - ASSERT_EQUAL, - ASSERT_NOTEQUAL, - ASSERT_MATCH, - ASSERT_NOTMATCH, - ASSERT_INRANGE, - ASSERT_OTHER, -} assert_type_T; - -/// Type for dict_list function -typedef enum { - kDictListKeys, ///< List dictionary keys. - kDictListValues, ///< List dictionary values. - kDictListItems, ///< List dictionary contents: [keys, values]. -} DictListType; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval.c.generated.h" #endif -#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 uint64_t last_timer_id = 1; static PMap(uint64_t) *timers = NULL; -/// Dummy va_list for passing to vim_snprintf -/// -/// Used because: -/// - passing a NULL pointer doesn't work when va_list isn't a pointer -/// - locally in the function results in a "used before set" warning -/// - using va_start() to initialize it gives "function with fixed args" error -static va_list dummy_ap; - static const char *const msgpack_type_names[] = { [kMPNil] = "nil", [kMPBoolean] = "boolean", @@ -1018,8 +814,8 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) return ret; } -static int eval_expr_typval(const typval_T *expr, typval_T *argv, - int argc, typval_T *rettv) +int eval_expr_typval(const typval_T *expr, typval_T *argv, + int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int dummy; @@ -1064,7 +860,7 @@ static int eval_expr_typval(const typval_T *expr, typval_T *argv, /// Like eval_to_bool() but using a typval_T instead of a string. /// Works for string, funcref and partial. -static bool eval_expr_to_bool(const typval_T *expr, bool *error) +bool eval_expr_to_bool(const typval_T *expr, bool *error) FUNC_ATTR_NONNULL_ARG(1, 2) { typval_T argv, rettv; @@ -1238,7 +1034,7 @@ static void restore_vimvar(int idx, typval_T *save_tv) } /// If there is a window for "curbuf", make it the current window. -static void find_win_for_curbuf(void) +void find_win_for_curbuf(void) { for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win != NULL) { @@ -2276,9 +2072,9 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, /// /// @return A pointer to just after the name, including indexes. Returns NULL /// for a parsing error, but it is still needed to free items in lp. -static char_u *get_lval(char_u *const name, typval_T *const rettv, - lval_T *const lp, const bool unlet, const bool skip, - const int flags, const int fne_flags) +char_u *get_lval(char_u *const name, typval_T *const rettv, + lval_T *const lp, const bool unlet, const bool skip, + const int flags, const int fne_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { dictitem_T *v; @@ -2599,7 +2395,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, /* * Clear lval "lp" that was filled by get_lval(). */ -static void clear_lval(lval_T *lp) +void clear_lval(lval_T *lp) { xfree(lp->ll_exp_name); xfree(lp->ll_newkey); @@ -3583,7 +3379,7 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) * * Return OK or FAIL. */ -static int eval1(char_u **arg, typval_T *rettv, int evaluate) +int eval1(char_u **arg, typval_T *rettv, int evaluate) { int result; typval_T var2; @@ -4887,8 +4683,8 @@ eval_index( /// @param[in] evaluate If not true, rettv is not populated. /// /// @return OK or FAIL. -static int get_option_tv(const char **const arg, typval_T *const rettv, - const bool evaluate) +int get_option_tv(const char **const arg, typval_T *const rettv, + const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { long numval; @@ -6227,88 +6023,6 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -#ifdef INCLUDE_GENERATED_DECLARATIONS - -#ifdef _MSC_VER -// This prevents MSVC from replacing the functions with intrinsics, -// and causing errors when trying to get their addresses in funcs.generated.h -#pragma function (ceil) -#pragma function (floor) -#endif - -PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES -# include "funcs.generated.h" -PRAGMA_DIAG_POP -#endif - -/* - * Function given to ExpandGeneric() to obtain the list of internal - * or user defined function names. - */ -char_u *get_function_name(expand_T *xp, int idx) -{ - static int intidx = -1; - char_u *name; - - if (idx == 0) - intidx = -1; - if (intidx < 0) { - name = get_user_func_name(xp, idx); - if (name != NULL) - return name; - } - while ( (size_t)++intidx < ARRAY_SIZE(functions) - && functions[intidx].name[0] == '\0') { - } - - if ((size_t)intidx >= ARRAY_SIZE(functions)) { - return NULL; - } - - const char *const key = functions[intidx].name; - const size_t key_len = strlen(key); - memcpy(IObuff, key, key_len); - IObuff[key_len] = '('; - if (functions[intidx].max_argc == 0) { - IObuff[key_len + 1] = ')'; - IObuff[key_len + 2] = NUL; - } else { - IObuff[key_len + 1] = NUL; - } - return IObuff; -} - -/* - * Function given to ExpandGeneric() to obtain the list of internal or - * user defined variable or function names. - */ -char_u *get_expr_name(expand_T *xp, int idx) -{ - static int intidx = -1; - char_u *name; - - if (idx == 0) - intidx = -1; - if (intidx < 0) { - name = get_function_name(xp, idx); - if (name != NULL) - return name; - } - return get_user_var_name(xp, ++intidx); -} - -/// Find internal function in hash functions -/// -/// @param[in] name Name of the function. -/// -/// Returns pointer to the function definition or NULL if not found. -static const VimLFuncDef *find_internal_func(const char *const name) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL -{ - size_t len = strlen(name); - return find_internal_func_gperf(name, len); -} - /// Return name of the function corresponding to `name` /// /// If `name` points to variable that is either a function or partial then @@ -6715,7 +6429,7 @@ call_func( emsg_funcname(N_("E933: Function was deleted: %s"), name); break; case ERROR_TOOMANY: - emsg_funcname(e_toomanyarg, name); + emsg_funcname(_(e_toomanyarg), name); break; case ERROR_TOOFEW: emsg_funcname(N_("E119: Not enough arguments for function: %s"), @@ -6762,193 +6476,9 @@ static void emsg_funcname(char *ermsg, const char_u *name) } } -/* - * Return TRUE for a non-zero Number and a non-empty String. - */ -static int non_zero_arg(typval_T *argvars) -{ - return ((argvars[0].v_type == VAR_NUMBER - && argvars[0].vval.v_number != 0) - || (argvars[0].v_type == VAR_SPECIAL - && argvars[0].vval.v_special == kSpecialVarTrue) - || (argvars[0].v_type == VAR_STRING - && argvars[0].vval.v_string != NULL - && *argvars[0].vval.v_string != NUL)); -} - -/********************************************* - * Implementation of the built-in functions - */ - - -// Apply a floating point C function on a typval with one float_T. -// -// Some versions of glibc on i386 have an optimization that makes it harder to -// call math functions indirectly from inside an inlined function, causing -// compile-time errors. Avoid `inline` in that case. #3072 -static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T f; - float_T (*function)(float_T) = (float_T (*)(float_T))fptr; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &f)) { - rettv->vval.v_float = function(f); - } else { - rettv->vval.v_float = 0.0; - } -} - -static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; - - Array args = ARRAY_DICT_INIT; - - for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) { - ADD(args, vim_to_object(tv)); - } - - Error err = ERROR_INIT; - Object result = fn(VIML_INTERNAL_CALL, args, &err); - - if (ERROR_SET(&err)) { - emsgf_multiline((const char *)e_api_error, err.msg); - goto end; - } - - if (!object_to_vim(result, rettv, &err)) { - EMSG2(_("Error converting the call result: %s"), err.msg); - } - -end: - api_free_array(args); - api_free_object(result); - api_clear_error(&err); -} - -/* - * "abs(expr)" function - */ -static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_FLOAT) { - float_op_wrapper(argvars, rettv, (FunPtr)&fabs); - } else { - varnumber_T n; - bool error = false; - - n = tv_get_number_chk(&argvars[0], &error); - if (error) { - rettv->vval.v_number = -1; - } else if (n > 0) { - rettv->vval.v_number = n; - } else { - rettv->vval.v_number = -n; - } - } -} - -/* - * "add(list, item)" function - */ -static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = 1; // Default: failed. - if (argvars[0].v_type == VAR_LIST) { - list_T *const l = argvars[0].vval.v_list; - if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { - tv_list_append_tv(l, &argvars[1]); - tv_copy(&argvars[0], rettv); - } - } else { - EMSG(_(e_listreq)); - } -} - -/* - * "and(expr, expr)" function - */ -static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) - & tv_get_number_chk(&argvars[1], NULL); -} - - -/// "api_info()" function -static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - Dictionary metadata = api_metadata(); - (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); - api_free_dictionary(metadata); -} - -// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(&argvars[0]); - - set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); -} - -// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, true, &argvars[2], rettv); - } -} - -static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - // use the current window - rettv->vval.v_number = ARGCOUNT; - } else if (argvars[0].v_type == VAR_NUMBER - && tv_get_number(&argvars[0]) == -1) { - // use the global argument list - rettv->vval.v_number = GARGCOUNT; - } else { - // use the argument list of the specified window - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp != NULL) { - rettv->vval.v_number = WARGCOUNT(wp); - } else { - rettv->vval.v_number = -1; - } - } -} - -/* - * "argidx()" function - */ -static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = curwin->w_arg_idx; -} - -/// "arglistid" function -static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - win_T *wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp != NULL) { - rettv->vval.v_number = wp->w_alist->id; - } -} - /// Get the argument list for a given window -static void get_arglist_as_rettv(aentry_T *arglist, int argcount, - typval_T *rettv) +void get_arglist_as_rettv(aentry_T *arglist, int argcount, + typval_T *rettv) { tv_list_alloc_ret(rettv, argcount); if (arglist != NULL) { @@ -6959,46 +6489,8 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount, } } -/* - * "argv(nr)" function - */ -static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - aentry_T *arglist = NULL; - int argcount = -1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type == VAR_UNKNOWN) { - arglist = ARGLIST; - argcount = ARGCOUNT; - } else if (argvars[1].v_type == VAR_NUMBER - && tv_get_number(&argvars[1]) == -1) { - arglist = GARGLIST; - argcount = GARGCOUNT; - } else { - win_T *wp = find_win_by_nr_or_id(&argvars[1]); - if (wp != NULL) { - // Use the argument list of the specified window - arglist = WARGLIST(wp); - argcount = WARGCOUNT(wp); - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - int idx = tv_get_number_chk(&argvars[0], NULL); - if (arglist != NULL && idx >= 0 && idx < argcount) { - rettv->vval.v_string = (char_u *)xstrdup( - (const char *)alist_name(&arglist[idx])); - } else if (idx == -1) { - get_arglist_as_rettv(arglist, argcount, rettv); - } - } else { - get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); - } -} - // Prepare "gap" for an assert error and add the sourcing position. -static void prepare_assert_error(garray_T *gap) +void prepare_assert_error(garray_T *gap) { char buf[NUMBUFLEN]; @@ -7052,9 +6544,9 @@ static void ga_concat_esc(garray_T *gap, char_u *str) } // Fill "gap" with information about an assert error. -static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, - char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv, assert_type_T atype) +void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, + char_u *exp_str, typval_T *exp_tv, + typval_T *got_tv, assert_type_T atype) { char_u *tofree; @@ -7096,7 +6588,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, } // Add an assert error to v:errors. -static void assert_error(garray_T *gap) +void assert_error(garray_T *gap) { struct vimvar *vp = &vimvars[VV_ERRORS]; @@ -7108,7 +6600,7 @@ static void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -static int assert_equal_common(typval_T *argvars, assert_type_T atype) +int assert_equal_common(typval_T *argvars, assert_type_T atype) FUNC_ATTR_NONNULL_ALL { garray_T ga; @@ -7125,7 +6617,7 @@ static int assert_equal_common(typval_T *argvars, assert_type_T atype) return 0; } -static int assert_equalfile(typval_T *argvars) +int assert_equalfile(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -7179,68 +6671,86 @@ static int assert_equalfile(typval_T *argvars) return 0; } -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; + bool error = false; + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); assert_error(&ga); ga_clear(&ga); - ret = 1; + return 1; } - - suppress_errthrow = false; - emsg_on_display = false; - rettv->vval.v_number = ret; -} - -// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); + return 0; } -// "assert_equalfile(fname-one, fname-two)" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +// Common for assert_true() and assert_false(). +int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL { - rettv->vval.v_number = assert_equalfile(argvars); -} + bool error = false; + garray_T ga; -// "assert_notequal(expected, actual[, msg])" function -static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); + if ((argvars[0].v_type != VAR_NUMBER + || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true + || error) + && (argvars[0].v_type != VAR_SPECIAL + || (argvars[0].vval.v_special + != (SpecialVarValue) (is_true + ? kSpecialVarTrue + : kSpecialVarFalse)))) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], + (char_u *)(is_true ? "True" : "False"), + NULL, &argvars[0], ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; } -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_exception(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { - garray_T ga; + garray_T ga; + const char *const error = tv_get_string_chk(&argvars[0]); + if (vimvars[VV_EXCEPTION].vv_str == NULL) { prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0])); + ga_concat(&ga, (char_u *)"v:exception is not set"); assert_error(&ga); ga_clear(&ga); - rettv->vval.v_number = 1; -} - -/// "assert_exception(string[, msg])" function -static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_exception(argvars); + return 1; + } else if (error != NULL + && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; } -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +int assert_fails(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; @@ -7289,94 +6799,10 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg_silent = false; emsg_on_display = false; set_vim_var_string(VV_ERRMSG, NULL, 0); - rettv->vval.v_number = ret; + return ret; } -static int assert_inrange(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); - - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -// Common for assert_true() and assert_false(). -static int assert_bool(typval_T *argvars, bool is_true) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - garray_T ga; - - if ((argvars[0].v_type != VAR_NUMBER - || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true - || error) - && (argvars[0].v_type != VAR_SPECIAL - || (argvars[0].vval.v_special - != (SpecialVarValue) (is_true - ? kSpecialVarTrue - : kSpecialVarFalse)))) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), - NULL, &argvars[0], ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -static int assert_exception(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, (char_u *)"v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - return 1; - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, false); -} - -static int assert_match_common(typval_T *argvars, assert_type_T atype) +int assert_match_common(typval_T *argvars, assert_type_T atype) FUNC_ATTR_NONNULL_ALL { char buf1[NUMBUFLEN]; @@ -7398,355 +6824,6 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype) return 0; } -/// "assert_inrange(lower, upper[, msg])" function -static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_inrange(argvars); -} - -/// "assert_match(pattern, actual[, msg])" function -static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); -} - -/// "assert_notmatch(pattern, actual[, msg])" function -static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); -} - -// "assert_true(actual[, msg])" function -static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, true); -} - -/* - * "atan2()" function - */ -static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = atan2(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "browse(save, title, initdir, default)" function - */ -static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; -} - -/* - * "browsedir(title, initdir)" function - */ -static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - f_browse(argvars, rettv, NULL); -} - - -/* - * Find a buffer by number or exact name. - */ -static buf_T *find_buffer(typval_T *avar) -{ - buf_T *buf = NULL; - - if (avar->v_type == VAR_NUMBER) - buf = buflist_findnr((int)avar->vval.v_number); - else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { - buf = buflist_findname_exp(avar->vval.v_string); - if (buf == NULL) { - /* No full path name match, try a match with a URL or a "nofile" - * buffer, these don't use the full path. */ - FOR_ALL_BUFFERS(bp) { - if (bp->b_fname != NULL - && (path_with_url((char *)bp->b_fname) - || bt_nofile(bp) - ) - && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { - buf = bp; - break; - } - } - } - } - return buf; -} - -// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *name = (char_u *)tv_get_string(&argvars[0]); - - rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); -} - -/* - * "bufexists(expr)" function - */ -static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); -} - -/* - * "buflisted(expr)" function - */ -static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_p_bl); -} - -// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - buf_T *buf = get_buf_arg(&argvars[0]); - - if (buf != NULL && buf->b_ml.ml_mfp == NULL) { - aco_save_T aco; - - aucmd_prepbuf(&aco, buf); - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); - } -} - -/* - * "bufloaded(expr)" function - */ -static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); -} - - -/* - * Get buffer by number or pattern. - */ -static buf_T *tv_get_buf(typval_T *tv, int curtab_only) -{ - char_u *name = tv->vval.v_string; - int save_magic; - char_u *save_cpo; - buf_T *buf; - - if (tv->v_type == VAR_NUMBER) - return buflist_findnr((int)tv->vval.v_number); - if (tv->v_type != VAR_STRING) - return NULL; - if (name == NULL || *name == NUL) - return curbuf; - if (name[0] == '$' && name[1] == NUL) - return lastbuf; - - // Ignore 'magic' and 'cpoptions' here to make scripts portable - save_magic = p_magic; - p_magic = TRUE; - save_cpo = p_cpo; - p_cpo = (char_u *)""; - - buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name), - TRUE, FALSE, curtab_only)); - - p_magic = save_magic; - p_cpo = save_cpo; - - // If not found, try expanding the name, like done for bufexists(). - if (buf == NULL) { - buf = find_buffer(tv); - } - - return buf; -} - -/// Get the buffer from "arg" and give an error and return NULL if it is not -/// valid. -static buf_T * get_buf_arg(typval_T *arg) -{ - buf_T *buf; - - emsg_off++; - buf = tv_get_buf(arg, false); - emsg_off--; - if (buf == NULL) { - EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg)); - } - return buf; -} - -/* - * "bufname(expr)" function - */ -static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - if (buf != NULL && buf->b_fname != NULL) { - rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname); - } -} - -/* - * "bufnr(expr)" function - */ -static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - bool error = false; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - // If the buffer isn't found and the second argument is not zero create a - // new buffer. - const char *name; - if (buf == NULL - && argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error) != 0 - && !error - && (name = tv_get_string_chk(&argvars[0])) != NULL) { - buf = buflist_new((char_u *)name, NULL, 1, 0); - } - - if (buf != NULL) { - rettv->vval.v_number = buf->b_fnum; - } -} - -static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) -{ - if (!tv_check_str_or_nr(&argvars[0])) { - rettv->vval.v_number = -1; - return; - } - - emsg_off++; - buf_T *buf = tv_get_buf(&argvars[0], true); - if (buf == NULL) { // no need to search if buffer was not found - rettv->vval.v_number = -1; - goto end; - } - - int winnr = 0; - int winid; - bool found_buf = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (wp->w_buffer == buf) { - found_buf = true; - winid = wp->handle; - break; - } - } - rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); -end: - emsg_off--; -} - -/// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_win_common(argvars, rettv, false); -} - -/// "bufwinnr(nr)" function -static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_win_common(argvars, rettv, true); -} - -/* - * "byte2line(byte)" function - */ -static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long boff = tv_get_number(&argvars[0]) - 1; - if (boff < 0) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0, - &boff, false); - } -} - -static void byteidx(typval_T *argvars, typval_T *rettv, int comp) -{ - const char *const str = tv_get_string_chk(&argvars[0]); - varnumber_T idx = tv_get_number_chk(&argvars[1], NULL); - rettv->vval.v_number = -1; - if (str == NULL || idx < 0) { - return; - } - - const char *t = str; - for (; idx > 0; idx--) { - if (*t == NUL) { // EOL reached. - return; - } - if (enc_utf8 && comp) { - t += utf_ptr2len((const char_u *)t); - } else { - t += (*mb_ptr2len)((const char_u *)t); - } - } - rettv->vval.v_number = (varnumber_T)(t - str); -} - -/* - * "byteidx()" function - */ -static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - byteidx(argvars, rettv, FALSE); -} - -/* - * "byteidxcomp()" function - */ -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - byteidx(argvars, rettv, TRUE); -} - int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { @@ -7778,11522 +6855,1513 @@ func_call_skip_call: return r; } -/// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Find a window: When using a Window ID in any tab page, when using a number +/// in the current tab page. +win_T * find_win_by_nr_or_id(typval_T *vp) { - if (argvars[1].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - if (argvars[1].vval.v_list == NULL) { - return; - } - - char_u *func; - partial_T *partial = NULL; - dict_T *selfdict = NULL; - if (argvars[0].v_type == VAR_FUNC) { - func = argvars[0].vval.v_string; - } else if (argvars[0].v_type == VAR_PARTIAL) { - partial = argvars[0].vval.v_partial; - func = partial_name(partial); - } else { - func = (char_u *)tv_get_string(&argvars[0]); - } - if (*func == NUL) { - return; // type error or empty name - } + int nr = (int)tv_get_number_chk(vp, NULL); - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - selfdict = argvars[2].vval.v_dict; + if (nr >= LOWEST_WIN_ID) { + return win_id2wp(vp); } - func_call(func, &argvars[1], partial, selfdict, rettv); + return find_win_by_nr(vp, NULL); } /* - * "changenr()" function + * Implementation of map() and filter(). */ -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) +void filter_map(typval_T *argvars, typval_T *rettv, int map) { - 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; - } + typval_T *expr; + list_T *l = NULL; + dictitem_T *di; + hashtab_T *ht; + hashitem_T *hi; + dict_T *d = NULL; + typval_T save_val; + typval_T save_key; + int rem = false; + int todo; + char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); + const char *const arg_errmsg = (map + ? N_("map() argument") + : N_("filter() argument")); + int save_did_emsg; + int idx = 0; - 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); + if (argvars[0].v_type == VAR_LIST) { + tv_copy(&argvars[0], rettv); + if ((l = argvars[0].vval.v_list) == NULL + || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, + TV_TRANSLATE))) { return; } - } - const char *error; - rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error); - if (!rettv->vval.v_number) { - EMSG(error); - } -} - -// "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 - */ -static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[1].v_type != VAR_UNKNOWN) { - if (!tv_check_num(&argvars[1])) { + } else if (argvars[0].v_type == VAR_DICT) { + tv_copy(&argvars[0], rettv); + if ((d = argvars[0].vval.v_dict) == NULL + || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; } + } else { + EMSG2(_(e_listdictarg), ermsg); + return; } - rettv->vval.v_number = utf_ptr2char( - (const char_u *)tv_get_string(&argvars[0])); -} + expr = &argvars[1]; + // On type errors, the preceding call has already displayed an error + // message. Avoid a misleading error message for an empty string that + // was not passed as argument. + if (expr->v_type != VAR_UNKNOWN) { + prepare_vimvar(VV_VAL, &save_val); -/* - * "cindent(lnum)" function - */ -static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - pos_T pos; - linenr_T lnum; + // We reset "did_emsg" to be able to detect whether an error + // occurred during evaluation of the expression. + save_did_emsg = did_emsg; + did_emsg = FALSE; - pos = curwin->w_cursor; - lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_c_indent(); - curwin->w_cursor = pos; - } else - rettv->vval.v_number = -1; -} + prepare_vimvar(VV_KEY, &save_key); + if (argvars[0].v_type == VAR_DICT) { + vimvars[VV_KEY].vv_type = VAR_STRING; -/* - * "clearmatches()" function - */ -static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - clear_matches(curwin); -} + ht = &d->dv_hashtab; + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; -/* - * "col(string)" function - */ -static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) { - if (fp->col == MAXCOL) { - /* '> can be MAXCOL, get the length of the line then */ - if (fp->lnum <= curbuf->b_ml.ml_line_count) - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - else - col = MAXCOL; + di = TV_DICT_HI2DI(hi); + if (map + && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) + || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { + break; + } + + vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); + int r = filter_map_one(&di->di_tv, expr, map, &rem); + tv_clear(&vimvars[VV_KEY].vv_tv); + if (r == FAIL || did_emsg) { + break; + } + if (!map && rem) { + if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) + || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { + break; + } + tv_dict_item_remove(d, di); + } + } + } + hash_unlock(ht); } else { - col = fp->col + 1; - /* col(".") when the cursor is on the NUL at the end of the line - * because of "coladd" can be seen as an extra column. */ - if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); - - if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, - curwin->w_virtcol - curwin->w_cursor.coladd)) { - int l; - - if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) - col += l; + assert(argvars[0].v_type == VAR_LIST); + vimvars[VV_KEY].vv_type = VAR_NUMBER; + + for (listitem_T *li = tv_list_first(l); li != NULL;) { + if (map + && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { + break; } + vimvars[VV_KEY].vv_nr = idx; + if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL + || did_emsg) { + break; + } + if (!map && rem) { + li = tv_list_item_remove(l, li); + } else { + li = TV_LIST_ITEM_NEXT(l, li); + } + idx++; } } - } - rettv->vval.v_number = col; -} - -/* - * "complete()" function - */ -static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if ((State & INSERT) == 0) { - EMSG(_("E785: complete() can only be used in Insert mode")); - return; - } - - /* Check for undo allowed here, because if something was already inserted - * the line was already saved for undo and this check isn't done. */ - if (!undo_allowed()) - return; - if (argvars[1].v_type != VAR_LIST) { - EMSG(_(e_invarg)); - return; - } + restore_vimvar(VV_KEY, &save_key); + restore_vimvar(VV_VAL, &save_val); - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol <= 0) { - return; + did_emsg |= save_did_emsg; } - - set_completion(startcol - 1, argvars[1].vval.v_list); -} - -/* - * "complete_add()" function - */ -static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); -} - -/* - * "complete_check()" function - */ -static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int saved = RedrawingDisabled; - - RedrawingDisabled = 0; - ins_compl_check_keys(0, true); - rettv->vval.v_number = compl_interrupted; - RedrawingDisabled = saved; } -// "complete_info()" function -static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) + FUNC_ATTR_NONNULL_ARG(1, 2) { - tv_dict_alloc_ret(rettv); + typval_T rettv; + typval_T argv[3]; + int retval = FAIL; - list_T *what_list = NULL; + tv_copy(tv, &vimvars[VV_VAL].vv_tv); + argv[0] = vimvars[VV_KEY].vv_tv; + argv[1] = vimvars[VV_VAL].vv_tv; + if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { + goto theend; + } + if (map) { + // map(): replace the list item value. + tv_clear(tv); + rettv.v_lock = 0; + *tv = rettv; + } else { + bool error = false; - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; + // filter(): when expr is zero remove the item + *remp = (tv_get_number_chk(&rettv, &error) == 0); + tv_clear(&rettv); + // On type error, nothing has been removed; return FAIL to stop the + // loop. The error message was given by tv_get_number_chk(). + if (error) { + goto theend; } - what_list = argvars[0].vval.v_list; } - get_complete_info(what_list, rettv->vval.v_dict); + retval = OK; +theend: + tv_clear(&vimvars[VV_VAL].vv_tv); + return retval; } -/* - * "confirm(message, buttons[, default [, type]])" function - */ -static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void common_function(typval_T *argvars, typval_T *rettv, + bool is_funcref, FunPtr fptr) { - char buf[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *message; - const char *buttons = NULL; - int def = 1; - int type = VIM_GENERIC; - const char *typestr; - bool error = false; - - message = tv_get_string_chk(&argvars[0]); - if (message == NULL) { - error = true; - } - if (argvars[1].v_type != VAR_UNKNOWN) { - buttons = tv_get_string_buf_chk(&argvars[1], buf); - if (buttons == NULL) { - error = true; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - def = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - typestr = tv_get_string_buf_chk(&argvars[3], buf2); - if (typestr == NULL) { - error = true; - } else { - switch (TOUPPER_ASC(*typestr)) { - case 'E': type = VIM_ERROR; break; - case 'Q': type = VIM_QUESTION; break; - case 'I': type = VIM_INFO; break; - case 'W': type = VIM_WARNING; break; - case 'G': type = VIM_GENERIC; break; - } - } - } - } - } + char_u *s; + char_u *name; + bool use_string = false; + partial_T *arg_pt = NULL; + char_u *trans_name = NULL; - if (buttons == NULL || *buttons == NUL) { - buttons = _("&Ok"); + if (argvars[0].v_type == VAR_FUNC) { + // function(MyFunc, [arg], dict) + s = argvars[0].vval.v_string; + } else if (argvars[0].v_type == VAR_PARTIAL + && argvars[0].vval.v_partial != NULL) { + // function(dict.MyFunc, [arg]) + arg_pt = argvars[0].vval.v_partial; + s = partial_name(arg_pt); + } else { + // function('MyFunc', [arg], dict) + s = (char_u *)tv_get_string(&argvars[0]); + use_string = true; } - if (!error) { - rettv->vval.v_number = do_dialog( - type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false); + if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { + name = s; + trans_name = trans_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD + | TFN_NO_DEREF, NULL, NULL); + if (*name != NUL) { + s = NULL; + } } -} - -/* - * "copy()" function - */ -static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - var_item_copy(NULL, &argvars[0], rettv, false, 0); -} - -/* - * "count()" function - */ -static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long n = 0; - int ic = 0; - bool error = false; - - if (argvars[2].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[2], &error); - } - - if (argvars[0].v_type == VAR_STRING) { - const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); - const char_u *p = argvars[0].vval.v_string; - - if (!error && expr != NULL && *expr != NUL && p != NULL) { - if (ic) { - const size_t len = STRLEN(expr); + if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) + || (is_funcref && trans_name == NULL)) { + emsgf(_(e_invarg2), (use_string + ? tv_get_string(&argvars[0]) + : (const char *)s)); + // Don't check an autoload name for existence here. + } else if (trans_name != NULL + && (is_funcref ? find_func(trans_name) == NULL + : !translated_function_exists((const char *)trans_name))) { + emsgf(_("E700: Unknown function: %s"), s); + } else { + int dict_idx = 0; + int arg_idx = 0; + list_T *list = NULL; + if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { + char sid_buf[25]; + int off = *s == 's' ? 2 : 5; - while (*p != NUL) { - if (mb_strnicmp(p, expr, len) == 0) { - n++; - p += len; - } else { - MB_PTR_ADV(p); - } - } - } else { - char_u *next; - while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { - n++; - p = next + STRLEN(expr); - } - } + // Expand s: and into nr_, so that the function can + // also be called from another script. Using trans_function_name() + // would also work, but some plugins depend on the name being + // printable text. + snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); + STRCPY(name, sid_buf); + STRCAT(name, s + off); + } else { + name = vim_strsave(s); } - } else if (argvars[0].v_type == VAR_LIST) { - listitem_T *li; - list_T *l; - long idx; - if ((l = argvars[0].vval.v_list) != NULL) { - li = tv_list_first(l); + if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - idx = tv_get_number_chk(&argvars[3], &error); - if (!error) { - li = tv_list_find(l, idx); - if (li == NULL) { - EMSGN(_(e_listidx), idx); - } - } + // function(name, [args], dict) + arg_idx = 1; + dict_idx = 2; + } else if (argvars[1].v_type == VAR_DICT) { + // function(name, dict) + dict_idx = 1; + } else { + // function(name, [args]) + arg_idx = 1; + } + if (dict_idx > 0) { + if (argvars[dict_idx].v_type != VAR_DICT) { + EMSG(_("E922: expected a dict")); + xfree(name); + goto theend; + } + if (argvars[dict_idx].vval.v_dict == NULL) { + dict_idx = 0; } - if (error) - li = NULL; } - - for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { - if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { - n++; + if (arg_idx > 0) { + if (argvars[arg_idx].v_type != VAR_LIST) { + EMSG(_("E923: Second argument of function() must be " + "a list or a dict")); + xfree(name); + goto theend; + } + list = argvars[arg_idx].vval.v_list; + if (tv_list_len(list) == 0) { + arg_idx = 0; } } } - } else if (argvars[0].v_type == VAR_DICT) { - int todo; - dict_T *d; - hashitem_T *hi; + if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { + partial_T *const pt = xcalloc(1, sizeof(*pt)); - if ((d = argvars[0].vval.v_dict) != NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - if (argvars[3].v_type != VAR_UNKNOWN) { - EMSG(_(e_invarg)); + // result is a VAR_PARTIAL + if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { + const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); + const int lv_len = tv_list_len(list); + + pt->pt_argc = arg_len + lv_len; + pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); + int i = 0; + for (; i < arg_len; i++) { + tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); + } + if (lv_len > 0) { + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); } } - todo = error ? 0 : (int)d->dv_hashtab.ht_used; - for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) { - n++; - } + // For "function(dict.func, [], dict)" and "func" is a partial + // use "dict". That is backwards compatible. + if (dict_idx > 0) { + // The dict is bound explicitly, pt_auto is false + pt->pt_dict = argvars[dict_idx].vval.v_dict; + (pt->pt_dict->dv_refcount)++; + } else if (arg_pt != NULL) { + // If the dict was bound automatically the result is also + // bound automatically. + pt->pt_dict = arg_pt->pt_dict; + pt->pt_auto = arg_pt->pt_auto; + if (pt->pt_dict != NULL) { + (pt->pt_dict->dv_refcount)++; } } + + pt->pt_refcount = 1; + if (arg_pt != NULL && arg_pt->pt_func != NULL) { + pt->pt_func = arg_pt->pt_func; + func_ptr_ref(pt->pt_func); + xfree(name); + } else if (is_funcref) { + pt->pt_func = find_func(trans_name); + func_ptr_ref(pt->pt_func); + xfree(name); + } else { + pt->pt_name = name; + func_ref(name); + } + + rettv->v_type = VAR_PARTIAL; + rettv->vval.v_partial = pt; + } else { + // result is a VAR_FUNC + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = name; + func_ref(name); } - } else { - EMSG2(_(e_listdictarg), "count()"); } - rettv->vval.v_number = n; +theend: + xfree(trans_name); } -/* - * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function - * - * Checks the existence of a cscope connection. - */ -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Returns buffer options, variables and other attributes in a dictionary. +dict_T *get_buffer_info(buf_T *buf) { - int num = 0; - const char *dbpath = NULL; - const char *prepend = NULL; - char buf[NUMBUFLEN]; + dict_T *const dict = tv_dict_alloc(); - if (argvars[0].v_type != VAR_UNKNOWN - && argvars[1].v_type != VAR_UNKNOWN) { - num = (int)tv_get_number(&argvars[0]); - dbpath = tv_get_string(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - prepend = tv_get_string_buf(&argvars[2], buf); - } - } + tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); + tv_dict_add_str(dict, S_LEN("name"), + buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); + tv_dict_add_nr(dict, S_LEN("lnum"), + buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); + tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); + tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); + tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); + tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); + tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); + tv_dict_add_nr(dict, S_LEN("hidden"), + buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); - rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, - (char_u *)prepend); -} + // Get a reference to buffer variables + tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); -/// "ctxget([{index}])" function -static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - size_t index = 0; - if (argvars[0].v_type == VAR_NUMBER) { - index = argvars[0].vval.v_number; - } else if (argvars[0].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a Number as an argument"); - return; + // List of windows displaying this buffer + list_T *const windows = tv_list_alloc(kListLenMayKnow); + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + tv_list_append_number(windows, (varnumber_T)wp->handle); + } } + tv_dict_add_list(dict, S_LEN("windows"), windows); - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; + if (buf->b_signlist != NULL) { + // List of signs placed in this buffer + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } - Dictionary ctx_dict = ctx_to_dict(ctx); - Error err = ERROR_INIT; - object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); - api_free_dictionary(ctx_dict); - api_clear_error(&err); + return dict; } -/// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Get the line number from VimL object +/// +/// @note Unlike tv_get_lnum(), this one supports only "$" special string. +/// +/// @param[in] tv Object to get value from. Is expected to be a number or +/// a special string "$". +/// @param[in] buf Buffer to take last line number from in case tv is "$". May +/// be NULL, in this case "$" results in zero return. +/// +/// @return Line number or 0 in case of error. +linenr_T tv_get_lnum_buf(const typval_T *const tv, + const buf_T *const buf) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { - if (!ctx_restore(NULL, kCtxAll)) { - EMSG(_("Context stack is empty")); + if (tv->v_type == VAR_STRING + && tv->vval.v_string != NULL + && tv->vval.v_string[0] == '$' + && buf != NULL) { + return buf->b_ml.ml_line_count; } + return tv_get_number_chk(tv, NULL); } -/// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, + typval_T *rettv) { - int types = kCtxAll; - if (argvars[0].v_type == VAR_LIST) { - types = 0; - TV_LIST_ITER(argvars[0].vval.v_list, li, { - typval_T *tv_li = TV_LIST_ITEM_TV(li); - if (tv_li->v_type == VAR_STRING) { - if (strequal((char *)tv_li->vval.v_string, "regs")) { - types |= kCtxRegs; - } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { - types |= kCtxJumps; - } else if (strequal((char *)tv_li->vval.v_string, "bufs")) { - types |= kCtxBufs; - } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { - types |= kCtxGVars; - } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { - types |= kCtxSFuncs; - } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { - types |= kCtxFuncs; + if (what_arg->v_type == VAR_UNKNOWN) { + tv_list_alloc_ret(rettv, kListLenMayKnow); + if (is_qf || wp != NULL) { + (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list); + } + } else { + tv_dict_alloc_ret(rettv); + if (is_qf || wp != NULL) { + if (what_arg->v_type == VAR_DICT) { + dict_T *d = what_arg->vval.v_dict; + + if (d != NULL) { + qf_get_properties(wp, d, rettv->vval.v_dict); } + } else { + EMSG(_(e_dictreq)); } - }); - } else if (argvars[0].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a List as an argument"); - return; + } } - ctx_save(NULL, types); } -/// "ctxset({context}[, {index}])" function -static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Returns information (variables, options, etc.) about a tab page +/// as a dictionary. +dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) { - if (argvars[0].v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "expected dictionary as first argument"); - return; - } + dict_T *const dict = tv_dict_alloc(); - size_t index = 0; - if (argvars[1].v_type == VAR_NUMBER) { - index = argvars[1].vval.v_number; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - EMSG2(_(e_invarg2), "expected nothing or a Number as second argument"); - return; - } + tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - Context *ctx = ctx_get(index); - if (ctx == NULL) { - EMSG3(_(e_invargNval), "index", "out of bounds"); - return; + list_T *const l = tv_list_alloc(kListLenMayKnow); + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + tv_list_append_number(l, (varnumber_T)wp->handle); } + tv_dict_add_list(dict, S_LEN("windows"), l); - int save_did_emsg = did_emsg; - did_emsg = false; - - Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; - Context tmp = CONTEXT_INIT; - ctx_from_dict(dict, &tmp); - - if (did_emsg) { - ctx_free(&tmp); - } else { - ctx_free(ctx); - *ctx = tmp; - } + // Make a reference to tabpage variables + tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); - api_free_dictionary(dict); - did_emsg = save_did_emsg; + return dict; } -/// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Returns information about a window as a dictionary. +dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = ctx_size(); -} + dict_T *const dict = tv_dict_alloc(); -/// "cursor(lnum, col)" function, or -/// "cursor(list)" -/// -/// Moves the cursor to the specified line and column. -/// -/// @returns 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long line, col; - long coladd = 0; - bool set_curswant = true; + tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); + tv_dict_add_nr(dict, S_LEN("winnr"), winnr); + tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); + tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); + tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); + tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); + tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); + tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); + tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { - pos_T pos; - colnr_T curswant = -1; + tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); + tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); + tv_dict_add_nr(dict, S_LEN("loclist"), + (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { - EMSG(_(e_invarg)); - return; - } - - line = pos.lnum; - col = pos.col; - coladd = pos.coladd; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - set_curswant = false; - } - } else { - line = tv_get_lnum(argvars); - col = (long)tv_get_number_chk(&argvars[1], NULL); - if (argvars[2].v_type != VAR_UNKNOWN) { - coladd = (long)tv_get_number_chk(&argvars[2], NULL); - } - } - if (line < 0 || col < 0 - || coladd < 0) { - return; // type error; errmsg already given - } - if (line > 0) { - curwin->w_cursor.lnum = line; - } - if (col > 0) { - curwin->w_cursor.col = col - 1; - } - curwin->w_cursor.coladd = coladd; - - // Make sure the cursor is in a valid position. - check_cursor(); - // Correct cursor for multi-byte character. - if (has_mbyte) { - mb_adjust_cursor(); - } + // Add a reference to window variables + tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); - curwin->w_set_curswant = set_curswant; - rettv->vval.v_number = 0; + return dict; } /* - * "deepcopy()" function + * Find window specified by "vp" in tabpage "tp". */ -static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int noref = 0; - - if (argvars[1].v_type != VAR_UNKNOWN) { - noref = tv_get_number_chk(&argvars[1], NULL); - } - if (noref < 0 || noref > 1) { - EMSG(_(e_invarg)); - } else { - var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 - ? get_copyID() - : 0)); - } -} - -// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) { - return; - } - - const char *const name = tv_get_string(&argvars[0]); - if (*name == NUL) { - EMSG(_(e_invarg)); - return; - } - - char nbuf[NUMBUFLEN]; - const char *flags; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } else { - flags = ""; - } - - if (*flags == NUL) { - // delete a file - rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "d") == 0) { - // delete an empty directory - rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1; - } else if (strcmp(flags, "rf") == 0) { - // delete a directory recursively - rettv->vval.v_number = delete_recursive(name); - } else { - emsgf(_(e_invexpr2), flags); - } -} - -// dictwatcheradd(dict, key, funcref) function -static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } else if (argvars[0].vval.v_dict == NULL) { - const char *const arg_errmsg = _("dictwatcheradd() argument"); - const size_t arg_errmsg_len = strlen(arg_errmsg); - emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg); - return; - } - - if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) { - emsgf(_(e_invarg2), "key"); - return; - } - - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; - } - const size_t key_pattern_len = strlen(key_pattern); - - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - emsgf(_(e_invarg2), "funcref"); - return; - } - - tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len, - callback); -} - -// dictwatcherdel(dict, key, funcref) function -static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +win_T * +find_win_by_nr( + typval_T *vp, + tabpage_T *tp /* NULL for current tab page */ +) { - if (check_restricted() || check_secure()) { - return; - } - - if (argvars[0].v_type != VAR_DICT) { - emsgf(_(e_invarg2), "dict"); - return; - } + int nr = (int)tv_get_number_chk(vp, NULL); - if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) { - emsgf(_(e_invarg2), "funcref"); - return; + if (nr < 0) { + return NULL; } - const char *const key_pattern = tv_get_string_chk(argvars + 1); - if (key_pattern == NULL) { - return; + if (nr == 0) { + return curwin; } - Callback callback; - if (!callback_from_typval(&callback, &argvars[2])) { - return; + // This method accepts NULL as an alias for curtab. + if (tp == NULL) { + tp = curtab; } - if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern, - strlen(key_pattern), callback)) { - EMSG("Couldn't find a watcher matching key and callback"); + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + if (nr >= LOWEST_WIN_ID) { + if (wp->handle == nr) { + return wp; + } + } else if (--nr <= 0) { + return wp; + } } - - callback_free(&callback); + return NULL; } -/// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Find window specified by "wvp" in tabpage "tvp". +win_T *find_tabwin(typval_T *wvp, typval_T *tvp) { - linenr_T last; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - return; - } - const bool is_curbuf = buf == curbuf; - - const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - last = tv_get_lnum_buf(&argvars[2], buf); - } else { - last = first; - } - - if (buf->b_ml.ml_mfp == NULL || first < 1 - || first > buf->b_ml.ml_line_count || last < first) { - rettv->vval.v_number = 1; // FAIL - return; - } - - if (!is_curbuf) { - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - if (last > curbuf->b_ml.ml_line_count) { - last = curbuf->b_ml.ml_line_count; - } - const long count = last - first + 1; - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (u_save(first - 1, last + 1) == FAIL) { - rettv->vval.v_number = 1; // FAIL - return; - } - - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } + win_T *wp = NULL; + tabpage_T *tp = NULL; - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= count; - } else if (wp->w_cursor.lnum> first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + if (wvp->v_type != VAR_UNKNOWN) { + if (tvp->v_type != VAR_UNKNOWN) { + long n = tv_get_number(tvp); + if (n >= 0) { + tp = find_tabpage(n); } + } else { + tp = curtab; } - } - check_cursor_col(); - deleted_lines_mark(first, count); - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; + if (tp != NULL) { + wp = find_win_by_nr(wvp, tp); + } + } else { + wp = curwin; } -} -/* - * "did_filetype()" function - */ -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = did_filetype; + return wp; } /* - * "diff_filler()" function + * getwinvar() and gettabwinvar() */ -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void +getwinvar( + typval_T *argvars, + typval_T *rettv, + int off /* 1 for gettabwinvar() */ +) { - rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars)); -} + win_T *win, *oldcurwin; + dictitem_T *v; + tabpage_T *tp = NULL; + tabpage_T *oldtabpage = NULL; + bool done = false; -/* - * "diff_hlID()" function - */ -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(argvars); - static linenr_T prev_lnum = 0; - static int changedtick = 0; - static int fnum = 0; - static int change_start = 0; - static int change_end = 0; - static hlf_T hlID = (hlf_T)0; - int filler_lines; - int col; - - if (lnum < 0) /* ignore type error in {lnum} arg */ - lnum = 0; - if (lnum != prev_lnum - || changedtick != buf_get_changedtick(curbuf) - || fnum != curbuf->b_fnum) { - /* New line, buffer, change: need to get the values. */ - filler_lines = diff_check(curwin, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { - change_start = MAXCOL; - change_end = -1; - if (diff_find_change(curwin, lnum, &change_start, &change_end)) - hlID = HLF_ADD; /* added line */ - else - hlID = HLF_CHD; /* changed line */ - } else - hlID = HLF_ADD; /* added line */ - } else - hlID = (hlf_T)0; - prev_lnum = lnum; - changedtick = buf_get_changedtick(curbuf); - fnum = curbuf->b_fnum; + if (off == 1) { + tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + } else { + tp = curtab; } + win = find_win_by_nr(&argvars[off], tp); + const char *varname = tv_get_string_chk(&argvars[off + 1]); - if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}. - if (col >= change_start && col <= change_end) { - hlID = HLF_TXD; // Changed text. - } else { - hlID = HLF_CHD; // Changed line. - } - } - rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); -} + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; -/* - * "empty({expr})" function - */ -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool n = true; + emsg_off++; + if (win != NULL && varname != NULL) { + // Set curwin to be our win, temporarily. Also set the tabpage, + // otherwise the window is not valid. Only do this when needed, + // autocommands get blocked. + bool need_switch_win = tp != curtab || win != curwin; + if (!need_switch_win + || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { + if (*varname == '&') { + if (varname[1] == NUL) { + // get all window-local options in a dict + dict_T *opts = get_winbuf_options(false); - switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: { - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - } - case VAR_PARTIAL: { - n = false; - break; - } - case VAR_NUMBER: { - n = argvars[0].vval.v_number == 0; - break; - } - case VAR_FLOAT: { - n = argvars[0].vval.v_float == 0.0; - break; - } - case VAR_LIST: { - n = (tv_list_len(argvars[0].vval.v_list) == 0); - break; - } - case VAR_DICT: { - n = (tv_dict_len(argvars[0].vval.v_dict) == 0); - break; - } - case VAR_SPECIAL: { - // Using switch to get warning if SpecialVarValue receives more values. - switch (argvars[0].vval.v_special) { - case kSpecialVarTrue: { - n = false; - break; + if (opts != NULL) { + tv_dict_set_ret(rettv, opts); + done = true; + } + } else if (get_option_tv(&varname, rettv, 1) == OK) { + // window-local-option + done = true; } - case kSpecialVarFalse: - case kSpecialVarNull: { - n = true; - break; + } else { + // Look up the variable. + // Let getwinvar({nr}, "") return the "w:" dictionary. + v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, + strlen(varname), false); + if (v != NULL) { + tv_copy(&v->di_tv, rettv); + done = true; } } - break; } - case VAR_UNKNOWN: { - internal_error("f_empty(UNKNOWN)"); - break; + + if (need_switch_win) { + // restore previous notion of curwin + restore_win(oldcurwin, oldtabpage, true); } } + emsg_off--; - rettv->vval.v_number = n; -} - -/// "environ()" function -static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - size_t env_size = os_get_fullenv_size(); - char **env = xmalloc(sizeof(*env) * (env_size + 1)); - env[env_size] = NULL; - - os_copy_fullenv(env, env_size); - - for (size_t i = 0; i < env_size; i++) { - const char * str = env[i]; - const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), - '='); - assert(end != NULL); - ptrdiff_t len = end - str; - assert(len > 0); - const char * value = str + len + 1; - tv_dict_add_str(rettv->vval.v_dict, - str, len, - value); + if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) { + // use the default return value + tv_copy(&argvars[off + 2], rettv); } - os_free_fullenv(env); } /* - * "escape({string}, {chars})" function + * This function is used by f_input() and f_inputdialog() functions. The third + * argument to f_input() specifies the type of completion to use at the + * prompt. The third argument to f_inputdialog() specifies the value to return + * when the user cancels the prompt. */ -static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char buf[NUMBUFLEN]; - - rettv->vval.v_string = vim_strsave_escaped( - (const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], buf)); - rettv->v_type = VAR_STRING; -} - -/// "getenv()" function -static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void get_user_input(const typval_T *const argvars, + typval_T *const rettv, + const bool inputdialog, + const bool secret) + FUNC_ATTR_NONNULL_ALL { - char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); - - if (p == NULL) { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_special = kSpecialVarNull; - return; - } - rettv->vval.v_string = p; rettv->v_type = VAR_STRING; -} + rettv->vval.v_string = NULL; -/* - * "eval()" function - */ -static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *s = tv_get_string_chk(&argvars[0]); - if (s != NULL) { - s = (const char *)skipwhite((const char_u *)s); - } - - const char *const expr_start = s; - if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) { - if (expr_start != NULL && !aborting()) { - EMSG2(_(e_invexpr2), expr_start); + const char *prompt = ""; + const char *defstr = ""; + const char *cancelreturn = NULL; + const char *xp_name = NULL; + Callback input_callback = { .type = kCallbackNone }; + char prompt_buf[NUMBUFLEN]; + char defstr_buf[NUMBUFLEN]; + char cancelreturn_buf[NUMBUFLEN]; + char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; + if (argvars[0].v_type == VAR_DICT) { + if (argvars[1].v_type != VAR_UNKNOWN) { + EMSG(_("E5050: {opts} must be the only argument")); + return; } - need_clr_eos = FALSE; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - } else if (*s != NUL) { - EMSG(_(e_trailing)); - } -} - -/* - * "eventhandler()" function - */ -static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = vgetc_busy; -} - -/* - * "executable()" function - */ -static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = tv_get_string(&argvars[0]); - - // Check in $PATH and also check directly if there is a directory name - rettv->vval.v_number = os_can_exe(name, NULL, true); -} - -typedef struct { - const list_T *const l; - const listitem_T *li; -} GetListLineCookie; - -static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) -{ - GetListLineCookie *const p = (GetListLineCookie *)cookie; - - const listitem_T *const item = p->li; - if (item == NULL) { - return NULL; - } - char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); - p->li = TV_LIST_ITEM_NEXT(p->l, item); - return (char_u *)(s == NULL ? NULL : xstrdup(s)); -} - -// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int save_msg_silent = msg_silent; - const int save_emsg_silent = emsg_silent; - const bool save_emsg_noredir = emsg_noredir; - const bool save_redir_off = redir_off; - garray_T *const save_capture_ga = capture_ga; - const int save_msg_col = msg_col; - bool echo_output = false; - - if (check_secure()) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&argvars[1], buf); - - if (s == NULL) { + dict_T *const dict = argvars[0].vval.v_dict; + prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); + if (prompt == NULL) { + return; + } + defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, ""); + if (defstr == NULL) { + return; + } + cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), + cancelreturn_buf, def); + if (cancelreturn == NULL) { // error return; } - if (*s == NUL) { - echo_output = true; + if (*cancelreturn == NUL) { + cancelreturn = NULL; + } + xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), + xp_name_buf, def); + if (xp_name == NULL) { // error + return; } - if (strncmp(s, "silent", 6) == 0) { - msg_silent++; + if (xp_name == def) { // default to NULL + xp_name = NULL; } - if (strcmp(s, "silent!") == 0) { - emsg_silent = true; - emsg_noredir = true; + if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { + return; } } else { - msg_silent++; + prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); + if (prompt == NULL) { + return; + } + if (argvars[1].v_type != VAR_UNKNOWN) { + defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf); + if (defstr == NULL) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const arg2 = tv_get_string_buf_chk(&argvars[2], + cancelreturn_buf); + if (arg2 == NULL) { + return; + } + if (inputdialog) { + cancelreturn = arg2; + } else { + xp_name = arg2; + } + } + } } - garray_T capture_local; - ga_init(&capture_local, (int)sizeof(char), 80); - capture_ga = &capture_local; - redir_off = false; - if (!echo_output) { - msg_col = 0; // prevent leading spaces + int xp_type = EXPAND_NOTHING; + char *xp_arg = NULL; + if (xp_name != NULL) { + // input() with a third argument: completion + const int xp_namelen = (int)strlen(xp_name); + + uint32_t argt; + if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, + &argt, (char_u **)&xp_arg) == FAIL) { + return; + } } - if (argvars[0].v_type != VAR_LIST) { - do_cmdline_cmd(tv_get_string(&argvars[0])); - } else if (argvars[0].vval.v_list != NULL) { - list_T *const list = argvars[0].vval.v_list; - tv_list_ref(list); - GetListLineCookie cookie = { - .l = list, - .li = tv_list_first(list), - }; - do_cmdline(NULL, get_list_line, (void *)&cookie, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - tv_list_unref(list); - } - msg_silent = save_msg_silent; - emsg_silent = save_emsg_silent; - emsg_noredir = save_emsg_noredir; - redir_off = save_redir_off; - // "silent reg" or "silent echo x" leaves msg_col somewhere in the line. - if (echo_output) { - // When not working silently: put it in column zero. A following - // "echon" will overwrite the message, unavoidably. - msg_col = 0; - } else { - // When working silently: Put it back where it was, since nothing - // should have been written. - msg_col = save_msg_col; + const bool cmd_silent_save = cmd_silent; + + cmd_silent = false; // Want to see the prompt. + // Only the part of the message after the last NL is considered as + // prompt for the command line, unlsess cmdline is externalized + const char *p = prompt; + if (!ui_has(kUICmdline)) { + const char *lastnl = strrchr(prompt, '\n'); + if (lastnl != NULL) { + p = lastnl+1; + msg_start(); + msg_clr_eos(); + msg_puts_attr_len(prompt, p - prompt, echo_attr); + msg_didout = false; + msg_starthere(); + } } + cmdline_row = msg_row; - ga_append(capture_ga, NUL); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = capture_ga->ga_data; + stuffReadbuffSpec(defstr); - capture_ga = save_capture_ga; -} + const int save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + rettv->vval.v_string = + (char_u *)getcmdline_prompt(secret ? NUL : '@', p, echo_attr, + xp_type, xp_arg, input_callback); + ex_normal_busy = save_ex_normal_busy; + callback_free(&input_callback); -/// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *arg = tv_get_string(&argvars[0]); - char *path = NULL; + if (rettv->vval.v_string == NULL && cancelreturn != NULL) { + rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); + } - (void)os_can_exe(arg, &path, true); + xfree(xp_arg); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)path; + // Since the user typed this, no need to wait for return. + need_wait_return = false; + msg_didout = false; + cmd_silent = cmd_silent_save; } -/// Find a window: When using a Window ID in any tab page, when using a number -/// in the current tab page. -win_T * find_win_by_nr_or_id(typval_T *vp) +/// Turn a dictionary into a list +/// +/// @param[in] tv Dictionary to convert. Is checked for actually being +/// a dictionary, will give an error if not. +/// @param[out] rettv Location where result will be saved. +/// @param[in] what What to save in rettv. +void dict_list(typval_T *const tv, typval_T *const rettv, + const DictListType what) { - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr >= LOWEST_WIN_ID) { - return win_id2wp(vp); + if (tv->v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + if (tv->vval.v_dict == NULL) { + return; } - return find_win_by_nr(vp, NULL); -} + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); -/* - * "exists()" function - */ -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = false; - int len = 0; + TV_DICT_ITER(tv->vval.v_dict, di, { + typval_T tv_item = { .v_lock = VAR_UNLOCKED }; - const char *p = tv_get_string(&argvars[0]); - if (*p == '$') { // Environment variable. - // First try "normal" environment variables (fast). - if (os_env_exists(p + 1)) { - n = true; - } else { - // Try expanding things like $VIM and ${HOME}. - char_u *const exp = expand_env_save((char_u *)p); - if (exp != NULL && *exp != '$') { - n = true; + switch (what) { + case kDictListKeys: { + tv_item.v_type = VAR_STRING; + tv_item.vval.v_string = vim_strsave(di->di_key); + break; } - xfree(exp); - } - } else if (*p == '&' || *p == '+') { // Option. - n = (get_option_tv(&p, NULL, true) == OK); - if (*skipwhite((const char_u *)p) != NUL) { - n = false; // Trailing garbage. - } - } else if (*p == '*') { // Internal or user defined function. - n = function_exists(p + 1, false); - } else if (*p == ':') { - n = cmd_exists(p + 1); - } else if (*p == '#') { - if (p[1] == '#') { - n = autocmd_supported(p + 2); - } else { - n = au_exists(p + 1); - } - } else { // Internal variable. - typval_T tv; - - // get_name_len() takes care of expanding curly braces - const char *name = p; - char *tofree; - len = get_name_len((const char **)&p, &tofree, true, false); - if (len > 0) { - if (tofree != NULL) { - name = tofree; + case kDictListValues: { + tv_copy(&di->di_tv, &tv_item); + break; } - n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); - if (n) { - // Handle d.key, l[idx], f(expr). - n = (handle_subscript(&p, &tv, true, false) == OK); - if (n) { - tv_clear(&tv); - } + case kDictListItems: { + // items() + list_T *const sub_l = tv_list_alloc(2); + tv_item.v_type = VAR_LIST; + tv_item.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); + + break; } } - if (*p != NUL) - n = FALSE; - - xfree(tofree); - } - rettv->vval.v_number = n; + tv_list_append_owned_tv(rettv->vval.v_list, tv_item); + }); } -/* - * "expand()" function - */ -static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Builds a process argument vector from a VimL object (typval_T). +/// +/// @param[in] cmd_tv VimL object +/// @param[out] cmd Returns the command or executable name. +/// @param[out] executable Returns `false` if argv[0] is not executable. +/// +/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. +/// Else, string values of `cmd_tv` copied to a (char **) list with +/// argv[0] resolved to full path ($PATHEXT-resolved on Windows). +char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) { - size_t len; - char_u *errormsg; - int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; - expand_T xpc; - bool error = false; - char_u *result; - - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[2], &error) - && !error) { - tv_list_set_ret(rettv, NULL); - } - - const char *s = tv_get_string(&argvars[0]); - if (*s == '%' || *s == '#' || *s == '<') { - emsg_off++; - result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); - emsg_off--; - if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv, (result != NULL)); - if (result != NULL) { - tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); - } - } else - rettv->vval.v_string = result; - } else { - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ - if (argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) { - options += WILD_ICASE; - } - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, - WILD_ALL); - } else { - ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)xpc.xp_files[i], -1); - } - ExpandCleanup(&xpc); - } - } else { - rettv->vval.v_string = NULL; + if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics". + const char *cmd_str = tv_get_string(cmd_tv); + if (cmd) { + *cmd = cmd_str; } + return shell_build_argv(cmd_str, NULL); } -} - -/// "menu_get(path [, modes])" function -static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - int modes = MENU_ALL_MODES; - if (argvars[1].v_type == VAR_STRING) { - const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); - modes = get_menu_cmd_modes(strmodes, false, NULL, NULL); + if (cmd_tv->v_type != VAR_LIST) { + EMSG2(_(e_invarg2), "expected String or List"); + return NULL; } - menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); -} -/* - * "extend(list, list [, idx])" function - * "extend(dict, dict [, action])" function - */ -static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const arg_errmsg = N_("extend() argument"); + list_T *argl = cmd_tv->vval.v_list; + int argc = tv_list_len(argl); + if (!argc) { + EMSG(_(e_invarg)); // List must have at least one item. + return NULL; + } - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { - long before; - bool error = false; - - list_T *const l1 = argvars[0].vval.v_list; - list_T *const l2 = argvars[1].vval.v_list; - if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { - listitem_T *item; - if (argvars[2].v_type != VAR_UNKNOWN) { - before = (long)tv_get_number_chk(&argvars[2], &error); - if (error) { - return; // Type error; errmsg already given. - } - - if (before == tv_list_len(l1)) { - item = NULL; - } else { - item = tv_list_find(l1, before); - if (item == NULL) { - EMSGN(_(e_listidx), before); - return; - } - } - } else { - item = NULL; - } - tv_list_extend(l1, l2, item); - - tv_copy(&argvars[0], rettv); - } - } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == - VAR_DICT) { - dict_T *const d1 = argvars[0].vval.v_dict; - dict_T *const d2 = argvars[1].vval.v_dict; - if (d1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (d2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { - const char *action = "force"; - // Check the third argument. - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const av[] = { "keep", "force", "error" }; - - action = tv_get_string_chk(&argvars[2]); - if (action == NULL) { - return; // Type error; error message already given. - } - size_t i; - for (i = 0; i < ARRAY_SIZE(av); i++) { - if (strcmp(action, av[i]) == 0) { - break; - } - } - if (i == 3) { - EMSG2(_(e_invarg2), action); - return; - } - } - - tv_dict_extend(d1, d2, action); - - tv_copy(&argvars[0], rettv); - } - } else { - EMSG2(_(e_listdictarg), "extend()"); - } -} - -/* - * "feedkeys()" function - */ -static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is not allowed in the sandbox. If the commands would still be - // executed in the sandbox it would be OK, but it probably happens later, - // when "sandbox" is no longer set. - if (check_secure()) { - return; - } - - const char *const keys = tv_get_string(&argvars[0]); - char nbuf[NUMBUFLEN]; - const char *flags = NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - flags = tv_get_string_buf(&argvars[1], nbuf); - } - - nvim_feedkeys(cstr_as_string((char *)keys), - cstr_as_string((char *)flags), true); -} - -/// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const p = tv_get_string(&argvars[0]); - rettv->vval.v_number = - (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); -} - -/* - * Return 0 for not writable, 1 for writable file, 2 for a dir which we have - * rights to write into. - */ -static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *filename = tv_get_string(&argvars[0]); - rettv->vval.v_number = os_file_is_writable(filename); -} - - -static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) -{ - char_u *fresult = NULL; - char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; - int count = 1; - bool first = true; - bool error = false; - - rettv->vval.v_string = NULL; - rettv->v_type = VAR_STRING; - - const char *fname = tv_get_string(&argvars[0]); - - char pathbuf[NUMBUFLEN]; - if (argvars[1].v_type != VAR_UNKNOWN) { - const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf); - if (p == NULL) { - error = true; - } else { - if (*p != NUL) { - path = (char_u *)p; - } - - if (argvars[2].v_type != VAR_UNKNOWN) { - count = tv_get_number_chk(&argvars[2], &error); - } - } - } - - if (count < 0) { - tv_list_alloc_ret(rettv, kListLenUnknown); - } - - if (*fname != NUL && !error) { - do { - if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) - xfree(fresult); - fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, - first ? strlen(fname) : 0, - 0, first, path, - find_what, curbuf->b_ffname, - (find_what == FINDFILE_DIR - ? (char_u *)"" - : curbuf->b_p_sua)); - first = false; - - if (fresult != NULL && rettv->v_type == VAR_LIST) { - tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1); - } - } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); - } - - if (rettv->v_type == VAR_STRING) - rettv->vval.v_string = fresult; -} - - -/* - * Implementation of map() and filter(). - */ -static void filter_map(typval_T *argvars, typval_T *rettv, int map) -{ - typval_T *expr; - list_T *l = NULL; - dictitem_T *di; - hashtab_T *ht; - hashitem_T *hi; - dict_T *d = NULL; - typval_T save_val; - typval_T save_key; - int rem = false; - int todo; - char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); - const char *const arg_errmsg = (map - ? N_("map() argument") - : N_("filter() argument")); - int save_did_emsg; - int idx = 0; - - if (argvars[0].v_type == VAR_LIST) { - tv_copy(&argvars[0], rettv); - if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, - TV_TRANSLATE))) { - return; - } - } else if (argvars[0].v_type == VAR_DICT) { - tv_copy(&argvars[0], rettv); - if ((d = argvars[0].vval.v_dict) == NULL - || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { - return; - } - } else { - EMSG2(_(e_listdictarg), ermsg); - return; - } - - expr = &argvars[1]; - // On type errors, the preceding call has already displayed an error - // message. Avoid a misleading error message for an empty string that - // was not passed as argument. - if (expr->v_type != VAR_UNKNOWN) { - prepare_vimvar(VV_VAL, &save_val); - - // We reset "did_emsg" to be able to detect whether an error - // occurred during evaluation of the expression. - save_did_emsg = did_emsg; - did_emsg = FALSE; - - prepare_vimvar(VV_KEY, &save_key); - if (argvars[0].v_type == VAR_DICT) { - vimvars[VV_KEY].vv_type = VAR_STRING; - - ht = &d->dv_hashtab; - hash_lock(ht); - todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - --todo; - - di = TV_DICT_HI2DI(hi); - if (map - && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) - || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { - break; - } - - vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); - int r = filter_map_one(&di->di_tv, expr, map, &rem); - tv_clear(&vimvars[VV_KEY].vv_tv); - if (r == FAIL || did_emsg) { - break; - } - if (!map && rem) { - if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE) - || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) { - break; - } - tv_dict_item_remove(d, di); - } - } - } - hash_unlock(ht); - } else { - assert(argvars[0].v_type == VAR_LIST); - vimvars[VV_KEY].vv_type = VAR_NUMBER; - - for (listitem_T *li = tv_list_first(l); li != NULL;) { - if (map - && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, - TV_TRANSLATE)) { - break; - } - vimvars[VV_KEY].vv_nr = idx; - if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL - || did_emsg) { - break; - } - if (!map && rem) { - li = tv_list_item_remove(l, li); - } else { - li = TV_LIST_ITEM_NEXT(l, li); - } - idx++; - } - } - - restore_vimvar(VV_KEY, &save_key); - restore_vimvar(VV_VAL, &save_val); - - did_emsg |= save_did_emsg; - } -} - -static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - typval_T rettv; - typval_T argv[3]; - int retval = FAIL; - - tv_copy(tv, &vimvars[VV_VAL].vv_tv); - argv[0] = vimvars[VV_KEY].vv_tv; - argv[1] = vimvars[VV_VAL].vv_tv; - if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { - goto theend; - } - if (map) { - // map(): replace the list item value. - tv_clear(tv); - rettv.v_lock = 0; - *tv = rettv; - } else { - bool error = false; - - // filter(): when expr is zero remove the item - *remp = (tv_get_number_chk(&rettv, &error) == 0); - tv_clear(&rettv); - // On type error, nothing has been removed; return FAIL to stop the - // loop. The error message was given by tv_get_number_chk(). - if (error) { - goto theend; - } - } - retval = OK; -theend: - tv_clear(&vimvars[VV_VAL].vv_tv); - return retval; -} - -/* - * "filter()" function - */ -static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - filter_map(argvars, rettv, FALSE); -} - -/* - * "finddir({fname}[, {path}[, {count}]])" function - */ -static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - findfilendir(argvars, rettv, FINDFILE_DIR); -} - -/* - * "findfile({fname}[, {path}[, {count}]])" function - */ -static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - findfilendir(argvars, rettv, FINDFILE_FILE); -} - -/* - * "float2nr({float})" function - */ -static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T f; - - if (tv_get_float_chk(argvars, &f)) { - if (f <= -VARNUMBER_MAX + DBL_EPSILON) { - rettv->vval.v_number = -VARNUMBER_MAX; - } else if (f >= VARNUMBER_MAX - DBL_EPSILON) { - rettv->vval.v_number = VARNUMBER_MAX; - } else { - rettv->vval.v_number = (varnumber_T)f; - } - } -} - -/* - * "fmod()" function - */ -static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - float_T fx; - float_T fy; - - rettv->v_type = VAR_FLOAT; - if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) { - rettv->vval.v_float = fmod(fx, fy); - } else { - rettv->vval.v_float = 0.0; - } -} - -/* - * "fnameescape({string})" function - */ -static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_string = (char_u *)vim_strsave_fnameescape( - tv_get_string(&argvars[0]), false); - rettv->v_type = VAR_STRING; -} - -/* - * "fnamemodify({fname}, {mods})" function - */ -static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *fbuf = NULL; - size_t len; - char buf[NUMBUFLEN]; - const char *fname = tv_get_string_chk(&argvars[0]); - const char *const mods = tv_get_string_buf_chk(&argvars[1], buf); - if (fname == NULL || mods == NULL) { - fname = NULL; - } else { - len = strlen(fname); - size_t usedlen = 0; - (void)modify_fname((char_u *)mods, false, &usedlen, - (char_u **)&fname, &fbuf, &len); - } - - rettv->v_type = VAR_STRING; - if (fname == NULL) { - rettv->vval.v_string = NULL; - } else { - rettv->vval.v_string = (char_u *)xmemdupz(fname, len); - } - xfree(fbuf); -} - - -/* - * "foldclosed()" function - */ -static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - linenr_T first; - linenr_T last; - if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) { - if (end) { - rettv->vval.v_number = (varnumber_T)last; - } else { - rettv->vval.v_number = (varnumber_T)first; - } - return; - } - } - rettv->vval.v_number = -1; -} - -/* - * "foldclosed()" function - */ -static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, FALSE); -} - -/* - * "foldclosedend()" function - */ -static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - foldclosed_both(argvars, rettv, TRUE); -} - -/* - * "foldlevel()" function - */ -static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = foldLevel(lnum); - } -} - -/* - * "foldtext()" function - */ -static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T foldstart; - linenr_T foldend; - char_u *dashes; - linenr_T lnum; - char_u *s; - char_u *r; - int len; - char *txt; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - 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) { - // Find first non-empty line in the fold. - for (lnum = foldstart; lnum < foldend; lnum++) { - if (!linewhite(lnum)) { - break; - } - } - - /* Find interesting text in this line. */ - s = skipwhite(ml_get(lnum)); - /* skip C comment-start */ - if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { - s = skipwhite(s + 2); - if (*skipwhite(s) == NUL && lnum + 1 < foldend) { - s = skipwhite(ml_get(lnum + 1)); - if (*s == '*') - s = skipwhite(s + 1); - } - } - unsigned long count = (unsigned long)(foldend - foldstart + 1); - txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); - r = xmalloc(STRLEN(txt) - + STRLEN(dashes) // for %s - + 20 // for %3ld - + STRLEN(s)); // concatenated - sprintf((char *)r, txt, dashes, count); - len = (int)STRLEN(r); - STRCAT(r, s); - /* remove 'foldmarker' and 'commentstring' */ - foldtext_cleanup(r + len); - rettv->vval.v_string = r; - } -} - -/* - * "foldtextresult(lnum)" function - */ -static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *text; - char_u buf[FOLD_TEXT_LEN]; - foldinfo_T foldinfo; - int fold_count; - static bool entered = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (entered) { - return; // reject recursive use - } - entered = true; - linenr_T lnum = tv_get_lnum(argvars); - // Treat illegal types and illegal string values for {lnum} the same. - if (lnum < 0) { - lnum = 0; - } - fold_count = foldedCount(curwin, lnum, &foldinfo); - if (fold_count > 0) { - text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf); - if (text == buf) { - text = vim_strsave(text); - } - rettv->vval.v_string = text; - } - - entered = false; -} - -/* - * "foreground()" function - */ -static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ -} - -static void common_function(typval_T *argvars, typval_T *rettv, - bool is_funcref, FunPtr fptr) -{ - char_u *s; - char_u *name; - bool use_string = false; - partial_T *arg_pt = NULL; - char_u *trans_name = NULL; - - if (argvars[0].v_type == VAR_FUNC) { - // function(MyFunc, [arg], dict) - s = argvars[0].vval.v_string; - } else if (argvars[0].v_type == VAR_PARTIAL - && argvars[0].vval.v_partial != NULL) { - // function(dict.MyFunc, [arg]) - arg_pt = argvars[0].vval.v_partial; - s = partial_name(arg_pt); - } else { - // function('MyFunc', [arg], dict) - s = (char_u *)tv_get_string(&argvars[0]); - use_string = true; - } - - if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { - name = s; - trans_name = trans_function_name(&name, false, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD - | TFN_NO_DEREF, NULL, NULL); - if (*name != NUL) { - s = NULL; - } - } - if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s)) - || (is_funcref && trans_name == NULL)) { - emsgf(_(e_invarg2), (use_string - ? tv_get_string(&argvars[0]) - : (const char *)s)); - // Don't check an autoload name for existence here. - } else if (trans_name != NULL - && (is_funcref ? find_func(trans_name) == NULL - : !translated_function_exists((const char *)trans_name))) { - emsgf(_("E700: Unknown function: %s"), s); - } else { - int dict_idx = 0; - int arg_idx = 0; - list_T *list = NULL; - if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { - char sid_buf[25]; - int off = *s == 's' ? 2 : 5; - - // Expand s: and into nr_, so that the function can - // also be called from another script. Using trans_function_name() - // would also work, but some plugins depend on the name being - // printable text. - snprintf(sid_buf, sizeof(sid_buf), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); - STRCPY(name, sid_buf); - STRCAT(name, s + off); - } else { - name = vim_strsave(s); - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[2].v_type != VAR_UNKNOWN) { - // function(name, [args], dict) - arg_idx = 1; - dict_idx = 2; - } else if (argvars[1].v_type == VAR_DICT) { - // function(name, dict) - dict_idx = 1; - } else { - // function(name, [args]) - arg_idx = 1; - } - if (dict_idx > 0) { - if (argvars[dict_idx].v_type != VAR_DICT) { - EMSG(_("E922: expected a dict")); - xfree(name); - goto theend; - } - if (argvars[dict_idx].vval.v_dict == NULL) { - dict_idx = 0; - } - } - if (arg_idx > 0) { - if (argvars[arg_idx].v_type != VAR_LIST) { - EMSG(_("E923: Second argument of function() must be " - "a list or a dict")); - xfree(name); - goto theend; - } - list = argvars[arg_idx].vval.v_list; - if (tv_list_len(list) == 0) { - arg_idx = 0; - } - } - } - if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) { - partial_T *const pt = xcalloc(1, sizeof(*pt)); - - // result is a VAR_PARTIAL - if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { - const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); - const int lv_len = tv_list_len(list); - - pt->pt_argc = arg_len + lv_len; - pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); - int i = 0; - for (; i < arg_len; i++) { - tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); - } - if (lv_len > 0) { - TV_LIST_ITER(list, li, { - tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); - }); - } - } - - // For "function(dict.func, [], dict)" and "func" is a partial - // use "dict". That is backwards compatible. - if (dict_idx > 0) { - // The dict is bound explicitly, pt_auto is false - pt->pt_dict = argvars[dict_idx].vval.v_dict; - (pt->pt_dict->dv_refcount)++; - } else if (arg_pt != NULL) { - // If the dict was bound automatically the result is also - // bound automatically. - pt->pt_dict = arg_pt->pt_dict; - pt->pt_auto = arg_pt->pt_auto; - if (pt->pt_dict != NULL) { - (pt->pt_dict->dv_refcount)++; - } - } - - pt->pt_refcount = 1; - if (arg_pt != NULL && arg_pt->pt_func != NULL) { - pt->pt_func = arg_pt->pt_func; - func_ptr_ref(pt->pt_func); - xfree(name); - } else if (is_funcref) { - pt->pt_func = find_func(trans_name); - func_ptr_ref(pt->pt_func); - xfree(name); - } else { - pt->pt_name = name; - func_ref(name); - } - - rettv->v_type = VAR_PARTIAL; - rettv->vval.v_partial = pt; - } else { - // result is a VAR_FUNC - rettv->v_type = VAR_FUNC; - rettv->vval.v_string = name; - func_ref(name); - } - } -theend: - xfree(trans_name); -} - -static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - common_function(argvars, rettv, true, fptr); -} - -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - common_function(argvars, rettv, false, fptr); -} - -/// "garbagecollect()" function -static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is postponed until we are back at the toplevel, because we may be - // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". - want_garbage_collect = true; - - if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) { - garbage_collect_at_exit = true; - } -} - -/* - * "get()" function - */ -static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - listitem_T *li; - list_T *l; - dictitem_T *di; - dict_T *d; - typval_T *tv = NULL; - bool what_is_dict = false; - - if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL) { - bool error = false; - - li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); - if (!error && li != NULL) { - tv = TV_LIST_ITEM_TV(li); - } - } - } else if (argvars[0].v_type == VAR_DICT) { - if ((d = argvars[0].vval.v_dict) != NULL) { - di = tv_dict_find(d, tv_get_string(&argvars[1]), -1); - if (di != NULL) { - tv = &di->di_tv; - } - } - } else if (tv_is_func(argvars[0])) { - partial_T *pt; - partial_T fref_pt; - - if (argvars[0].v_type == VAR_PARTIAL) { - pt = argvars[0].vval.v_partial; - } else { - memset(&fref_pt, 0, sizeof(fref_pt)); - fref_pt.pt_name = argvars[0].vval.v_string; - pt = &fref_pt; - } - - if (pt != NULL) { - const char *const what = tv_get_string(&argvars[1]); - - if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) { - rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - const char *const n = (const char *)partial_name(pt); - assert(n != NULL); - rettv->vval.v_string = (char_u *)xstrdup(n); - if (rettv->v_type == VAR_FUNC) { - func_ref(rettv->vval.v_string); - } - } else if (strcmp(what, "dict") == 0) { - what_is_dict = true; - if (pt->pt_dict != NULL) { - tv_dict_set_ret(rettv, pt->pt_dict); - } - } else if (strcmp(what, "args") == 0) { - rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { - for (int i = 0; i < pt->pt_argc; i++) { - tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); - } - } - } else { - EMSG2(_(e_invarg2), what); - } - - // When {what} == "dict" and pt->pt_dict == NULL, evaluate the - // third argument - if (!what_is_dict) { - return; - } - } - } else { - EMSG2(_(e_listdictarg), "get()"); - } - - if (tv == NULL) { - if (argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } - } else { - tv_copy(tv, rettv); - } -} - -/// Returns buffer options, variables and other attributes in a dictionary. -static dict_T *get_buffer_info(buf_T *buf) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); - tv_dict_add_str(dict, S_LEN("name"), - buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); - tv_dict_add_nr(dict, S_LEN("lnum"), - buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); - tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); - tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); - tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); - tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); - tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); - tv_dict_add_nr(dict, S_LEN("hidden"), - buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); - - // Get a reference to buffer variables - tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); - - // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(kListLenMayKnow); - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - tv_list_append_number(windows, (varnumber_T)wp->handle); - } - } - tv_dict_add_list(dict, S_LEN("windows"), windows); - - if (buf->b_signlist != NULL) { - // List of signs placed in this buffer - tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); - } - - return dict; -} - -/// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *argbuf = NULL; - bool filtered = false; - bool sel_buflisted = false; - bool sel_bufloaded = false; - bool sel_bufmodified = false; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - // List of all the buffers or selected buffers - if (argvars[0].v_type == VAR_DICT) { - dict_T *sel_d = argvars[0].vval.v_dict; - - if (sel_d != NULL) { - dictitem_T *di; - - filtered = true; - - di = tv_dict_find(sel_d, S_LEN("buflisted")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_buflisted = true; - } - - di = tv_dict_find(sel_d, S_LEN("bufloaded")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufloaded = true; - } - di = tv_dict_find(sel_d, S_LEN("bufmodified")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufmodified = true; - } - } - } else if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one buffer. Argument specifies the buffer - if (tv_check_num(&argvars[0])) { // issue errmsg if type error - emsg_off++; - argbuf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (argbuf == NULL) { - return; - } - } - } - - // Return information about all the buffers or a specified buffer - FOR_ALL_BUFFERS(buf) { - if (argbuf != NULL && argbuf != buf) { - continue; - } - if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) - || (sel_buflisted && !buf->b_p_bl) - || (sel_bufmodified && !buf->b_changed))) { - continue; - } - - dict_T *const d = get_buffer_info(buf); - tv_list_append_dict(rettv->vval.v_list, d); - if (argbuf != NULL) { - return; - } - } -} - -/* - * Get line or list of lines from buffer "buf" into "rettv". - * Return a range (from start to end) of lines in rettv from the specified - * buffer. - * If 'retlist' is TRUE, then the lines are returned as a Vim List. - */ -static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) -{ - rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); - rettv->vval.v_string = NULL; - - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { - if (retlist) { - tv_list_alloc_ret(rettv, 0); - } - return; - } - - if (retlist) { - if (start < 1) { - start = 1; - } - if (end > buf->b_ml.ml_line_count) { - end = buf->b_ml.ml_line_count; - } - tv_list_alloc_ret(rettv, end - start + 1); - while (start <= end) { - tv_list_append_string(rettv->vval.v_list, - (const char *)ml_get_buf(buf, start++, false), -1); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) - ? vim_strsave(ml_get_buf(buf, start, false)) - : NULL); - } -} - -/// Get the line number from VimL object -/// -/// @note Unlike tv_get_lnum(), this one supports only "$" special string. -/// -/// @param[in] tv Object to get value from. Is expected to be a number or -/// a special string "$". -/// @param[in] buf Buffer to take last line number from in case tv is "$". May -/// be NULL, in this case "$" results in zero return. -/// -/// @return Line number or 0 in case of error. -static linenr_T tv_get_lnum_buf(const typval_T *const tv, - const buf_T *const buf) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (tv->v_type == VAR_STRING - && tv->vval.v_string != NULL - && tv->vval.v_string[0] == '$' - && buf != NULL) { - return buf->b_ml.ml_line_count; - } - return tv_get_number_chk(tv, NULL); -} - -/* - * "getbufline()" function - */ -static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - - if (tv_check_str_or_nr(&argvars[0])) { - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN - ? lnum - : tv_get_lnum_buf(&argvars[2], buf)); - - get_buffer_lines(buf, lnum, end, true, rettv); -} - -/* - * "getbufvar()" function - */ -static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - if (!tv_check_str_or_nr(&argvars[0])) { - goto f_getbufvar_end; - } - - const char *varname = tv_get_string_chk(&argvars[1]); - emsg_off++; - buf_T *const buf = tv_get_buf(&argvars[0], false); - - if (buf != NULL && varname != NULL) { - // set curbuf to be our buf, temporarily - buf_T *const save_curbuf = curbuf; - curbuf = buf; - - if (*varname == '&') { // buffer-local-option - if (varname[1] == NUL) { - // get all buffer-local options in a dict - dict_T *opts = get_winbuf_options(true); - - if (opts != NULL) { - tv_dict_set_ret(rettv, opts); - done = true; - } - } else if (get_option_tv(&varname, rettv, true) == OK) { - // buffer-local-option - done = true; - } - } else { - // Look up the variable. - // Let getbufvar({nr}, "") return the "b:" dictionary. - dictitem_T *const v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, 'b', - varname, strlen(varname), false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curbuf - curbuf = save_curbuf; - } - emsg_off--; - -f_getbufvar_end: - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - // use the default value - tv_copy(&argvars[2], rettv); - } -} - -// "getchangelist()" function -static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error - emsg_off++; - const buf_T *const buf = tv_get_buf(&argvars[0], false); - emsg_off--; - if (buf == NULL) { - return; - } - - list_T *const l = tv_list_alloc(buf->b_changelistlen); - tv_list_append_list(rettv->vval.v_list, l); - // The current window change list index tracks only the position in the - // current buffer change list. For other buffers, use the change list - // length as the current index. - tv_list_append_number(rettv->vval.v_list, - (buf == curwin->w_buffer) - ? curwin->w_changelistidx - : buf->b_changelistlen); - - for (int i = 0; i < buf->b_changelistlen; i++) { - if (buf->b_changelist[i].mark.lnum == 0) { - continue; - } - dict_T *const d = tv_dict_alloc(); - tv_list_append_dict(l, d); - tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum); - tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col); - tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd); - } -} - -/* - * "getchar()" function - */ -static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - varnumber_T n; - bool error = false; - - no_mapping++; - for (;; ) { - // Position the cursor. Needed after a message that ends in a space, - // or if event processing caused a redraw. - ui_cursor_goto(msg_row, msg_col); - - if (argvars[0].v_type == VAR_UNKNOWN) { - // getchar(): blocking wait. - if (!(char_avail() || using_script() || input_available())) { - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); - if (!multiqueue_empty(main_loop.events)) { - multiqueue_process_events(main_loop.events); - continue; - } - } - n = safe_vgetc(); - } else if (tv_get_number_chk(&argvars[0], &error) == 1) { - // getchar(1): only check if char avail - n = vpeekc_any(); - } else if (error || vpeekc_any() == NUL) { - // illegal argument or getchar(0) and no char avail: return zero - n = 0; - } else { - // getchar(0) and char avail: return char - n = safe_vgetc(); - } - - if (n == K_IGNORE) { - continue; - } - break; - } - no_mapping--; - - vimvars[VV_MOUSE_WIN].vv_nr = 0; - vimvars[VV_MOUSE_WINID].vv_nr = 0; - vimvars[VV_MOUSE_LNUM].vv_nr = 0; - vimvars[VV_MOUSE_COL].vv_nr = 0; - - rettv->vval.v_number = n; - if (IS_SPECIAL(n) || mod_mask != 0) { - char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */ - int i = 0; - - /* Turn a special key into three bytes, plus modifier. */ - if (mod_mask != 0) { - temp[i++] = K_SPECIAL; - temp[i++] = KS_MODIFIER; - temp[i++] = mod_mask; - } - if (IS_SPECIAL(n)) { - temp[i++] = K_SPECIAL; - temp[i++] = K_SECOND(n); - temp[i++] = K_THIRD(n); - } else { - i += utf_char2bytes(n, temp + i); - } - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(temp); - - if (is_mouse_key(n)) { - int row = mouse_row; - int col = mouse_col; - int grid = mouse_grid; - win_T *win; - linenr_T lnum; - win_T *wp; - int winnr = 1; - - if (row >= 0 && col >= 0) { - /* Find the window at the mouse coordinates and compute the - * text position. */ - win = mouse_find_win(&grid, &row, &col); - if (win == NULL) { - return; - } - (void)mouse_comp_pos(win, &row, &col, &lnum); - for (wp = firstwin; wp != win; wp = wp->w_next) - ++winnr; - vimvars[VV_MOUSE_WIN].vv_nr = winnr; - vimvars[VV_MOUSE_WINID].vv_nr = wp->handle; - vimvars[VV_MOUSE_LNUM].vv_nr = lnum; - vimvars[VV_MOUSE_COL].vv_nr = col + 1; - } - } - } -} - -/* - * "getcharmod()" function - */ -static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = mod_mask; -} - -/* - * "getcharsearch()" function - */ -static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_str(dict, S_LEN("char"), last_csearch()); - tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward()); - tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); -} - -/* - * "getcmdline()" function - */ -static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_cmdline_str(); -} - -/* - * "getcmdpos()" function - */ -static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_pos() + 1; -} - -/* - * "getcmdtype()" function - */ -static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = get_cmdline_type(); -} - -/* - * "getcmdwintype()" function - */ -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = cmdwin_type; -} - -// "getcompletion()" function -static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *pat; - expand_T xpc; - bool filtered = false; - int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH - | WILD_NO_BEEP; - - if (argvars[2].v_type != VAR_UNKNOWN) { - filtered = (bool)tv_get_number_chk(&argvars[2], NULL); - } - - if (p_wic) { - options |= WILD_ICASE; - } - - // For filtered results, 'wildignore' is used - if (!filtered) { - options |= WILD_KEEP_ALL; - } - - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - EMSG(_(e_invarg)); - return; - } - - if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { - set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - goto theend; - } - - ExpandInit(&xpc); - xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_context = cmdcomplete_str_to_type( - (char_u *)tv_get_string(&argvars[1])); - if (xpc.xp_context == EXPAND_NOTHING) { - EMSG2(_(e_invarg2), argvars[1].vval.v_string); - return; - } - - if (xpc.xp_context == EXPAND_MENUS) { - set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_CSCOPE) { - set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_SIGN) { - set_context_in_sign_cmd(&xpc, xpc.xp_pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - -theend: - pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - - 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); -} - -/// `getcwd([{win}[, {tab}]])` function -/// -/// Every scope not specified implies the currently selected scope object. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. - }; - - char_u *cwd = NULL; // Current working directory to print - char_u *from = NULL; // The original string to copy - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - // If there is no argument there are no more scopes after it, break out. - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - scope_number[i] = argvars[i].vval.v_number; - // It is an error for the scope number to be less than `-1`. - if (scope_number[i] < -1) { - EMSG(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); - if (!tp) { - EMSG(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { - EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - EMSG(_("E5002: Cannot find window number.")); - return; - } - } - } - - cwd = xmalloc(MAXPATHL); - - switch (scope) { - case kCdScopeWindow: - assert(win); - from = win->w_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeTab: - assert(tp); - from = tp->tp_localdir; - if (from) { - break; - } - FALLTHROUGH; - case kCdScopeGlobal: - if (globaldir) { // `globaldir` is not always set. - from = globaldir; - } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. - from = (char_u *)""; // Return empty string on failure. - } - break; - case kCdScopeInvalid: // We should never get here - assert(false); - } - - if (from) { - xstrlcpy((char *)cwd, (char *)from, MAXPATHL); - } - - rettv->vval.v_string = vim_strsave(cwd); -#ifdef BACKSLASH_IN_FILENAME - slash_adjust(rettv->vval.v_string); -#endif - - xfree(cwd); -} - -/* - * "getfontname()" function - */ -static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; -} - -/* - * "getfperm({fname})" function - */ -static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char *perm = NULL; - char_u flags[] = "rwx"; - - const char *filename = tv_get_string(&argvars[0]); - int32_t file_perm = os_getperm(filename); - if (file_perm >= 0) { - perm = xstrdup("---------"); - for (int i = 0; i < 9; i++) { - if (file_perm & (1 << (8 - i))) { - perm[i] = flags[i % 3]; - } - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)perm; -} - -/* - * "getfsize({fname})" function - */ -static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_NUMBER; - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir((const char_u *)fname)) { - rettv->vval.v_number = 0; - } else { - rettv->vval.v_number = (varnumber_T)filesize; - - /* non-perfect check for overflow */ - if ((uint64_t)rettv->vval.v_number != filesize) { - rettv->vval.v_number = -2; - } - } - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "getftime({fname})" function - */ -static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *fname = tv_get_string(&argvars[0]); - - FileInfo file_info; - if (os_fileinfo(fname, &file_info)) { - rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec; - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "getftype({fname})" function - */ -static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *type = NULL; - char *t; - - const char *fname = tv_get_string(&argvars[0]); - - rettv->v_type = VAR_STRING; - FileInfo file_info; - if (os_fileinfo_link(fname, &file_info)) { - uint64_t mode = file_info.stat.st_mode; -#ifdef S_ISREG - if (S_ISREG(mode)) - t = "file"; - else if (S_ISDIR(mode)) - t = "dir"; -# ifdef S_ISLNK - else if (S_ISLNK(mode)) - t = "link"; -# endif -# ifdef S_ISBLK - else if (S_ISBLK(mode)) - t = "bdev"; -# endif -# ifdef S_ISCHR - else if (S_ISCHR(mode)) - t = "cdev"; -# endif -# ifdef S_ISFIFO - else if (S_ISFIFO(mode)) - t = "fifo"; -# endif -# ifdef S_ISSOCK - else if (S_ISSOCK(mode)) - t = "socket"; -# endif - else - t = "other"; -#else -# ifdef S_IFMT - switch (mode & S_IFMT) { - case S_IFREG: t = "file"; break; - case S_IFDIR: t = "dir"; break; -# ifdef S_IFLNK - case S_IFLNK: t = "link"; break; -# endif -# ifdef S_IFBLK - case S_IFBLK: t = "bdev"; break; -# endif -# ifdef S_IFCHR - case S_IFCHR: t = "cdev"; break; -# endif -# ifdef S_IFIFO - case S_IFIFO: t = "fifo"; break; -# endif -# ifdef S_IFSOCK - case S_IFSOCK: t = "socket"; break; -# endif - default: t = "other"; - } -# else - if (os_isdir((const char_u *)fname)) { - t = "dir"; - } else { - t = "file"; - } -# endif -#endif - type = vim_strsave((char_u *)t); - } - rettv->vval.v_string = type; -} - -// "getjumplist()" function -static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); - if (wp == NULL) { - return; - } - - cleanup_jumplist(wp, true); - - list_T *const l = tv_list_alloc(wp->w_jumplistlen); - tv_list_append_list(rettv->vval.v_list, l); - tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx); - - for (int i = 0; i < wp->w_jumplistlen; i++) { - if (wp->w_jumplist[i].fmark.mark.lnum == 0) { - continue; - } - dict_T *const d = tv_dict_alloc(); - tv_list_append_dict(l, d); - tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum); - tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col); - tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd); - tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum); - if (wp->w_jumplist[i].fname != NULL) { - tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname); - } - } -} - -/* - * "getline(lnum, [end])" function - */ -static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T end; - bool retlist; - - const linenr_T lnum = tv_get_lnum(argvars); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = lnum; - retlist = false; - } else { - end = tv_get_lnum(&argvars[1]); - retlist = true; - } - - get_buffer_lines(curbuf, lnum, end, retlist, rettv); -} - -static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, - typval_T *rettv) -{ - if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv, kListLenMayKnow); - if (is_qf || wp != NULL) { - (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list); - } - } else { - tv_dict_alloc_ret(rettv); - if (is_qf || wp != NULL) { - if (what_arg->v_type == VAR_DICT) { - dict_T *d = what_arg->vval.v_dict; - - if (d != NULL) { - qf_get_properties(wp, d, rettv->vval.v_dict); - } - } else { - EMSG(_(e_dictreq)); - } - } - } -} - -/// "getloclist()" function -static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - get_qf_loc_list(false, wp, &argvars[1], rettv); -} - -/* - * "getmatches()" function - */ -static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - matchitem_T *cur = curwin->w_match_head; - int i; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - while (cur != NULL) { - dict_T *dict = tv_dict_alloc(); - if (cur->match.regprog == NULL) { - // match added with matchaddpos() - for (i = 0; i < MAXPOSMATCH; i++) { - llpos_T *llpos; - char buf[30]; // use 30 to avoid compiler warning - - llpos = &cur->pos.pos[i]; - if (llpos->lnum == 0) { - break; - } - list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); - tv_list_append_number(l, (varnumber_T)llpos->lnum); - if (llpos->col > 0) { - tv_list_append_number(l, (varnumber_T)llpos->col); - tv_list_append_number(l, (varnumber_T)llpos->len); - } - int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); - assert((size_t)len < sizeof(buf)); - tv_dict_add_list(dict, buf, (size_t)len, l); - } - } else { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); - } - tv_dict_add_str(dict, S_LEN("group"), - (const char *)syn_id2name(cur->hlg_id)); - tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); - tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); - - if (cur->conceal_char) { - char buf[MB_MAXBYTES + 1]; - - buf[utf_char2bytes((int)cur->conceal_char, (char_u *)buf)] = NUL; - tv_dict_add_str(dict, S_LEN("conceal"), buf); - } - - tv_list_append_dict(rettv->vval.v_list, dict); - cur = cur->next; - } -} - -/* - * "getpid()" function - */ -static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = os_get_pid(); -} - -static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) -{ - pos_T *fp; - int fnum = -1; - - if (getcurpos) { - fp = &curwin->w_cursor; - } else { - fp = var2fpos(&argvars[0], true, &fnum); - } - - list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); - tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)fp->lnum - : (varnumber_T)0)); - tv_list_append_number( - l, ((fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0)); - tv_list_append_number( - l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); - if (getcurpos) { - const int save_set_curswant = curwin->w_set_curswant; - const colnr_T save_curswant = curwin->w_curswant; - const colnr_T save_virtcol = curwin->w_virtcol; - - update_curswant(); - tv_list_append_number(l, (curwin->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL - : (varnumber_T)curwin->w_curswant + 1)); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (save_set_curswant) { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } -} - -/* - * "getcurpos(string)" function - */ -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getpos_both(argvars, rettv, true); -} - -/* - * "getpos(string)" function - */ -static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getpos_both(argvars, rettv, false); -} - -/// "getqflist()" functions -static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_qf_loc_list(true, NULL, &argvars[0], rettv); -} - -/// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *strregname; - int arg2 = false; - bool return_list = false; - bool error = false; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - arg2 = tv_get_number_chk(&argvars[1], &error); - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - return_list = tv_get_number_chk(&argvars[2], &error); - } - } - } else { - strregname = (const char *)vimvars[VV_REG].vv_str; - } - - if (error) { - return; - } - - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; - } - - if (return_list) { - rettv->v_type = VAR_LIST; - rettv->vval.v_list = - get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); - if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(0); - } - tv_list_ref(rettv->vval.v_list); - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); - } -} - -/* - * "getregtype()" function - */ -static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *strregname; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { // Type error; errmsg already given. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - return; - } - } else { - // Default to v:register. - strregname = (const char *)vimvars[VV_REG].vv_str; - } - - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; - } - - colnr_T reglen = 0; - char buf[NUMBUFLEN + 2]; - MotionType reg_type = get_reg_type(regname, ®len); - format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)xstrdup(buf); -} - -/// Returns information (variables, options, etc.) about a tab page -/// as a dictionary. -static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - - list_T *const l = tv_list_alloc(kListLenMayKnow); - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - tv_list_append_number(l, (varnumber_T)wp->handle); - } - tv_dict_add_list(dict, S_LEN("windows"), l); - - // Make a reference to tabpage variables - tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); - - return dict; -} - -/// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tparg = NULL; - - tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN - ? 1 - : kListLenMayKnow)); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one tab page - tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tparg == NULL) { - return; - } - } - - // Get information about a specific tab page or all tab pages - int tpnr = 0; - FOR_ALL_TABS(tp) { - tpnr++; - if (tparg != NULL && tp != tparg) { - continue; - } - dict_T *const d = get_tabpage_info(tp, tpnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (tparg != NULL) { - return; - } - } -} - -/* - * "gettabvar()" function - */ -static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *oldcurwin; - tabpage_T *oldtabpage; - bool done = false; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const varname = tv_get_string_chk(&argvars[1]); - tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tp != NULL && varname != NULL) { - // Set tp to be our tabpage, temporarily. Also set the window to the - // first window in the tabpage, otherwise the window is not valid. - win_T *const window = tp == curtab || tp->tp_firstwin == NULL - ? firstwin - : tp->tp_firstwin; - if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { - // look up the variable - // Let gettabvar({nr}, "") return the "t:" dictionary. - const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', - varname, strlen(varname), - false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - - // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); - } - - if (!done && argvars[2].v_type != VAR_UNKNOWN) { - tv_copy(&argvars[2], rettv); - } -} - -/* - * "gettabwinvar()" function - */ -static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 1); -} - -// "gettagstack()" function -static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = curwin; // default is current window - - tv_dict_alloc_ret(rettv); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - } - - get_tagstack(wp, rettv->vval.v_dict); -} - -/// Returns information about a window as a dictionary. -static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); - tv_dict_add_nr(dict, S_LEN("winnr"), winnr); - tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); - tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); - tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); - tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); - tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); - tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); - tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); - tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - - tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); - tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); - tv_dict_add_nr(dict, S_LEN("loclist"), - (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); - - // Add a reference to window variables - tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); - - return dict; -} - -/// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wparg = NULL; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp(argvars); - if (wparg == NULL) { - return; - } - } - - // Collect information about either all the windows across all the tab - // pages or one particular window. - int16_t tabnr = 0; - FOR_ALL_TABS(tp) { - tabnr++; - int16_t winnr = 0; - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - winnr++; - if (wparg != NULL && wp != wparg) { - continue; - } - dict_T *const d = get_win_info(wp, tabnr, winnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (wparg != NULL) { - // found information about a specific window - return; - } - } - } -} - -// Dummy timer callback. Used by f_wait(). -static void dummy_timer_due_cb(TimeWatcher *tw, void *data) -{ -} - -// Dummy timer close callback. Used by f_wait(). -static void dummy_timer_close_cb(TimeWatcher *tw, void *data) -{ - xfree(tw); -} - -/// "wait(timeout, condition[, interval])" function -static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_NUMBER) { - EMSG2(_(e_invargval), "1"); - return; - } - if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN) - || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) { - EMSG2(_(e_invargval), "3"); - return; - } - - int timeout = argvars[0].vval.v_number; - typval_T expr = argvars[1]; - int interval = argvars[2].v_type == VAR_NUMBER - ? argvars[2].vval.v_number - : 200; // Default. - TimeWatcher *tw = xmalloc(sizeof(TimeWatcher)); - - // Start dummy timer. - time_watcher_init(&main_loop, tw, NULL); - tw->events = main_loop.events; - tw->blockable = true; - time_watcher_start(tw, dummy_timer_due_cb, interval, interval); - - typval_T argv = TV_INITIAL_VALUE; - typval_T exprval = TV_INITIAL_VALUE; - bool error = false; - int save_called_emsg = called_emsg; - called_emsg = false; - - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, - eval_expr_typval(&expr, &argv, 0, &exprval) != OK - || tv_get_number_chk(&exprval, &error) - || called_emsg || error || got_int); - - if (called_emsg || error) { - rettv->vval.v_number = -3; - } else if (got_int) { - got_int = false; - vgetc(); - rettv->vval.v_number = -2; - } else if (tv_get_number_chk(&exprval, &error)) { - rettv->vval.v_number = 0; - } - - called_emsg = save_called_emsg; - - // Stop dummy timer - time_watcher_stop(tw); - time_watcher_close(tw, dummy_timer_close_cb); -} - -// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); -} - -// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); -} - -/* - * "getwinposx()" function - */ -static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/* - * "getwinposy()" function - */ -static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/* - * Find window specified by "vp" in tabpage "tp". - */ -static win_T * -find_win_by_nr ( - typval_T *vp, - tabpage_T *tp /* NULL for current tab page */ -) -{ - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr < 0) { - return NULL; - } - - if (nr == 0) { - return curwin; - } - - // This method accepts NULL as an alias for curtab. - if (tp == NULL) { - tp = curtab; - } - - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - if (nr >= LOWEST_WIN_ID) { - if (wp->handle == nr) { - return wp; - } - } else if (--nr <= 0) { - return wp; - } - } - return NULL; -} - -/// Find window specified by "wvp" in tabpage "tvp". -static win_T *find_tabwin(typval_T *wvp, typval_T *tvp) -{ - win_T *wp = NULL; - tabpage_T *tp = NULL; - - if (wvp->v_type != VAR_UNKNOWN) { - if (tvp->v_type != VAR_UNKNOWN) { - long n = tv_get_number(tvp); - if (n >= 0) { - tp = find_tabpage(n); - } - } else { - tp = curtab; - } - - if (tp != NULL) { - wp = find_win_by_nr(wvp, tp); - } - } else { - wp = curwin; - } - - return wp; -} - -/// "getwinvar()" function -static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - getwinvar(argvars, rettv, 0); -} - -/* - * getwinvar() and gettabwinvar() - */ -static void -getwinvar( - typval_T *argvars, - typval_T *rettv, - int off /* 1 for gettabwinvar() */ -) -{ - win_T *win, *oldcurwin; - dictitem_T *v; - tabpage_T *tp = NULL; - tabpage_T *oldtabpage = NULL; - bool done = false; - - if (off == 1) { - tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - } else { - tp = curtab; - } - win = find_win_by_nr(&argvars[off], tp); - const char *varname = tv_get_string_chk(&argvars[off + 1]); - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - emsg_off++; - if (win != NULL && varname != NULL) { - // Set curwin to be our win, temporarily. Also set the tabpage, - // otherwise the window is not valid. Only do this when needed, - // autocommands get blocked. - bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { - if (*varname == '&') { - if (varname[1] == NUL) { - // get all window-local options in a dict - dict_T *opts = get_winbuf_options(false); - - if (opts != NULL) { - tv_dict_set_ret(rettv, opts); - done = true; - } - } else if (get_option_tv(&varname, rettv, 1) == OK) { - // window-local-option - done = true; - } - } else { - // Look up the variable. - // Let getwinvar({nr}, "") return the "w:" dictionary. - v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, - strlen(varname), false); - if (v != NULL) { - tv_copy(&v->di_tv, rettv); - done = true; - } - } - } - - if (need_switch_win) { - // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); - } - } - emsg_off--; - - if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) { - // use the default return value - tv_copy(&argvars[off + 2], rettv); - } -} - -/* - * "glob()" function - */ -static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int options = WILD_SILENT|WILD_USE_NL; - expand_T xpc; - bool error = false; - - /* When the optional second argument is non-zero, don't remove matches - * for 'wildignore' and don't put matches for 'suffixes' at the end. */ - rettv->v_type = VAR_STRING; - if (argvars[1].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[1], &error)) { - options |= WILD_KEEP_ALL; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[2], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[3], &error)) { - options |= WILD_ALLLINKS; - } - } - } - if (!error) { - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - if (p_wic) - options += WILD_ICASE; - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ExpandOne( - &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); - } else { - ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, - WILD_ALL_KEEP); - tv_list_alloc_ret(rettv, xpc.xp_numfiles); - for (int i = 0; i < xpc.xp_numfiles; i++) { - tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], - -1); - } - ExpandCleanup(&xpc); - } - } else - rettv->vval.v_string = NULL; -} - -/// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int flags = 0; // Flags for globpath. - bool error = false; - - // Return a string, or a list if the optional third argument is non-zero. - rettv->v_type = VAR_STRING; - - if (argvars[2].v_type != VAR_UNKNOWN) { - // When the optional second argument is non-zero, don't remove matches - // for 'wildignore' and don't put matches for 'suffixes' at the end. - if (tv_get_number_chk(&argvars[2], &error)) { - flags |= WILD_KEEP_ALL; - } - - if (argvars[3].v_type != VAR_UNKNOWN) { - if (tv_get_number_chk(&argvars[3], &error)) { - tv_list_set_ret(rettv, NULL); - } - if (argvars[4].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[4], &error)) { - flags |= WILD_ALLLINKS; - } - } - } - - char buf1[NUMBUFLEN]; - const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); - if (file != NULL && !error) { - garray_T ga; - ga_init(&ga, (int)sizeof(char_u *), 10); - globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); - - if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); - } else { - tv_list_alloc_ret(rettv, ga.ga_len); - for (int i = 0; i < ga.ga_len; i++) { - tv_list_append_string(rettv->vval.v_list, - ((const char **)(ga.ga_data))[i], -1); - } - } - - ga_clear_strings(&ga); - } else { - rettv->vval.v_string = NULL; - } -} - -// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = ((pat == NULL) - ? NULL - : file_pat_to_reg_pat((char_u *)pat, NULL, NULL, - false)); -} - -/// "has()" function -static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - static const char *const has_list[] = { -#if defined(BSD) && !defined(__APPLE__) - "bsd", -#endif -#ifdef UNIX - "unix", -#endif -#if defined(WIN32) - "win32", -#endif -#if defined(WIN64) || defined(_WIN64) - "win64", -#endif - "fname_case", -#ifdef HAVE_ACL - "acl", -#endif - "autochdir", - "arabic", - "autocmd", - "browsefilter", - "byte_offset", - "cindent", - "cmdline_compl", - "cmdline_hist", - "comments", - "conceal", - "cscope", - "cursorbind", - "cursorshape", -#ifdef DEBUG - "debug", -#endif - "dialog_con", - "diff", - "digraphs", - "eval", /* always present, of course! */ - "ex_extra", - "extra_search", - "file_in_path", - "filterpipe", - "find_in_path", - "float", - "folding", -#if defined(UNIX) - "fork", -#endif - "gettext", -#if defined(HAVE_ICONV) - "iconv", -#endif - "insert_expand", - "jumplist", - "keymap", - "lambda", - "langmap", - "libcall", - "linebreak", - "lispindent", - "listcmds", - "localmap", -#ifdef __APPLE__ - "mac", - "macunix", - "osx", - "osxdarwin", -#endif - "menu", - "mksession", - "modify_fname", - "mouse", - "multi_byte", - "multi_lang", - "num64", - "packages", - "path_extra", - "persistent_undo", - "postscript", - "printer", - "profile", - "pythonx", - "reltime", - "quickfix", - "rightleft", - "scrollbind", - "showcmd", - "cmdline_info", - "shada", - "signs", - "smartindent", - "startuptime", - "statusline", - "spell", - "syntax", -#if !defined(UNIX) - "system", // TODO(SplinterOfChaos): This IS defined for UNIX! -#endif - "tablineat", - "tag_binary", - "termguicolors", - "termresponse", - "textobjects", - "timers", - "title", - "user-commands", /* was accidentally included in 5.4 */ - "user_commands", - "vertsplit", - "virtualedit", - "visual", - "visualextra", - "vreplace", - "wildignore", - "wildmenu", - "windows", - "winaltkeys", - "writebackup", -#if defined(HAVE_WSL) - "wsl", -#endif - "nvim", - }; - - bool n = false; - const char *const name = tv_get_string(&argvars[0]); - for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) { - if (STRICMP(name, has_list[i]) == 0) { - n = true; - break; - } - } - - if (!n) { - if (STRNICMP(name, "patch", 5) == 0) { - if (name[5] == '-' - && strlen(name) >= 11 - && ascii_isdigit(name[6]) - && ascii_isdigit(name[8]) - && ascii_isdigit(name[10])) { - int major = atoi(name + 6); - int minor = atoi(name + 8); - - // Expect "patch-9.9.01234". - n = (major < VIM_VERSION_MAJOR - || (major == VIM_VERSION_MAJOR - && (minor < VIM_VERSION_MINOR - || (minor == VIM_VERSION_MINOR - && has_vim_patch(atoi(name + 10)))))); - } else { - n = has_vim_patch(atoi(name + 5)); - } - } else if (STRNICMP(name, "nvim-", 5) == 0) { - // Expect "nvim-x.y.z" - n = has_nvim_version(name + 5); - } else if (STRICMP(name, "vim_starting") == 0) { - n = (starting != 0); - } else if (STRICMP(name, "ttyin") == 0) { - n = stdin_isatty; - } else if (STRICMP(name, "ttyout") == 0) { - n = stdout_isatty; - } else if (STRICMP(name, "multi_byte_encoding") == 0) { - n = has_mbyte != 0; - } else if (STRICMP(name, "syntax_items") == 0) { - n = syntax_present(curwin); -#ifdef UNIX - } else if (STRICMP(name, "unnamedplus") == 0) { - n = eval_has_provider("clipboard"); -#endif - } - } - - if (!n && eval_has_provider(name)) { - n = true; - } - - rettv->vval.v_number = n; -} - -/* - * "has_key()" function - */ -static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - if (argvars[0].vval.v_dict == NULL) - return; - - rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict, - tv_get_string(&argvars[1]), - -1) != NULL; -} - -/// `haslocaldir([{win}[, {tab}]])` function -/// -/// Returns `1` if the scope object has a local directory, `0` otherwise. If a -/// scope object is not specified the current one is implied. This function -/// share a lot of code with `f_getcwd`. -/// -/// @pre The arguments must be of type number. -/// @pre There may not be more than two arguments. -/// @pre An argument may not be -1 if preceding arguments are not all -1. -/// -/// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Possible scope of working directory to return. - CdScope scope = kCdScopeInvalid; - - // Numbers of the scope objects (window, tab) we want the working directory - // of. A `-1` means to skip this scope, a `0` means the current object. - int scope_number[] = { - [kCdScopeWindow] = 0, // Number of window to look at. - [kCdScopeTab ] = 0, // Number of tab to look at. - }; - - tabpage_T *tp = curtab; // The tabpage to look at. - win_T *win = curwin; // The window to look at. - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - // Pre-conditions and scope extraction together - for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) { - if (argvars[i].v_type == VAR_UNKNOWN) { - break; - } - if (argvars[i].v_type != VAR_NUMBER) { - EMSG(_(e_invarg)); - return; - } - scope_number[i] = argvars[i].vval.v_number; - if (scope_number[i] < -1) { - EMSG(_(e_invarg)); - return; - } - // Use the narrowest scope the user requested - if (scope_number[i] >= 0 && scope == kCdScopeInvalid) { - // The scope is the current iteration step. - scope = i; - } else if (scope_number[i] < 0) { - scope = i + 1; - } - } - - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - - // Find the tabpage by number - if (scope_number[kCdScopeTab] > 0) { - tp = find_tabpage(scope_number[kCdScopeTab]); - if (!tp) { - EMSG(_("E5000: Cannot find tab number.")); - return; - } - } - - // Find the window in `tp` by number, `NULL` if none. - if (scope_number[kCdScopeWindow] >= 0) { - if (scope_number[kCdScopeTab] < 0) { - EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0.")); - return; - } - - if (scope_number[kCdScopeWindow] > 0) { - win = find_win_by_nr(&argvars[0], tp); - if (!win) { - EMSG(_("E5002: Cannot find window number.")); - return; - } - } - } - - switch (scope) { - case kCdScopeWindow: - assert(win); - rettv->vval.v_number = win->w_localdir ? 1 : 0; - break; - case kCdScopeTab: - assert(tp); - rettv->vval.v_number = tp->tp_localdir ? 1 : 0; - break; - case kCdScopeGlobal: - // The global scope never has a local directory - break; - case kCdScopeInvalid: - // We should never get here - assert(false); - } -} - -/* - * "hasmapto()" function - */ -static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *mode; - const char *const name = tv_get_string(&argvars[0]); - bool abbr = false; - char buf[NUMBUFLEN]; - if (argvars[1].v_type == VAR_UNKNOWN) { - mode = "nvo"; - } else { - mode = tv_get_string_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - abbr = tv_get_number(&argvars[2]); - } - } - - if (map_to_exists(name, mode, abbr)) { - rettv->vval.v_number = true; - } else { - rettv->vval.v_number = false; - } -} - -/* - * "histadd()" function - */ -static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType histype; - - rettv->vval.v_number = false; - if (check_restricted() || check_secure()) { - return; - } - const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error - histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; - if (histype != HIST_INVALID) { - char buf[NUMBUFLEN]; - str = tv_get_string_buf(&argvars[1], buf); - if (*str != NUL) { - init_history(); - add_to_history(histype, (char_u *)str, false, NUL); - rettv->vval.v_number = true; - return; - } - } -} - -/* - * "histdel()" function - */ -static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n; - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - n = 0; - } else if (argvars[1].v_type == VAR_UNKNOWN) { - // only one argument: clear entire history - n = clr_history(get_histtype(str, strlen(str), false)); - } else if (argvars[1].v_type == VAR_NUMBER) { - // index given: remove that entry - n = del_history_idx(get_histtype(str, strlen(str), false), - (int)tv_get_number(&argvars[1])); - } else { - // string given: remove all matching entries - char buf[NUMBUFLEN]; - n = del_history_entry(get_histtype(str, strlen(str), false), - (char_u *)tv_get_string_buf(&argvars[1], buf)); - } - rettv->vval.v_number = n; -} - -/* - * "histget()" function - */ -static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - HistoryType type; - int idx; - - const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error - if (str == NULL) { - rettv->vval.v_string = NULL; - } else { - type = get_histtype(str, strlen(str), false); - if (argvars[1].v_type == VAR_UNKNOWN) { - idx = get_history_idx(type); - } else { - idx = (int)tv_get_number_chk(&argvars[1], NULL); - } - // -1 on type error - rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); - } - rettv->v_type = VAR_STRING; -} - -/* - * "histnr()" function - */ -static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *const history = tv_get_string_chk(&argvars[0]); - HistoryType i = history == NULL - ? HIST_INVALID - : get_histtype(history, strlen(history), false); - if (i != HIST_INVALID) { - i = get_history_idx(i); - } - rettv->vval.v_number = i; -} - -/* - * "highlightID(name)" function - */ -static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = syn_name2id( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "highlight_exists()" function - */ -static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = highlight_exists( - (const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "hostname()" function - */ -static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char hostname[256]; - - os_get_hostname(hostname, 256); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave((char_u *)hostname); -} - -/* - * iconv() function - */ -static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - vimconv_T vimconv; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *const str = tv_get_string(&argvars[0]); - char buf1[NUMBUFLEN]; - char_u *const from = enc_canonize(enc_skip( - (char_u *)tv_get_string_buf(&argvars[1], buf1))); - char buf2[NUMBUFLEN]; - char_u *const to = enc_canonize(enc_skip( - (char_u *)tv_get_string_buf(&argvars[2], buf2))); - vimconv.vc_type = CONV_NONE; - convert_setup(&vimconv, from, to); - - // If the encodings are equal, no conversion needed. - if (vimconv.vc_type == CONV_NONE) { - rettv->vval.v_string = (char_u *)xstrdup(str); - } else { - rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL); - } - - convert_setup(&vimconv, NULL, NULL); - xfree(from); - xfree(to); -} - -/* - * "indent()" function - */ -static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - rettv->vval.v_number = get_indent_lnum(lnum); - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "index()" function - */ -static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - long idx = 0; - bool ic = false; - - rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - list_T *const l = argvars[0].vval.v_list; - if (l != NULL) { - listitem_T *item = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - // Start at specified item. - idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); - if (error || idx == -1) { - item = NULL; - } else { - item = tv_list_find(l, idx); - assert(item != NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = !!tv_get_number_chk(&argvars[3], &error); - if (error) { - item = NULL; - } - } - } - - for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { - if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { - rettv->vval.v_number = idx; - break; - } - } - } -} - -static int inputsecret_flag = 0; - -/* - * This function is used by f_input() and f_inputdialog() functions. The third - * argument to f_input() specifies the type of completion to use at the - * prompt. The third argument to f_inputdialog() specifies the value to return - * when the user cancels the prompt. - */ -void get_user_input(const typval_T *const argvars, - typval_T *const rettv, const bool inputdialog) - FUNC_ATTR_NONNULL_ALL -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - const char *prompt = ""; - const char *defstr = ""; - const char *cancelreturn = NULL; - const char *xp_name = NULL; - Callback input_callback = { .type = kCallbackNone }; - char prompt_buf[NUMBUFLEN]; - char defstr_buf[NUMBUFLEN]; - char cancelreturn_buf[NUMBUFLEN]; - char xp_name_buf[NUMBUFLEN]; - char def[1] = { 0 }; - if (argvars[0].v_type == VAR_DICT) { - if (argvars[1].v_type != VAR_UNKNOWN) { - EMSG(_("E5050: {opts} must be the only argument")); - return; - } - dict_T *const dict = argvars[0].vval.v_dict; - prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); - if (prompt == NULL) { - return; - } - defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, ""); - if (defstr == NULL) { - return; - } - cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), - cancelreturn_buf, def); - if (cancelreturn == NULL) { // error - return; - } - if (*cancelreturn == NUL) { - cancelreturn = NULL; - } - xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), - xp_name_buf, def); - if (xp_name == NULL) { // error - return; - } - if (xp_name == def) { // default to NULL - xp_name = NULL; - } - if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) { - return; - } - } else { - prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); - if (prompt == NULL) { - return; - } - if (argvars[1].v_type != VAR_UNKNOWN) { - defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf); - if (defstr == NULL) { - return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const arg2 = tv_get_string_buf_chk(&argvars[2], - cancelreturn_buf); - if (arg2 == NULL) { - return; - } - if (inputdialog) { - cancelreturn = arg2; - } else { - xp_name = arg2; - } - } - } - } - - int xp_type = EXPAND_NOTHING; - char *xp_arg = NULL; - if (xp_name != NULL) { - // input() with a third argument: completion - const int xp_namelen = (int)strlen(xp_name); - - uint32_t argt; - if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, - &argt, (char_u **)&xp_arg) == FAIL) { - return; - } - } - - const bool cmd_silent_save = cmd_silent; - - cmd_silent = false; // Want to see the prompt. - // Only the part of the message after the last NL is considered as - // prompt for the command line, unlsess cmdline is externalized - const char *p = prompt; - if (!ui_has(kUICmdline)) { - const char *lastnl = strrchr(prompt, '\n'); - if (lastnl != NULL) { - p = lastnl+1; - msg_start(); - msg_clr_eos(); - msg_puts_attr_len(prompt, p - prompt, echo_attr); - msg_didout = false; - msg_starthere(); - } - } - cmdline_row = msg_row; - - stuffReadbuffSpec(defstr); - - const int save_ex_normal_busy = ex_normal_busy; - ex_normal_busy = 0; - rettv->vval.v_string = - (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, - xp_type, xp_arg, input_callback); - ex_normal_busy = save_ex_normal_busy; - callback_free(&input_callback); - - if (rettv->vval.v_string == NULL && cancelreturn != NULL) { - rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); - } - - xfree(xp_arg); - - // Since the user typed this, no need to wait for return. - need_wait_return = false; - msg_didout = false; - cmd_silent = cmd_silent_save; -} - -/* - * "input()" function - * Also handles inputsecret() when inputsecret is set. - */ -static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_user_input(argvars, rettv, FALSE); -} - -/* - * "inputdialog()" function - */ -static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - get_user_input(argvars, rettv, TRUE); -} - -/* - * "inputlist()" function - */ -static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int selected; - int mouse_used; - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "inputlist()"); - return; - } - - msg_start(); - msg_row = Rows - 1; /* for when 'cmdheight' > 1 */ - lines_left = Rows; /* avoid more prompt */ - msg_scroll = TRUE; - msg_clr_eos(); - - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); - msg_putchar('\n'); - }); - - // Ask for choice. - selected = prompt_for_number(&mouse_used); - if (mouse_used) { - selected -= lines_left; - } - - rettv->vval.v_number = selected; -} - - -static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL}; - -/// "inputrestore()" function -static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (!GA_EMPTY(&ga_userinput)) { - ga_userinput.ga_len--; - restore_typeahead((tasave_T *)(ga_userinput.ga_data) - + ga_userinput.ga_len); - // default return is zero == OK - } else if (p_verbose > 1) { - verb_msg(_("called inputrestore() more often than inputsave()")); - rettv->vval.v_number = 1; // Failed - } -} - -/// "inputsave()" function -static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Add an entry to the stack of typeahead storage. - tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); - save_typeahead(p); -} - -/// "inputsecret()" function -static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - cmdline_star++; - inputsecret_flag++; - f_input(argvars, rettv, NULL); - cmdline_star--; - inputsecret_flag--; -} - -/* - * "insert()" function - */ -static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - list_T *l; - bool error = false; - - if (argvars[0].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "insert()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { - long before = 0; - if (argvars[2].v_type != VAR_UNKNOWN) { - before = tv_get_number_chk(&argvars[2], &error); - } - if (error) { - // type error; errmsg already given - return; - } - - listitem_T *item = NULL; - if (before != tv_list_len(l)) { - item = tv_list_find(l, before); - if (item == NULL) { - EMSGN(_(e_listidx), before); - l = NULL; - } - } - if (l != NULL) { - tv_list_insert_tv(l, &argvars[1], item); - tv_copy(&argvars[0], rettv); - } - } -} - -/* - * "invert(expr)" function - */ -static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); -} - -/* - * "isdirectory()" function - */ -static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); -} - -/* - * "islocked()" function - */ -static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - lval_T lv; - dictitem_T *di; - - rettv->vval.v_number = -1; - const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]), - NULL, - &lv, false, false, - GLV_NO_AUTOLOAD|GLV_READ_ONLY, - FNE_CHECK_START); - if (end != NULL && lv.ll_name != NULL) { - if (*end != NUL) { - EMSG(_(e_trailing)); - } else { - if (lv.ll_tv == NULL) { - di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true); - if (di != NULL) { - // Consider a variable locked when: - // 1. the variable itself is locked - // 2. the value of the variable is locked. - // 3. the List or Dict value is locked. - rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) - || tv_islocked(&di->di_tv)); - } - } else if (lv.ll_range) { - EMSG(_("E786: Range not allowed")); - } else if (lv.ll_newkey != NULL) { - EMSG2(_(e_dictkey), lv.ll_newkey); - } else if (lv.ll_list != NULL) { - // List item. - rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); - } else { - // Dictionary item. - rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); - } - } - } - - clear_lval(&lv); -} - -// "isinf()" function -static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type == VAR_FLOAT - && xisinf(argvars[0].vval.v_float)) { - rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1; - } -} - -// "isnan()" function -static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT - && xisnan(argvars[0].vval.v_float); -} - -/// Turn a dictionary into a list -/// -/// @param[in] tv Dictionary to convert. Is checked for actually being -/// a dictionary, will give an error if not. -/// @param[out] rettv Location where result will be saved. -/// @param[in] what What to save in rettv. -static void dict_list(typval_T *const tv, typval_T *const rettv, - const DictListType what) -{ - if (tv->v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - if (tv->vval.v_dict == NULL) { - return; - } - - tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); - - TV_DICT_ITER(tv->vval.v_dict, di, { - typval_T tv_item = { .v_lock = VAR_UNLOCKED }; - - switch (what) { - case kDictListKeys: { - tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = vim_strsave(di->di_key); - break; - } - case kDictListValues: { - tv_copy(&di->di_tv, &tv_item); - break; - } - case kDictListItems: { - // items() - list_T *const sub_l = tv_list_alloc(2); - tv_item.v_type = VAR_LIST; - tv_item.vval.v_list = sub_l; - tv_list_ref(sub_l); - - tv_list_append_owned_tv(sub_l, (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), - }); - - tv_list_append_tv(sub_l, &di->di_tv); - - break; - } - } - - tv_list_append_owned_tv(rettv->vval.v_list, tv_item); - }); -} - -/// "id()" function -static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmalloc(len + 1); - vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p", - dummy_ap, argvars); -} - -/* - * "items(dict)" function - */ -static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 2); -} - -// "jobpid(id)" function -static void f_jobpid(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) { - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - Process *proc = (Process *)&data->stream.proc; - rettv->vval.v_number = proc->pid; -} - -// "jobresize(job, width, height)" function -static void f_jobresize(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_NUMBER - || argvars[2].v_type != VAR_NUMBER) { - // job id, width, height - EMSG(_(e_invarg)); - return; - } - - - Channel *data = find_job(argvars[0].vval.v_number, true); - if (!data) { - return; - } - - if (data->stream.proc.type != kProcessTypePty) { - EMSG(_(e_channotpty)); - return; - } - - pty_process_resize(&data->stream.pty, argvars[1].vval.v_number, - argvars[2].vval.v_number); - rettv->vval.v_number = 1; -} - -/// Builds a process argument vector from a VimL object (typval_T). -/// -/// @param[in] cmd_tv VimL object -/// @param[out] cmd Returns the command or executable name. -/// @param[out] executable Returns `false` if argv[0] is not executable. -/// -/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. -/// Else, string values of `cmd_tv` copied to a (char **) list with -/// argv[0] resolved to full path ($PATHEXT-resolved on Windows). -static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) -{ - if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics". - const char *cmd_str = tv_get_string(cmd_tv); - if (cmd) { - *cmd = cmd_str; - } - return shell_build_argv(cmd_str, NULL); - } - - if (cmd_tv->v_type != VAR_LIST) { - EMSG2(_(e_invarg2), "expected String or List"); - return NULL; - } - - list_T *argl = cmd_tv->vval.v_list; - int argc = tv_list_len(argl); - if (!argc) { - EMSG(_(e_invarg)); // List must have at least one item. - return NULL; - } - - const char *arg0 = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); - char *exe_resolved = NULL; - if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) { - if (arg0 && executable) { - char buf[IOSIZE]; - snprintf(buf, sizeof(buf), "'%s' is not executable", arg0); - EMSG3(_(e_invargNval), "cmd", buf); - *executable = false; - } - return NULL; - } - - if (cmd) { - *cmd = exe_resolved; - } - - // Build the argument vector - int i = 0; - char **argv = xcalloc(argc + 1, sizeof(char *)); - TV_LIST_ITER_CONST(argl, arg, { - const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg)); - if (!a) { - // Did emsg in tv_get_string_chk; just deallocate argv. - shell_free_argv(argv); - xfree(exe_resolved); - return NULL; - } - argv[i++] = xstrdup(a); - }); - // Replace argv[0] with absolute path. The only reason for this is to make - // $PATHEXT work on Windows with jobstart([…]). #9569 - xfree(argv[0]); - argv[0] = exe_resolved; - - return argv; -} - -// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - - if (check_restricted() || check_secure()) { - return; - } - - bool executable = true; - char **argv = tv_to_argv(&argvars[0], NULL, &executable); - char **env = NULL; - if (!argv) { - rettv->vval.v_number = executable ? 0 : -1; - return; // Did error message in tv_to_argv. - } - - if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) { - // Wrong argument types - EMSG2(_(e_invarg2), "expected dictionary"); - shell_free_argv(argv); - return; - } - - - dict_T *job_opts = NULL; - bool detach = false; - bool rpc = false; - bool pty = false; - bool clear_env = false; - 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) { - job_opts = argvars[1].vval.v_dict; - - detach = tv_dict_get_number(job_opts, "detach") != 0; - rpc = tv_dict_get_number(job_opts, "rpc") != 0; - pty = tv_dict_get_number(job_opts, "pty") != 0; - clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; - if (pty && rpc) { - EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set"); - shell_free_argv(argv); - return; - } - - char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && strlen(new_cwd) > 0) { - cwd = new_cwd; - // The new cwd must be a directory. - if (!os_isdir_executable((const char *)cwd)) { - EMSG2(_(e_invarg2), "expected valid directory"); - shell_free_argv(argv); - return; - } - } - dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); - if (job_env) { - if (job_env->di_tv.v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "env"); - shell_free_argv(argv); - return; - } - - size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); - size_t i = 0; - size_t env_size = 0; - - if (clear_env) { - // + 1 for last null entry - env = xmalloc((custom_env_size + 1) * sizeof(*env)); - env_size = 0; - } else { - env_size = os_get_fullenv_size(); - - env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); - - os_copy_fullenv(env, env_size); - i = env_size; - } - assert(env); // env must be allocated at this point - - TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { - const char *str = tv_get_string(&var->di_tv); - assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; - env[i] = xmalloc(len); - snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); - i++; - }); - - // must be null terminated - env[env_size + custom_env_size] = NULL; - } - - - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { - shell_free_argv(argv); - return; - } - } - - uint16_t width = 0, height = 0; - char *term_name = NULL; - - if (pty) { - 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); - } - - Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty, - rpc, detach, cwd, width, height, - term_name, env, &rettv->vval.v_number); - if (chan) { - channel_create_event(chan, NULL); - } -} - -// "jobstop()" function -static void f_jobstop(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) { - // Only argument is the job id - EMSG(_(e_invarg)); - return; - } - - Channel *data = find_job(argvars[0].vval.v_number, false); - if (!data) { - return; - } - - const char *error = NULL; - if (data->is_rpc) { - // Ignore return code, but show error later. - (void)channel_close(data->id, kChannelPartRpc, &error); - } - process_stop((Process *)&data->stream.proc); - rettv->vval.v_number = 1; - if (error) { - EMSG(error); - } -} - -// "jobwait(ids[, timeout])" function -static void f_jobwait(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_LIST || (argvars[1].v_type != VAR_NUMBER - && argvars[1].v_type != VAR_UNKNOWN)) { - EMSG(_(e_invarg)); - return; - } - - ui_busy_start(); - list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); - MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); - - // Validate, prepare jobs for waiting. - int i = 0; - TV_LIST_ITER_CONST(args, arg, { - Channel *chan = NULL; - if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER - || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { - jobs[i] = NULL; // Invalid job. - } else { - jobs[i] = chan; - channel_incref(chan); - if (chan->stream.proc.status < 0) { - // Process any pending events on the job's queue before temporarily - // replacing it. - multiqueue_process_events(chan->events); - multiqueue_replace_parent(chan->events, waiting_jobs); - } - } - i++; - }); - - int remaining = -1; - uint64_t before = 0; - if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) { - remaining = argvars[1].vval.v_number; - before = os_hrtime(); - } - - for (i = 0; i < tv_list_len(args); i++) { - if (remaining == 0) { - break; // Timeout. - } - if (jobs[i] == NULL) { - continue; // Invalid job, will assign status=-3 below. - } - int status = process_wait(&jobs[i]->stream.proc, remaining, - waiting_jobs); - if (status < 0) { - break; // Interrupted (CTRL-C) or timeout, skip remaining jobs. - } - if (remaining > 0) { - uint64_t now = os_hrtime(); - remaining = MIN(0, remaining - (int)((now - before) / 1000000)); - before = now; - } - } - - list_T *const rv = tv_list_alloc(tv_list_len(args)); - - // For each job: - // * Restore its parent queue if the job is still alive. - // * Append its status to the output list, or: - // -3 for "invalid job id" - // -2 for "interrupted" (user hit CTRL-C) - // -1 for jobs that were skipped or timed out - for (i = 0; i < tv_list_len(args); i++) { - if (jobs[i] == NULL) { - tv_list_append_number(rv, -3); - continue; - } - 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(); - tv_list_ref(rv); - rettv->v_type = VAR_LIST; - rettv->vval.v_list = rv; -} - -/* - * "join()" function - */ -static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - if (argvars[0].v_type != VAR_LIST) { - EMSG(_(e_listreq)); - return; - } - const char *const sep = (argvars[1].v_type == VAR_UNKNOWN - ? " " - : tv_get_string_chk(&argvars[1])); - - rettv->v_type = VAR_STRING; - - if (sep != NULL) { - garray_T ga; - ga_init(&ga, (int)sizeof(char), 80); - tv_list_join(&ga, argvars[0].vval.v_list, sep); - ga_append(&ga, NUL); - rettv->vval.v_string = (char_u *)ga.ga_data; - } else { - rettv->vval.v_string = NULL; - } -} - -/// json_decode() function -static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char numbuf[NUMBUFLEN]; - const char *s = NULL; - char *tofree = NULL; - size_t len; - if (argvars[0].v_type == VAR_LIST) { - if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) { - EMSG(_("E474: Failed to convert list to string")); - return; - } - s = tofree; - if (s == NULL) { - assert(len == 0); - s = ""; - } - } else { - s = tv_get_string_buf_chk(&argvars[0], numbuf); - if (s) { - len = strlen(s); - } else { - return; - } - } - if (json_decode_string(s, len, rettv) == FAIL) { - emsgf(_("E474: Failed to parse %.*s"), (int)len, s); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = 0; - } - assert(rettv->v_type != VAR_UNKNOWN); - xfree(tofree); -} - -/// json_encode() function -static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL); -} - -/* - * "keys()" function - */ -static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_list(argvars, rettv, 0); -} - -/* - * "last_buffer_nr()" function. - */ -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int n = 0; - - FOR_ALL_BUFFERS(buf) { - if (n < buf->b_fnum) { - n = buf->b_fnum; - } - } - - rettv->vval.v_number = n; -} - -/* - * "len()" function - */ -static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_NUMBER: { - rettv->vval.v_number = (varnumber_T)strlen( - tv_get_string(&argvars[0])); - break; - } - case VAR_LIST: { - rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list); - break; - } - case VAR_DICT: { - rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict); - break; - } - case VAR_UNKNOWN: - case VAR_SPECIAL: - case VAR_FLOAT: - case VAR_PARTIAL: - case VAR_FUNC: { - EMSG(_("E701: Invalid type for len()")); - break; - } - } -} - -static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) -{ - rettv->v_type = out_type; - if (out_type != VAR_NUMBER) { - rettv->vval.v_string = NULL; - } - - if (check_restricted() || check_secure()) { - return; - } - - // The first two args (libname and funcname) must be strings - if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { - return; - } - - const char *libname = (char *) argvars[0].vval.v_string; - const char *funcname = (char *) argvars[1].vval.v_string; - - VarType in_type = argvars[2].v_type; - - // input variables - char *str_in = (in_type == VAR_STRING) - ? (char *) argvars[2].vval.v_string : NULL; - int64_t int_in = argvars[2].vval.v_number; - - // output variables - char **str_out = (out_type == VAR_STRING) - ? (char **)&rettv->vval.v_string : NULL; - int int_out = 0; - - bool success = os_libcall(libname, funcname, - str_in, int_in, - str_out, &int_out); - - if (!success) { - EMSG2(_(e_libcall), funcname); - return; - } - - if (out_type == VAR_NUMBER) { - rettv->vval.v_number = (varnumber_T)int_out; - } -} - -/* - * "libcall()" function - */ -static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - libcall_common(argvars, rettv, VAR_STRING); -} - -/* - * "libcallnr()" function - */ -static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - libcall_common(argvars, rettv, VAR_NUMBER); -} - -/* - * "line(string)" function - */ -static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = 0; - pos_T *fp; - int fnum; - - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fp != NULL) - lnum = fp->lnum; - rettv->vval.v_number = lnum; -} - -/* - * "line2byte(lnum)" function - */ -static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false); - } - if (rettv->vval.v_number >= 0) { - rettv->vval.v_number++; - } -} - -/* - * "lispindent(lnum)" function - */ -static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const pos_T pos = curwin->w_cursor; - const linenr_T lnum = tv_get_lnum(argvars); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = lnum; - rettv->vval.v_number = get_lisp_indent(); - curwin->w_cursor = pos; - } else { - rettv->vval.v_number = -1; - } -} - -/* - * "localtime()" function - */ -static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (varnumber_T)time(NULL); -} - - -static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) -{ - char_u *keys_buf = NULL; - char_u *rhs; - int mode; - int abbr = FALSE; - int get_dict = FALSE; - mapblock_T *mp; - int buffer_local; - - // Return empty string for failure. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - char_u *keys = (char_u *)tv_get_string(&argvars[0]); - if (*keys == NUL) { - return; - } - - char buf[NUMBUFLEN]; - const char *which; - if (argvars[1].v_type != VAR_UNKNOWN) { - which = tv_get_string_buf_chk(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - abbr = tv_get_number(&argvars[2]); - if (argvars[3].v_type != VAR_UNKNOWN) { - get_dict = tv_get_number(&argvars[3]); - } - } - } else { - which = ""; - } - if (which == NULL) { - return; - } - - mode = get_map_mode((char_u **)&which, 0); - - keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, - CPO_TO_CPO_FLAGS); - rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); - xfree(keys_buf); - - if (!get_dict) { - // Return a string. - if (rhs != NULL) { - if (*rhs == NUL) { - rettv->vval.v_string = vim_strsave((char_u *)""); - } else { - rettv->vval.v_string = (char_u *)str2special_save( - (char *)rhs, false, false); - } - } - - } else { - tv_dict_alloc_ret(rettv); - if (rhs != NULL) { - // Return a dictionary. - mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); - } - } -} - -/// luaeval() function implementation -static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) - FUNC_ATTR_NONNULL_ALL -{ - const char *const str = (const char *)tv_get_string_chk(&argvars[0]); - if (str == NULL) { - return; - } - - executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); -} - -/// Fill a dictionary with all applicable maparg() like dictionaries -/// -/// @param dict The dictionary to be filled -/// @param mp The maphash that contains the mapping information -/// @param buffer_value The "buffer" value -/// @param compatible True for compatible with old maparg() dict -void mapblock_fill_dict(dict_T *const dict, - const mapblock_T *const mp, - long buffer_value, - bool compatible) - FUNC_ATTR_NONNULL_ALL -{ - char *const lhs = str2special_save((const char *)mp->m_keys, - compatible, !compatible); - char *const mapmode = map_mode_to_chars(mp->m_mode); - varnumber_T noremap_value; - - if (compatible) { - // Keep old compatible behavior - // This is unable to determine whether a mapping is a