From 8be423b7ac3b5742deb20a7eba8f5c9680c04500 Mon Sep 17 00:00:00 2001 From: James McCoy Date: Mon, 28 Mar 2022 11:55:12 +0100 Subject: [PATCH 01/20] patch 8.2.4640: some boolean options use "long" instead of "int" Problem: Some boolean options use "long" instead of "int". Solution: Adjust the type. (James McCoy, closes #10033) --- src/option.h | 6 +++--- src/version.c | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/option.h b/src/option.h index 152956167c..e34467514a 100644 --- a/src/option.h +++ b/src/option.h @@ -570,7 +570,7 @@ EXTERN char_u *p_fenc; // 'fileencoding' EXTERN char_u *p_fencs; // 'fileencodings' EXTERN char_u *p_ff; // 'fileformat' EXTERN char_u *p_ffs; // 'fileformats' -EXTERN long p_fic; // 'fileignorecase' +EXTERN int p_fic; // 'fileignorecase' EXTERN char_u *p_ft; // 'filetype' EXTERN char_u *p_fcs; // 'fillchar' EXTERN int p_fixeol; // 'fixendofline' @@ -750,7 +750,7 @@ EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' #endif EXTERN int p_ml; // 'modeline' -EXTERN long p_mle; // 'modelineexpr' +EXTERN int p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN int p_ma; // 'modifiable' EXTERN int p_mod; // 'modified' @@ -1078,7 +1078,7 @@ EXTERN int p_wiv; // 'weirdinvert' EXTERN char_u *p_ww; // 'whichwrap' EXTERN long p_wc; // 'wildchar' EXTERN long p_wcm; // 'wildcharm' -EXTERN long p_wic; // 'wildignorecase' +EXTERN int p_wic; // 'wildignorecase' EXTERN char_u *p_wim; // 'wildmode' #ifdef FEAT_WILDMENU EXTERN int p_wmnu; // 'wildmenu' diff --git a/src/version.c b/src/version.c index 680eef1f53..921e701698 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4640, /**/ 4639, /**/ From 471b3aed3e9c43d4dd53444ceb74f9a4f8a3874a Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Mon, 28 Mar 2022 12:41:19 +0100 Subject: [PATCH 02/20] patch 8.2.4641: may mark the wrong window for redrawing Problem: May mark the wrong window for redrawing. Solution: Use redraw_win_later(). (closes #10032) --- src/move.c | 4 ++-- src/version.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/move.c b/src/move.c index 3d8356fe95..a6d2fde74f 100644 --- a/src/move.c +++ b/src/move.c @@ -147,10 +147,10 @@ redraw_for_cursorcolumn(win_T *wp) { // When 'cursorcolumn' is set need to redraw with SOME_VALID. if (wp->w_p_cuc) - redraw_later(SOME_VALID); + redraw_win_later(wp, SOME_VALID); // When 'cursorlineopt' contains "screenline" need to redraw with VALID. else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) - redraw_later(VALID); + redraw_win_later(wp, VALID); } } #endif diff --git a/src/version.c b/src/version.c index 921e701698..0c1a16670a 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4641, /**/ 4640, /**/ From 859cc21c6b60af07b549456b7d050a03b3e48bc9 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Mon, 28 Mar 2022 15:22:35 +0100 Subject: [PATCH 03/20] patch 8.2.4642: Vim9: in :def function script var cannot be null Problem: Vim9: in :def function script var cannot be null. Solution: Only initialize a script variable when not set to a null value. (closes #10034) --- src/evalvars.c | 10 +++- src/globals.h | 1 + src/testdir/test_vim9_expr.vim | 87 ++++++++++++++++++++++++++++++++++ src/version.c | 2 + src/vim.h | 1 + src/vim9execute.c | 12 +++-- src/vim9script.c | 9 +++- src/vim9type.c | 4 ++ 8 files changed, 119 insertions(+), 7 deletions(-) diff --git a/src/evalvars.c b/src/evalvars.c index b49178f125..202ada1914 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2823,7 +2823,7 @@ eval_variable( { if (tv->v_type == VAR_DICT && tv->vval.v_dict == NULL && ((type != NULL && type != &t_dict_empty) - || !in_vim9script())) + || !in_vim9script())) { tv->vval.v_dict = dict_alloc(); if (tv->vval.v_dict != NULL) @@ -2843,6 +2843,14 @@ eval_variable( tv->vval.v_list->lv_type = alloc_type(type); } } + else if (tv->v_type == VAR_BLOB && tv->vval.v_blob == NULL + && ((type != NULL && type != &t_blob_null) + || !in_vim9script())) + { + tv->vval.v_blob = blob_alloc(); + if (tv->vval.v_blob != NULL) + ++tv->vval.v_blob->bv_refcount; + } } copy_tv(tv, rettv); } diff --git a/src/globals.h b/src/globals.h index 1319b1f4b0..f690efffc4 100644 --- a/src/globals.h +++ b/src/globals.h @@ -405,6 +405,7 @@ EXTERN type_T t_number_bool INIT6(VAR_NUMBER, 0, 0, TTFLAG_STATIC|TTFLAG_BOOL_OK EXTERN type_T t_float INIT6(VAR_FLOAT, 0, 0, TTFLAG_STATIC, NULL, NULL); EXTERN type_T t_string INIT6(VAR_STRING, 0, 0, TTFLAG_STATIC, NULL, NULL); EXTERN type_T t_blob INIT6(VAR_BLOB, 0, 0, TTFLAG_STATIC, NULL, NULL); +EXTERN type_T t_blob_null INIT6(VAR_BLOB, 0, 0, TTFLAG_STATIC, &t_void, NULL); EXTERN type_T t_job INIT6(VAR_JOB, 0, 0, TTFLAG_STATIC, NULL, NULL); EXTERN type_T t_channel INIT6(VAR_CHANNEL, 0, 0, TTFLAG_STATIC, NULL, NULL); diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index 209bb10a5e..e76e1344af 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -890,6 +890,93 @@ def Test_expr4_compare_null() unlet g:null_dict unlet g:not_null_list + # variables declared at script level used in a :def function + lines =<< trim END + vim9script + + var l_decl: list + var l_empty = [] + var l_null = null_list + + def TestList() + assert_false(l_decl == null) + assert_false(l_decl is null_list) + assert_false(l_empty == null) + assert_false(l_empty is null_list) + assert_true(l_null == null) + assert_true(l_null is null_list) + assert_true(l_null == null_list) + + add(l_decl, 6) + assert_equal([6], l_decl) + add(l_empty, 7) + assert_equal([7], l_empty) + var caught = false + try + add(l_null, 9) + catch /E1130:/ + caught = true + endtry + assert_true(caught) + enddef + TestList() + + var b_decl: blob + var b_empty = 0z + var b_null = null_blob + + def TestBlob() + assert_false(b_decl == null) + assert_false(b_decl is null_blob) + assert_false(b_empty == null) + assert_false(b_empty is null_blob) + assert_true(b_null == null) + assert_true(b_null is null_blob) + assert_true(b_null == null_blob) + + add(b_decl, 6) + assert_equal(0z06, b_decl) + add(b_empty, 7) + assert_equal(0z07, b_empty) + var caught = false + try + add(b_null, 9) + catch /E1131:/ + caught = true + endtry + assert_true(caught) + enddef + TestBlob() + + var d_decl: dict + var d_empty = {} + var d_null = null_dict + + def TestDict() + assert_false(d_decl == null) + assert_false(d_decl is null_dict) + assert_false(d_empty == null) + assert_false(d_empty is null_dict) + assert_true(d_null == null) + assert_true(d_null is null_dict) + assert_true(d_null == null_dict) + + d_decl['a'] = 6 + assert_equal({a: 6}, d_decl) + d_empty['b'] = 7 + assert_equal({b: 7}, d_empty) + var caught = false + try + d_null['c'] = 9 + catch /E1103:/ + caught = true + endtry + assert_true(caught) + enddef + TestDict() + END + v9.CheckScriptSuccess(lines) + lines =<< trim END var d: dict = {f: null_function} assert_equal(null_function, d.f) diff --git a/src/version.c b/src/version.c index 0c1a16670a..9d3afeb165 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4642, /**/ 4641, /**/ diff --git a/src/vim.h b/src/vim.h index 13ce032738..d9bd51b1aa 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2229,6 +2229,7 @@ typedef enum { #define ASSIGN_UNPACK 0x10 // using [a, b] = list #define ASSIGN_NO_MEMBER_TYPE 0x20 // use "any" for list and dict member type #define ASSIGN_FOR_LOOP 0x40 // assigning to loop variable +#define ASSIGN_INIT 0x80 // not assigning a value, just a declaration #include "ex_cmds.h" // Ex command defines #include "spell.h" // spell checking stuff diff --git a/src/vim9execute.c b/src/vim9execute.c index 0404eb4123..a95a488068 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1336,20 +1336,22 @@ do_2string(typval_T *tv, int is_2string_any, int tolerant) * When the value of "sv" is a null list of dict, allocate it. */ static void -allocate_if_null(typval_T *tv) +allocate_if_null(svar_T *sv) { + typval_T *tv = sv->sv_tv; + switch (tv->v_type) { case VAR_LIST: - if (tv->vval.v_list == NULL) + if (tv->vval.v_list == NULL && sv->sv_type != &t_list_empty) (void)rettv_list_alloc(tv); break; case VAR_DICT: - if (tv->vval.v_dict == NULL) + if (tv->vval.v_dict == NULL && sv->sv_type != &t_dict_empty) (void)rettv_dict_alloc(tv); break; case VAR_BLOB: - if (tv->vval.v_blob == NULL) + if (tv->vval.v_blob == NULL && sv->sv_type != &t_blob_null) (void)rettv_blob_alloc(tv); break; default: @@ -2891,7 +2893,7 @@ exec_instructions(ectx_T *ectx) sv = get_script_svar(sref, ectx->ec_dfunc_idx); if (sv == NULL) goto theend; - allocate_if_null(sv->sv_tv); + allocate_if_null(sv); if (GA_GROW_FAILS(&ectx->ec_stack, 1)) goto theend; copy_tv(sv->sv_tv, STACK_TV_BOT(0)); diff --git a/src/vim9script.c b/src/vim9script.c index 3e8fe2f653..b793110b92 100644 --- a/src/vim9script.c +++ b/src/vim9script.c @@ -822,7 +822,7 @@ vim9_declare_scriptvar(exarg_T *eap, char_u *arg) init_tv.v_type = VAR_NUMBER; else init_tv.v_type = type->tt_type; - set_var_const(name, 0, type, &init_tv, FALSE, 0, 0); + set_var_const(name, 0, type, &init_tv, FALSE, ASSIGN_INIT, 0); vim_free(name); return p; @@ -925,6 +925,13 @@ update_vim9_script_var( if (*type == NULL) *type = typval2type(tv, get_copyID(), &si->sn_type_list, do_member ? TVTT_DO_MEMBER : 0); + else if ((flags & ASSIGN_INIT) == 0 + && (*type)->tt_type == VAR_BLOB && tv->v_type == VAR_BLOB + && tv->vval.v_blob == NULL) + { + // "var b: blob = null_blob" has a different type. + *type = &t_blob_null; + } if (sv->sv_type_allocated) free_type(sv->sv_type); if (*type != NULL && ((*type)->tt_type == VAR_FUNC diff --git a/src/vim9type.c b/src/vim9type.c index b6687b0896..ff259f4941 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -337,7 +337,11 @@ typval2type_int(typval_T *tv, int copyID, garray_T *type_gap, int flags) if (tv->v_type == VAR_STRING) return &t_string; if (tv->v_type == VAR_BLOB) + { + if (tv->vval.v_blob == NULL) + return &t_blob_null; return &t_blob; + } if (tv->v_type == VAR_LIST) { From 24565cf27bc28cbd73f10e5ddf1e4ef53038a426 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Mon, 28 Mar 2022 18:16:52 +0100 Subject: [PATCH 04/20] patch 8.2.4643: Vim9: variable may be locked unintentionally Problem: Vim9: variable may be locked unintentionally. Solution: Clear "v_lock". (closes #10036) --- src/testdir/test_vim9_builtin.vim | 7 +++++++ src/version.c | 2 ++ src/vim9execute.c | 1 + 3 files changed, 10 insertions(+) diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 24a7f9583e..01f29d9d2b 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -371,10 +371,17 @@ def Test_bufname() assert_fails('bufname([])', 'E1220:') enddef +let s:bufnr_res = 0 + def Test_bufnr() var buf = bufnr() bufnr('%')->assert_equal(buf) + # check the lock is not taken over through the stack + const nr = 10 + bufnr_res = bufnr() + bufnr_res = 12345 + buf = bufnr('Xdummy', true) buf->assert_notequal(-1) exe 'bwipe! ' .. buf diff --git a/src/version.c b/src/version.c index 9d3afeb165..fe7f299cbd 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4643, /**/ 4642, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index a95a488068..c5dc30da04 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -937,6 +937,7 @@ call_prepare(int argcount, typval_T *argvars, ectx_T *ectx) tv = STACK_TV_BOT(-1); tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; + tv->v_lock = 0; return OK; } From 1624639ec8a6c3c99e417a2990f2f02f0d0b6e10 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 29 Mar 2022 11:38:17 +0100 Subject: [PATCH 05/20] patch 8.2.4644: redrawing too often when 'relativenumber' is set Problem: Redrawing too often when 'relativenumber' is set. Solution: Only redraw when the cursor line changed. (Lewis Russell, closes #10040) --- src/change.c | 2 +- src/drawscreen.c | 7 ++++--- src/structs.h | 3 +++ src/version.c | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/change.c b/src/change.c index 54ed684a6d..afc32b7241 100644 --- a/src/change.c +++ b/src/change.c @@ -641,7 +641,7 @@ changed_common( set_topline(wp, wp->w_topline); #endif // Relative numbering may require updating more. - if (wp->w_p_rnu) + if (wp->w_p_rnu && xtra != 0) redraw_win_later(wp, SOME_VALID); #ifdef FEAT_SYN_HL // Cursor line highlighting probably need to be updated with diff --git a/src/drawscreen.c b/src/drawscreen.c index cb4757748f..9f9cb09602 100644 --- a/src/drawscreen.c +++ b/src/drawscreen.c @@ -2507,11 +2507,11 @@ win_update(win_T *wp) } else { - if (wp->w_p_rnu) + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { #ifdef FEAT_FOLDING - // 'relativenumber' set: The text doesn't need to be drawn, but - // the number column nearly always does. + // 'relativenumber' set and the cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. fold_count = foldedCount(wp, lnum, &win_foldinfo); if (fold_count != 0) fold_line(wp, fold_count, &win_foldinfo, lnum, row); @@ -2553,6 +2553,7 @@ win_update(win_T *wp) // update w_last_cursorline. wp->w_last_cursorline = wp->w_p_cul ? wp->w_cursor.lnum : 0; #endif + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; #ifdef FEAT_VTP // Rewrite the character at the end of the screen line. diff --git a/src/structs.h b/src/structs.h index a153336c9c..192693bac4 100644 --- a/src/structs.h +++ b/src/structs.h @@ -3465,6 +3465,9 @@ struct window_S colnr_T w_old_visual_col; // last known start of visual part colnr_T w_old_curswant; // last known value of Curswant + linenr_T w_last_cursor_lnum_rnu; // cursor lnum when 'rnu' was last + // redrawn + lcs_chars_T w_lcs_chars; // 'listchars' characters /* diff --git a/src/version.c b/src/version.c index fe7f299cbd..90b30ab321 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4644, /**/ 4643, /**/ From fd01280d01c2270a320d8c962d24140a8176a400 Mon Sep 17 00:00:00 2001 From: James Cherti <60946298+jamescherti@users.noreply.github.com> Date: Tue, 29 Mar 2022 12:02:57 +0100 Subject: [PATCH 06/20] patch 8.2.4645: 'shortmess' changed when session does not store options Problem: 'shortmess' changed when session does not store options. Solution: Save and restore 'shortmess' if needed. (James Charti, closes #10037) --- src/session.c | 24 ++++++++++++++++--- src/testdir/test_mksession.vim | 43 ++++++++++++++++++++++++++++++++++ src/version.c | 2 ++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/session.c b/src/session.c index fa77f89c8e..d80f11ff7c 100644 --- a/src/session.c +++ b/src/session.c @@ -686,6 +686,11 @@ makeopens( if (put_line(fd, "endif") == FAIL) goto fail; + // save 'shortmess' if not storing options + if ((ssop_flags & SSOP_OPTIONS) == 0 + && put_line(fd, "let s:shortmess_save = &shortmess") == FAIL) + goto fail; + // Now save the current files, current buffer first. if (put_line(fd, "set shortmess=aoO") == FAIL) goto fail; @@ -956,10 +961,23 @@ makeopens( if (put_line(fd, "unlet! s:wipebuf") == FAIL) goto fail; - // Re-apply 'winheight', 'winwidth' and 'shortmess'. - if (fprintf(fd, "set winheight=%ld winwidth=%ld shortmess=%s", - p_wh, p_wiw, p_shm) < 0 || put_eol(fd) == FAIL) + // Re-apply 'winheight' and 'winwidth'. + if (fprintf(fd, "set winheight=%ld winwidth=%ld", + p_wh, p_wiw) < 0 || put_eol(fd) == FAIL) goto fail; + + // Restore 'shortmess'. + if (ssop_flags & SSOP_OPTIONS) + { + if (fprintf(fd, "set shortmess=%s", p_shm) < 0 || put_eol(fd) == FAIL) + goto fail; + } + else + { + if (put_line(fd, "let &shortmess = s:shortmess_save") == FAIL) + goto fail; + } + if (tab_firstwin->w_next != NULL) { // Restore 'winminheight' and 'winminwidth'. diff --git a/src/testdir/test_mksession.vim b/src/testdir/test_mksession.vim index 2c2737e006..247d1797d1 100644 --- a/src/testdir/test_mksession.vim +++ b/src/testdir/test_mksession.vim @@ -1007,6 +1007,49 @@ func Test_mksession_winminheight() set sessionoptions& endfunc +" Test for mksession with and without options restores shortmess +func Test_mksession_shortmess() + " Without options + set sessionoptions-=options + split + mksession! Xtest_mks.out + let found_save = 0 + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + let line = trim(line) + + if line ==# 'let s:shortmess_save = &shortmess' + let found_save += 1 + endif + + if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(1, found_save) + call assert_equal(1, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& + + " With options + set sessionoptions+=options + split + mksession! Xtest_mks.out + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + if line =~# 's:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(0, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& +endfunc + " Test for mksession with 'compatible' option func Test_mksession_compatible() mksession! Xtest_mks1.out diff --git a/src/version.c b/src/version.c index 90b30ab321..03778bd5ca 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4645, /**/ 4644, /**/ From b55986c52d4cd88a22d0b0b0e8a79547ba13e1d5 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 29 Mar 2022 13:24:58 +0100 Subject: [PATCH 07/20] patch 8.2.4646: using buffer line after it has been freed Problem: Using buffer line after it has been freed in old regexp engine. Solution: After getting mark get the line again. --- src/regexp_bt.c | 9 +++++++++ src/testdir/test_regexp_latin.vim | 7 +++++++ src/version.c | 2 ++ 3 files changed, 18 insertions(+) diff --git a/src/regexp_bt.c b/src/regexp_bt.c index 4082f59d35..793faaf6bc 100644 --- a/src/regexp_bt.c +++ b/src/regexp_bt.c @@ -3360,8 +3360,17 @@ regmatch( int mark = OPERAND(scan)[0]; int cmp = OPERAND(scan)[1]; pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; pos = getmark_buf(rex.reg_buf, mark, FALSE); + + // Line may have been freed, get it again. + if (REG_MULTI) + { + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + } + if (pos == NULL // mark doesn't exist || pos->lnum <= 0) // mark isn't set in reg_buf { diff --git a/src/testdir/test_regexp_latin.vim b/src/testdir/test_regexp_latin.vim index 71915be9ed..fc9c81bfee 100644 --- a/src/testdir/test_regexp_latin.vim +++ b/src/testdir/test_regexp_latin.vim @@ -1042,10 +1042,17 @@ endfunc func Test_using_mark_position() " this was using freed memory + " new engine new norm O0 call assert_fails("s/\\%')", 'E486:') bwipe! + + " old engine + new + norm O0 + call assert_fails("s/\\%#=1\\%')", 'E486:') + bwipe! endfunc func Test_using_visual_position() diff --git a/src/version.c b/src/version.c index 03778bd5ca..14fd1c369c 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4646, /**/ 4645, /**/ From 2bdad6126778f907c0b98002bfebf0e611a3f5db Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Tue, 29 Mar 2022 19:52:12 +0100 Subject: [PATCH 08/20] patch 8.2.4647: "source" can read past end of copied line Problem: "source" can read past end of copied line. Solution: Add a terminating NUL. --- src/scriptfile.c | 3 +++ src/testdir/test_source.vim | 9 +++++++++ src/version.c | 2 ++ 3 files changed, 14 insertions(+) diff --git a/src/scriptfile.c b/src/scriptfile.c index a334b2f9fc..39936a4265 100644 --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -1918,7 +1918,10 @@ get_one_sourceline(source_cookie_T *sp) break; // all the lines are processed ga_concat(&ga, ((char_u **)sp->buflines.ga_data)[sp->buf_lnum]); sp->buf_lnum++; + if (ga_grow(&ga, 1) == FAIL) + break; buf = (char_u *)ga.ga_data; + buf[ga.ga_len++] = NUL; } else { diff --git a/src/testdir/test_source.vim b/src/testdir/test_source.vim index 35d1453747..5f45ab413c 100644 --- a/src/testdir/test_source.vim +++ b/src/testdir/test_source.vim @@ -646,4 +646,13 @@ func Test_source_buffer_vim9() %bw! endfunc +func Test_source_buffer_long_line() + " This was reading past the end of the line. + new + norm300gr0 + so + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 14fd1c369c..b18de4c5e5 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4647, /**/ 4646, /**/ From 9247a221ce7800c0ae1b3487112d314b8ab79f53 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Wed, 30 Mar 2022 10:16:05 +0100 Subject: [PATCH 09/20] patch 8.2.4648: handling LSP messages is a bit slow Problem: Handling LSP messages is a bit slow. Solution: Included support for LSP messages. (Yegappan Lakshmanan, closes #10025) --- runtime/doc/channel.txt | 97 ++++++++++- src/channel.c | 298 ++++++++++++++++++++++++++----- src/job.c | 2 + src/json.c | 26 +++ src/proto/json.pro | 1 + src/structs.h | 1 + src/testdir/test_channel.vim | 209 ++++++++++++++++++++++ src/testdir/test_channel_lsp.py | 299 ++++++++++++++++++++++++++++++++ src/version.c | 2 + 9 files changed, 885 insertions(+), 50 deletions(-) create mode 100644 src/testdir/test_channel_lsp.py diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 2182ae6974..6edb50bffd 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -53,6 +53,7 @@ RAW nothing known, Vim cannot tell where a message ends NL every message ends in a NL (newline) character JSON JSON encoding |json_encode()| JS JavaScript style JSON-like encoding |js_encode()| +LSP Language Server Protocol encoding |language-server-protocol| Common combination are: - Using a job connected through pipes in NL mode. E.g., to run a style @@ -130,6 +131,7 @@ When using an IPv6 address, enclose it within square brackets. E.g., "js" - Use JS (JavaScript) encoding, more efficient than JSON. "nl" - Use messages that end in a NL character "raw" - Use raw messages + "lsp" - Use language server protocol encoding *channel-callback* *E921* "callback" A function that is called when a message is received that is not handled otherwise (e.g. a JSON message with ID zero). It @@ -140,8 +142,8 @@ When using an IPv6 address, enclose it within square brackets. E.g., endfunc let channel = ch_open("localhost:8765", {"callback": "Handle"}) < - When "mode" is "json" or "js" the "msg" argument is the body - of the received message, converted to Vim types. + When "mode" is "json" or "js" or "lsp" the "msg" argument is + the body of the received message, converted to Vim types. When "mode" is "nl" the "msg" argument is one message, excluding the NL. When "mode" is "raw" the "msg" argument is the whole message @@ -165,7 +167,19 @@ When using an IPv6 address, enclose it within square brackets. E.g., to check for messages, the close_cb may be invoked while still in the callback. The plugin must handle this somehow, it can be useful to know that no more data is coming. - *channel-drop* + If it is not known if there is a message to be read, use a + try/catch block: > + try + let msg = ch_readraw(a:channel) + catch + let msg = 'no message' + endtry + try + let err = ch_readraw(a:channel, #{part: 'err'}) + catch + let err = 'no error' + endtry +< *channel-drop* "drop" Specifies when to drop messages: "auto" When there is no callback to handle a message. The "close_cb" is also considered for this. @@ -443,7 +457,7 @@ to check if there is something to read. Note that when there is no callback, messages are dropped. To avoid that add a close callback to the channel. -To read all output from a RAW channel that is available: > +To read all normal output from a RAW channel that is available: > let output = ch_readraw(channel) To read the error output: > let output = ch_readraw(channel, {"part": "err"}) @@ -503,6 +517,7 @@ ch_evalexpr({handle}, {expr} [, {options}]) *ch_evalexpr()* according to the type of channel. The function cannot be used with a raw channel. See |channel-use|. {handle} can be a Channel or a Job that has a Channel. + When using the "lsp" channel mode, {expr} must be a |Dict|. *E917* {options} must be a Dictionary. It must not have a "callback" entry. It can have a "timeout" entry to specify the timeout @@ -578,7 +593,7 @@ ch_info({handle}) *ch_info()* "err_io" "out", "null", "pipe", "file" or "buffer" "err_timeout" timeout in msec "in_status" "open" or "closed" - "in_mode" "NL", "RAW", "JSON" or "JS" + "in_mode" "NL", "RAW", "JSON", "JS" or "LSP" "in_io" "null", "pipe", "file" or "buffer" "in_timeout" timeout in msec @@ -674,6 +689,7 @@ ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()* with a raw channel. See |channel-use|. *E912* {handle} can be a Channel or a Job that has a Channel. + When using the "lsp" channel mode, {expr} must be a |Dict|. Can also be used as a |method|: > GetChannel()->ch_sendexpr(expr) @@ -1361,5 +1377,76 @@ The same in |Vim9| script: > # start accepting shell commands startinsert +============================================================================== +14. Language Server Protocol *language-server-protocol* + +The language server protocol specification is available at: + + https://microsoft.github.io/language-server-protocol/specification + +Each LSP protocol message starts with a simple HTTP header followed by the +payload encoded in JSON-RPC format. This is described in: + + https://www.jsonrpc.org/specification + +For messages received on a channel with mode set to "lsp", Vim will process +the HTTP header and decode the payload into a Vim |Dict| type and call the +channel callback or the specified callback function. When sending messages on +a channel using |ch_evalexpr()| or |ch_sendexpr()|, Vim will add the HTTP +header and encode the Vim expression into JSON-RPC. + +To open a channel using the 'lsp' mode, set the 'mode' item in the |ch_open()| +{options} argument to 'lsp'. Example: > + + let ch = ch_open(..., #{mode: 'lsp'}) + +To open a channel using the 'lsp' mode with a job, set the 'in_mode' and +'out_mode' items in the |job_start()| {options} argument to 'lsp'. Example: > + + let job = job_start(...., #{in_mode: 'lsp', out_mode: 'lsp'}) + +To synchronously send a JSON-RPC request to the server, use the |ch_evalexpr()| +function. This function will return the response from the server. You can use +the 'timeout' field in the {options} argument to control the response wait +time. Example: > + + let req = {} + let req.method = 'textDocument/definition' + let req.params = {} + let req.params.textDocument = #{uri: 'a.c'} + let req.params.position = #{line: 10, character: 3} + let resp = ch_evalexpr(ch, req, #{timeout: 100}) + +Note that in the request message the 'id' field should not be specified. If it +is specified, then Vim will overwrite the value with an internally generated +identifier. Vim currently supports only a number type for the 'id' field. + +To send a JSON-RPC request to the server and asynchronously process the +response, use the |ch_sendexpr()| function and supply a callback function. +Example: > + + let req = {} + let req.method = 'textDocument/hover' + let req.params = {} + let req.params.textDocument = #{uri: 'a.c'} + let req.params.position = #{line: 10, character: 3} + let resp = ch_sendexpr(ch, req, #{callback: 'MyFn'}) + +To send a JSON-RPC notification message to the server, use the |ch_sendexpr()| +function. Example: > + + call ch_sendexpr(ch, #{method: 'initialized'}) + +To respond to a JSON-RPC request message from the server, use the +|ch_sendexpr()| function. In the response message, copy the 'id' field value +from the server request message. Example: > + + let resp = {} + let resp.id = req.id + let resp.result = 1 + call ch_sendexpr(ch, resp) + +The JSON-RPC notification messages from the server are delivered through the +|channel-callback| function. vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/src/channel.c b/src/channel.c index 68f1177f81..470092e153 100644 --- a/src/channel.c +++ b/src/channel.c @@ -2111,6 +2111,83 @@ channel_fill(js_read_T *reader) return TRUE; } +/* + * Process the HTTP header in a Language Server Protocol (LSP) message. + * + * The message format is described in the LSP specification: + * https://microsoft.github.io/language-server-protocol/specification + * + * It has the following two fields: + * + * Content-Length: ... + * Content-Type: application/vscode-jsonrpc; charset=utf-8 + * + * Each field ends with "\r\n". The header ends with an additional "\r\n". + * + * Returns OK if a valid header is received and FAIL if some fields in the + * header are not correct. Returns MAYBE if a partial header is received and + * need to wait for more data to arrive. + */ + static int +channel_process_lsp_http_hdr(js_read_T *reader) +{ + char_u *line_start; + char_u *p; + int_u hdr_len; + int payload_len = -1; + int_u jsbuf_len; + + // We find the end once, to avoid calling strlen() many times. + jsbuf_len = (int_u)STRLEN(reader->js_buf); + reader->js_end = reader->js_buf + jsbuf_len; + + p = reader->js_buf; + + // Process each line in the header till an empty line is read (header + // separator). + while (TRUE) + { + line_start = p; + while (*p != NUL && *p != '\n') + p++; + if (*p == NUL) // partial header + return MAYBE; + p++; + + // process the content length field (if present) + if ((p - line_start > 16) + && STRNICMP(line_start, "Content-Length: ", 16) == 0) + { + errno = 0; + payload_len = strtol((char *)line_start + 16, NULL, 10); + if (errno == ERANGE || payload_len < 0) + // invalid length, discard the payload + return FAIL; + } + + if ((p - line_start) == 2 && line_start[0] == '\r' && + line_start[1] == '\n') + // reached the empty line + break; + } + + if (payload_len == -1) + // Content-Length field is not present in the header + return FAIL; + + hdr_len = p - reader->js_buf; + + // if the entire payload is not received, wait for more data to arrive + if (jsbuf_len < hdr_len + payload_len) + return MAYBE; + + reader->js_used += hdr_len; + // recalculate the end based on the length read from the header. + reader->js_end = reader->js_buf + hdr_len + payload_len; + + return OK; +} + /* * Use the read buffer of "channel"/"part" and parse a JSON message that is * complete. The messages are added to the queue. @@ -2124,7 +2201,7 @@ channel_parse_json(channel_T *channel, ch_part_T part) jsonq_T *item; chanpart_T *chanpart = &channel->ch_part[part]; jsonq_T *head = &chanpart->ch_json_head; - int status; + int status = OK; int ret; if (channel_peek(channel, part) == NULL) @@ -2136,19 +2213,31 @@ channel_parse_json(channel_T *channel, ch_part_T part) reader.js_cookie = channel; reader.js_cookie_arg = part; + if (chanpart->ch_mode == MODE_LSP) + status = channel_process_lsp_http_hdr(&reader); + // When a message is incomplete we wait for a short while for more to // arrive. After the delay drop the input, otherwise a truncated string // or list will make us hang. // Do not generate error messages, they will be written in a channel log. - ++emsg_silent; - status = json_decode(&reader, &listtv, - chanpart->ch_mode == MODE_JS ? JSON_JS : 0); - --emsg_silent; + if (status == OK) + { + ++emsg_silent; + status = json_decode(&reader, &listtv, + chanpart->ch_mode == MODE_JS ? JSON_JS : 0); + --emsg_silent; + } if (status == OK) { // Only accept the response when it is a list with at least two // items. - if (listtv.v_type != VAR_LIST || listtv.vval.v_list->lv_len < 2) + if (chanpart->ch_mode == MODE_LSP && listtv.v_type != VAR_DICT) + { + ch_error(channel, "Did not receive a LSP dict, discarding"); + clear_tv(&listtv); + } + else if (chanpart->ch_mode != MODE_LSP && + (listtv.v_type != VAR_LIST || listtv.vval.v_list->lv_len < 2)) { if (listtv.v_type != VAR_LIST) ch_error(channel, "Did not receive a list, discarding"); @@ -2375,11 +2464,38 @@ channel_get_json( while (item != NULL) { - list_T *l = item->jq_value->vval.v_list; + list_T *l; typval_T *tv; - CHECK_LIST_MATERIALIZE(l); - tv = &l->lv_first->li_tv; + if (channel->ch_part[part].ch_mode != MODE_LSP) + { + l = item->jq_value->vval.v_list; + CHECK_LIST_MATERIALIZE(l); + tv = &l->lv_first->li_tv; + } + else + { + dict_T *d; + dictitem_T *di; + + // LSP message payload is a JSON-RPC dict. + // For RPC requests and responses, the 'id' item will be present. + // For notifications, it will not be present. + if (id > 0) + { + if (item->jq_value->v_type != VAR_DICT) + goto nextitem; + d = item->jq_value->vval.v_dict; + if (d == NULL) + goto nextitem; + di = dict_find(d, (char_u *)"id", -1); + if (di == NULL) + goto nextitem; + tv = &di->di_tv; + } + else + tv = item->jq_value; + } if ((without_callback || !item->jq_no_callback) && ((id > 0 && tv->v_type == VAR_NUMBER && tv->vval.v_number == id) @@ -2395,6 +2511,7 @@ channel_get_json( remove_json_node(head, item); return OK; } +nextitem: item = item->jq_next; } return FAIL; @@ -2762,6 +2879,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part) callback_T *callback = NULL; buf_T *buffer = NULL; char_u *p; + int called_otc; // one time callbackup if (channel->ch_nb_close_cb != NULL) // this channel is handled elsewhere (netbeans) @@ -2788,7 +2906,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part) buffer = NULL; } - if (ch_mode == MODE_JSON || ch_mode == MODE_JS) + if (ch_mode == MODE_JSON || ch_mode == MODE_JS || ch_mode == MODE_LSP) { listitem_T *item; int argc = 0; @@ -2802,29 +2920,47 @@ may_invoke_callback(channel_T *channel, ch_part_T part) return FALSE; } - for (item = listtv->vval.v_list->lv_first; - item != NULL && argc < CH_JSON_MAX_ARGS; - item = item->li_next) - argv[argc++] = item->li_tv; - while (argc < CH_JSON_MAX_ARGS) - argv[argc++].v_type = VAR_UNKNOWN; - - if (argv[0].v_type == VAR_STRING) + if (ch_mode == MODE_LSP) { - // ["cmd", arg] or ["cmd", arg, arg] or ["cmd", arg, arg, arg] - channel_exe_cmd(channel, part, argv); - free_tv(listtv); - return TRUE; - } + dict_T *d = listtv->vval.v_dict; + dictitem_T *di; - if (argv[0].v_type != VAR_NUMBER) - { - ch_error(channel, - "Dropping message with invalid sequence number type"); - free_tv(listtv); - return FALSE; + seq_nr = 0; + if (d != NULL) + { + di = dict_find(d, (char_u *)"id", -1); + if (di != NULL && di->di_tv.v_type == VAR_NUMBER) + seq_nr = di->di_tv.vval.v_number; + } + + argv[1] = *listtv; + } + else + { + for (item = listtv->vval.v_list->lv_first; + item != NULL && argc < CH_JSON_MAX_ARGS; + item = item->li_next) + argv[argc++] = item->li_tv; + while (argc < CH_JSON_MAX_ARGS) + argv[argc++].v_type = VAR_UNKNOWN; + + if (argv[0].v_type == VAR_STRING) + { + // ["cmd", arg] or ["cmd", arg, arg] or ["cmd", arg, arg, arg] + channel_exe_cmd(channel, part, argv); + free_tv(listtv); + return TRUE; + } + + if (argv[0].v_type != VAR_NUMBER) + { + ch_error(channel, + "Dropping message with invalid sequence number type"); + free_tv(listtv); + return FALSE; + } + seq_nr = argv[0].vval.v_number; } - seq_nr = argv[0].vval.v_number; } else if (channel_peek(channel, part) == NULL) { @@ -2906,24 +3042,35 @@ may_invoke_callback(channel_T *channel, ch_part_T part) argv[1].vval.v_string = msg; } + called_otc = FALSE; if (seq_nr > 0) { - int done = FALSE; - - // JSON or JS mode: invoke the one-time callback with the matching nr + // JSON or JS or LSP mode: invoke the one-time callback with the + // matching nr for (cbitem = cbhead->cq_next; cbitem != NULL; cbitem = cbitem->cq_next) if (cbitem->cq_seq_nr == seq_nr) { invoke_one_time_callback(channel, cbhead, cbitem, argv); - done = TRUE; + called_otc = TRUE; break; } - if (!done) + } + + if (seq_nr > 0 && (ch_mode != MODE_LSP || called_otc)) + { + if (!called_otc) { + // If the 'drop' channel attribute is set to 'never' or if + // ch_evalexpr() is waiting for this response message, then don't + // drop this message. if (channel->ch_drop_never) { // message must be read with ch_read() channel_push_json(channel, part, listtv); + + // Change the type to avoid the value being freed. + listtv->v_type = VAR_NUMBER; + free_tv(listtv); listtv = NULL; } else @@ -3006,7 +3153,7 @@ channel_has_readahead(channel_T *channel, ch_part_T part) { ch_mode_T ch_mode = channel->ch_part[part].ch_mode; - if (ch_mode == MODE_JSON || ch_mode == MODE_JS) + if (ch_mode == MODE_JSON || ch_mode == MODE_JS || ch_mode == MODE_LSP) { jsonq_T *head = &channel->ch_part[part].ch_json_head; @@ -3092,6 +3239,7 @@ channel_part_info(channel_T *channel, dict_T *dict, char *name, ch_part_T part) case MODE_RAW: s = "RAW"; break; case MODE_JSON: s = "JSON"; break; case MODE_JS: s = "JS"; break; + case MODE_LSP: s = "LSP"; break; } dict_add_string(dict, namebuf, (char_u *)s); @@ -4291,9 +4439,59 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) return; } - id = ++channel->ch_last_msg_id; - text = json_encode_nr_expr(id, &argvars[1], - (ch_mode == MODE_JS ? JSON_JS : 0) | JSON_NL); + if (ch_mode == MODE_LSP) + { + dict_T *d; + dictitem_T *di; + int callback_present = FALSE; + + if (argvars[1].v_type != VAR_DICT) + { + semsg(_(e_dict_required_for_argument_nr), 2); + return; + } + d = argvars[1].vval.v_dict; + di = dict_find(d, (char_u *)"id", -1); + if (di != NULL && di->di_tv.v_type != VAR_NUMBER) + { + // only number type is supported for the 'id' item + semsg(_(e_invalid_value_for_argument_str), "id"); + return; + } + + if (argvars[2].v_type == VAR_DICT) + if (dict_find(argvars[2].vval.v_dict, (char_u *)"callback", -1) + != NULL) + callback_present = TRUE; + + if (eval || callback_present) + { + // When evaluating an expression or sending an expression with a + // callback, always assign a generated ID + id = ++channel->ch_last_msg_id; + if (di == NULL) + dict_add_number(d, "id", id); + else + di->di_tv.vval.v_number = id; + } + else + { + // When sending an expression, if the message has an 'id' item, + // then use it. + id = 0; + if (di != NULL) + id = di->di_tv.vval.v_number; + } + if (dict_find(d, (char_u *)"jsonrpc", -1) == NULL) + dict_add_string(d, "jsonrpc", (char_u *)"2.0"); + text = json_encode_lsp_msg(&argvars[1]); + } + else + { + id = ++channel->ch_last_msg_id; + text = json_encode_nr_expr(id, &argvars[1], + (ch_mode == MODE_JS ? JSON_JS : 0) | JSON_NL); + } if (text == NULL) return; @@ -4309,13 +4507,23 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) if (channel_read_json_block(channel, part_read, timeout, id, &listtv) == OK) { - list_T *list = listtv->vval.v_list; + if (ch_mode == MODE_LSP) + { + *rettv = *listtv; + // Change the type to avoid the value being freed. + listtv->v_type = VAR_NUMBER; + free_tv(listtv); + } + else + { + list_T *list = listtv->vval.v_list; - // Move the item from the list and then change the type to - // avoid the value being freed. - *rettv = list->lv_u.mat.lv_last->li_tv; - list->lv_u.mat.lv_last->li_tv.v_type = VAR_NUMBER; - free_tv(listtv); + // Move the item from the list and then change the type to + // avoid the value being freed. + *rettv = list->lv_u.mat.lv_last->li_tv; + list->lv_u.mat.lv_last->li_tv.v_type = VAR_NUMBER; + free_tv(listtv); + } } } free_job_options(&opt); diff --git a/src/job.c b/src/job.c index 0ed33a78e5..e12f687f55 100644 --- a/src/job.c +++ b/src/job.c @@ -31,6 +31,8 @@ handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo) *modep = MODE_JS; else if (STRCMP(val, "json") == 0) *modep = MODE_JSON; + else if (STRCMP(val, "lsp") == 0) + *modep = MODE_LSP; else { semsg(_(e_invalid_argument_str), val); diff --git a/src/json.c b/src/json.c index 942d131e3a..b23bfa0895 100644 --- a/src/json.c +++ b/src/json.c @@ -86,6 +86,32 @@ json_encode_nr_expr(int nr, typval_T *val, int options) ga_append(&ga, NUL); return ga.ga_data; } + +/* + * Encode "val" into a JSON format string prefixed by the LSP HTTP header. + * Returns NULL when out of memory. + */ + char_u * +json_encode_lsp_msg(typval_T *val) +{ + garray_T ga; + garray_T lspga; + + ga_init2(&ga, 1, 4000); + if (json_encode_gap(&ga, val, 0) == FAIL) + return NULL; + ga_append(&ga, NUL); + + ga_init2(&lspga, 1, 4000); + vim_snprintf((char *)IObuff, IOSIZE, + "Content-Length: %u\r\n" + "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n\r\n", + ga.ga_len - 1); + ga_concat(&lspga, IObuff); + ga_concat_len(&lspga, ga.ga_data, ga.ga_len); + ga_clear(&ga); + return lspga.ga_data; +} #endif static void diff --git a/src/proto/json.pro b/src/proto/json.pro index 926c2bec40..f05c131452 100644 --- a/src/proto/json.pro +++ b/src/proto/json.pro @@ -1,6 +1,7 @@ /* json.c */ char_u *json_encode(typval_T *val, int options); char_u *json_encode_nr_expr(int nr, typval_T *val, int options); +char_u *json_encode_lsp_msg(typval_T *val); int json_decode(js_read_T *reader, typval_T *res, int options); int json_find_end(js_read_T *reader, int options); void f_js_decode(typval_T *argvars, typval_T *rettv); diff --git a/src/structs.h b/src/structs.h index 192693bac4..36eb054fae 100644 --- a/src/structs.h +++ b/src/structs.h @@ -2193,6 +2193,7 @@ typedef enum MODE_RAW, MODE_JSON, MODE_JS, + MODE_LSP // Language Server Protocol (http + json) } ch_mode_T; typedef enum { diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim index d9ab521f88..122bc570aa 100644 --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -2378,5 +2378,214 @@ func Test_job_start_with_invalid_argument() call assert_fails('call job_start([0zff])', 'E976:') endfunc +" Test for the 'lsp' channel mode +func LspCb(chan, msg) + call add(g:lspNotif, a:msg) +endfunc + +func LspOtCb(chan, msg) + call add(g:lspOtMsgs, a:msg) +endfunc + +func LspTests(port) + " call ch_logfile('Xlsprpc.log', 'w') + let ch = ch_open(s:localhost .. a:port, #{mode: 'lsp', callback: 'LspCb'}) + if ch_status(ch) == "fail" + call assert_report("Can't open the lsp channel") + return + endif + + " check for channel information + let info = ch_info(ch) + call assert_equal('LSP', info.sock_mode) + + " Evaluate an expression + let resp = ch_evalexpr(ch, #{method: 'simple-rpc', params: [10, 20]}) + call assert_false(empty(resp)) + call assert_equal(#{id: 1, jsonrpc: '2.0', result: 'simple-rpc'}, resp) + + " Evaluate an expression. While waiting for the response, a notification + " message is delivered. + let g:lspNotif = [] + let resp = ch_evalexpr(ch, #{method: 'rpc-with-notif', params: {'v': 10}}) + call assert_false(empty(resp)) + call assert_equal(#{id: 2, jsonrpc: '2.0', result: 'rpc-with-notif-resp'}, + \ resp) + call assert_equal([#{jsonrpc: '2.0', result: 'rpc-with-notif-notif'}], + \ g:lspNotif) + + " Wrong payload notification test + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'wrong-payload', params: {}}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', result: 'wrong-payload'}], g:lspNotif) + + " Test for receiving a response with incorrect 'id' and additional + " notification messages while evaluating an expression. + let g:lspNotif = [] + let resp = ch_evalexpr(ch, #{method: 'rpc-resp-incorrect-id', + \ params: {'a': [1, 2]}}) + call assert_false(empty(resp)) + call assert_equal(#{id: 4, jsonrpc: '2.0', + \ result: 'rpc-resp-incorrect-id-4'}, resp) + call assert_equal([#{jsonrpc: '2.0', result: 'rpc-resp-incorrect-id-1'}, + \ #{jsonrpc: '2.0', result: 'rpc-resp-incorrect-id-2'}, + \ #{jsonrpc: '2.0', id: 1, result: 'rpc-resp-incorrect-id-3'}], + \ g:lspNotif) + + " simple notification test + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'simple-notif', params: [#{a: 10, b: []}]}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', result: 'simple-notif'}], g:lspNotif) + + " multiple notifications test + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'multi-notif', params: [#{a: {}, b: {}}]}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', result: 'multi-notif1'}, + \ #{jsonrpc: '2.0', result: 'multi-notif2'}], g:lspNotif) + + " Test for sending a message with an identifier. + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'msg-with-id', id: 93, params: #{s: 'str'}}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', id: 93, result: 'msg-with-id'}], + \ g:lspNotif) + + " Test for setting the 'id' value in a request message + let resp = ch_evalexpr(ch, #{method: 'ping', id: 1, params: {}}) + call assert_equal(#{id: 8, jsonrpc: '2.0', result: 'alive'}, resp) + + " Test for using a one time callback function to process a response + let g:lspOtMsgs = [] + call ch_sendexpr(ch, #{method: 'msg-specifc-cb', params: {}}, + \ #{callback: 'LspOtCb'}) + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{id: 9, jsonrpc: '2.0', result: 'msg-specifc-cb'}], + \ g:lspOtMsgs) + + " Test for generating a request message from the other end (server) + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'server-req', params: #{}}) + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([{'id': 201, 'jsonrpc': '2.0', + \ 'result': {'method': 'checkhealth', 'params': {'a': 20}}}], + \ g:lspNotif) + + " Test for sending a message without an id + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'msg-without-id'}}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', result: + \ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'msg-without-id'}}}], + \ g:lspNotif) + + " Test for sending a notification message with an id + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'echo', id: 110, params: #{s: 'msg-with-id'}}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', result: + \ #{method: 'echo', jsonrpc: '2.0', id: 110, + \ params: #{s: 'msg-with-id'}}}], g:lspNotif) + + " Test for processing the extra fields in the HTTP header + let resp = ch_evalexpr(ch, #{method: 'extra-hdr-fields', params: {}}) + call assert_equal({'id': 14, 'jsonrpc': '2.0', 'result': 'extra-hdr-fields'}, + \ resp) + + " Test for processing a HTTP header without the Content-Length field + let resp = ch_evalexpr(ch, #{method: 'hdr-without-len', params: {}}, + \ #{timeout: 200}) + call assert_equal('', resp) + " send a ping to make sure communication still works + let resp = ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal({'id': 16, 'jsonrpc': '2.0', 'result': 'alive'}, resp) + + " Test for processing a HTTP header with wrong length + let resp = ch_evalexpr(ch, #{method: 'hdr-with-wrong-len', params: {}}, + \ #{timeout: 200}) + call assert_equal('', resp) + " send a ping to make sure communication still works + let resp = ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal({'id': 18, 'jsonrpc': '2.0', 'result': 'alive'}, resp) + + " Test for processing a HTTP header with negative length + let resp = ch_evalexpr(ch, #{method: 'hdr-with-negative-len', params: {}}, + \ #{timeout: 200}) + call assert_equal('', resp) + " send a ping to make sure communication still works + let resp = ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal({'id': 20, 'jsonrpc': '2.0', 'result': 'alive'}, resp) + + " Test for an empty header + let resp = ch_evalexpr(ch, #{method: 'empty-header', params: {}}, + \ #{timeout: 200}) + call assert_equal('', resp) + " send a ping to make sure communication still works + let resp = ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal({'id': 22, 'jsonrpc': '2.0', 'result': 'alive'}, resp) + + " Test for an empty payload + let resp = ch_evalexpr(ch, #{method: 'empty-payload', params: {}}, + \ #{timeout: 200}) + call assert_equal('', resp) + " send a ping to make sure communication still works + let resp = ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal({'id': 24, 'jsonrpc': '2.0', 'result': 'alive'}, resp) + + " Test for invoking an unsupported method + let resp = ch_evalexpr(ch, #{method: 'xyz', params: {}}, #{timeout: 200}) + call assert_equal('', resp) + + " Test for sending a message without a callback function. Notification + " message should be dropped but RPC response should not be dropped. + call ch_setoptions(ch, #{callback: ''}) + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'no-callback'}}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([], g:lspNotif) + " Restore the callback function + call ch_setoptions(ch, #{callback: 'LspCb'}) + let g:lspNotif = [] + call ch_sendexpr(ch, #{method: 'echo', params: #{s: 'no-callback'}}) + " Send a ping to wait for all the notification messages to arrive + call ch_evalexpr(ch, #{method: 'ping'}) + call assert_equal([#{jsonrpc: '2.0', result: + \ #{method: 'echo', jsonrpc: '2.0', params: #{s: 'no-callback'}}}], + \ g:lspNotif) + + " " Test for sending a raw message + " let g:lspNotif = [] + " let s = "Content-Length: 62\r\n" + " let s ..= "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n" + " let s ..= "\r\n" + " let s ..= '{"method":"echo","jsonrpc":"2.0","params":{"m":"raw-message"}}' + " call ch_sendraw(ch, s) + " call ch_evalexpr(ch, #{method: 'ping'}) + " call assert_equal([{'jsonrpc': '2.0', + " \ 'result': {'method': 'echo', 'jsonrpc': '2.0', + " \ 'params': {'m': 'raw-message'}}}], g:lspNotif) + + " Invalid arguments to ch_evalexpr() and ch_sendexpr() + call assert_fails('call ch_sendexpr(ch, #{method: "cookie", id: "cookie"})', + \ 'E475:') + call assert_fails('call ch_evalexpr(ch, #{method: "ping", id: [{}]})', 'E475:') + call assert_fails('call ch_evalexpr(ch, [1, 2, 3])', 'E1206:') + call assert_fails('call ch_sendexpr(ch, "abc")', 'E1206:') + call assert_fails('call ch_evalexpr(ch, #{method: "ping"}, #{callback: "LspOtCb"})', 'E917:') + " call ch_logfile('', 'w') +endfunc + +func Test_channel_lsp_mode() + call RunServer('test_channel_lsp.py', 'LspTests', []) +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_channel_lsp.py b/src/testdir/test_channel_lsp.py new file mode 100644 index 0000000000..530258d844 --- /dev/null +++ b/src/testdir/test_channel_lsp.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python +# +# Server that will accept connections from a Vim channel. +# Used by test_channel.vim to test LSP functionality. +# +# This requires Python 2.6 or later. + +from __future__ import print_function +import json +import socket +import sys +import time +import threading + +try: + # Python 3 + import socketserver +except ImportError: + # Python 2 + import SocketServer as socketserver + +class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): + + def setup(self): + self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + + def send_lsp_msg(self, msgid, resp_dict): + v = {'jsonrpc': '2.0', 'result': resp_dict} + if msgid != -1: + v['id'] = msgid + s = json.dumps(v) + resp = "Content-Length: " + str(len(s)) + "\r\n" + resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n" + resp += "\r\n" + resp += s + if self.debug: + with open("Xlspdebug.log", "a") as myfile: + myfile.write("\n=> send\n" + resp) + self.request.sendall(resp.encode('utf-8')) + + def send_wrong_payload(self): + v = 'wrong-payload' + s = json.dumps(v) + resp = "Content-Length: " + str(len(s)) + "\r\n" + resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n" + resp += "\r\n" + resp += s + self.request.sendall(resp.encode('utf-8')) + + def send_empty_header(self, msgid, resp_dict): + v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict} + s = json.dumps(v) + resp = "\r\n" + resp += s + self.request.sendall(resp.encode('utf-8')) + + def send_empty_payload(self): + resp = "Content-Length: 0\r\n" + resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n" + resp += "\r\n" + self.request.sendall(resp.encode('utf-8')) + + def send_extra_hdr_fields(self, msgid, resp_dict): + # test for sending extra fields in the http header + v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict} + s = json.dumps(v) + resp = "Host: abc.vim.org\r\n" + resp += "User-Agent: Python\r\n" + resp += "Accept-Language: en-US,en\r\n" + resp += "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n" + resp += "Content-Length: " + str(len(s)) + "\r\n" + resp += "\r\n" + resp += s + self.request.sendall(resp.encode('utf-8')) + + def send_hdr_without_len(self, msgid, resp_dict): + # test for sending the http header without length + v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict} + s = json.dumps(v) + resp = "Content-Type: application/vim-jsonrpc; charset=utf-8\r\n" + resp += "\r\n" + resp += s + self.request.sendall(resp.encode('utf-8')) + + def send_hdr_with_wrong_len(self, msgid, resp_dict): + # test for sending the http header with wrong length + v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict} + s = json.dumps(v) + resp = "Content-Length: 1000\r\n" + resp += "\r\n" + resp += s + self.request.sendall(resp.encode('utf-8')) + + def send_hdr_with_negative_len(self, msgid, resp_dict): + # test for sending the http header with negative length + v = {'jsonrpc': '2.0', 'id': msgid, 'result': resp_dict} + s = json.dumps(v) + resp = "Content-Length: -1\r\n" + resp += "\r\n" + resp += s + self.request.sendall(resp.encode('utf-8')) + + def do_ping(self, payload): + time.sleep(0.2) + self.send_lsp_msg(payload['id'], 'alive') + + def do_echo(self, payload): + self.send_lsp_msg(-1, payload) + + def do_simple_rpc(self, payload): + # test for a simple RPC request + self.send_lsp_msg(payload['id'], 'simple-rpc') + + def do_rpc_with_notif(self, payload): + # test for sending a notification before replying to a request message + self.send_lsp_msg(-1, 'rpc-with-notif-notif') + # sleep for some time to make sure the notification is delivered + time.sleep(0.2) + self.send_lsp_msg(payload['id'], 'rpc-with-notif-resp') + + def do_wrong_payload(self, payload): + # test for sending a non dict payload + self.send_wrong_payload() + time.sleep(0.2) + self.send_lsp_msg(-1, 'wrong-payload') + + def do_rpc_resp_incorrect_id(self, payload): + self.send_lsp_msg(-1, 'rpc-resp-incorrect-id-1') + self.send_lsp_msg(-1, 'rpc-resp-incorrect-id-2') + self.send_lsp_msg(1, 'rpc-resp-incorrect-id-3') + time.sleep(0.2) + self.send_lsp_msg(payload['id'], 'rpc-resp-incorrect-id-4') + + def do_simple_notif(self, payload): + # notification message test + self.send_lsp_msg(-1, 'simple-notif') + + def do_multi_notif(self, payload): + # send multiple notifications + self.send_lsp_msg(-1, 'multi-notif1') + self.send_lsp_msg(-1, 'multi-notif2') + + def do_msg_with_id(self, payload): + self.send_lsp_msg(payload['id'], 'msg-with-id') + + def do_msg_specific_cb(self, payload): + self.send_lsp_msg(payload['id'], 'msg-specifc-cb') + + def do_server_req(self, payload): + self.send_lsp_msg(201, {'method': 'checkhealth', 'params': {'a': 20}}) + + def do_extra_hdr_fields(self, payload): + self.send_extra_hdr_fields(payload['id'], 'extra-hdr-fields') + + def do_hdr_without_len(self, payload): + self.send_hdr_without_len(payload['id'], 'hdr-without-len') + + def do_hdr_with_wrong_len(self, payload): + self.send_hdr_with_wrong_len(payload['id'], 'hdr-with-wrong-len') + + def do_hdr_with_negative_len(self, payload): + self.send_hdr_with_negative_len(payload['id'], 'hdr-with-negative-len') + + def do_empty_header(self, payload): + self.send_empty_header(payload['id'], 'empty-header') + + def do_empty_payload(self, payload): + self.send_empty_payload() + + def process_msg(self, msg): + try: + decoded = json.loads(msg) + print("Decoded:") + print(str(decoded)) + if 'method' in decoded: + test_map = { + 'ping': self.do_ping, + 'echo': self.do_echo, + 'simple-rpc': self.do_simple_rpc, + 'rpc-with-notif': self.do_rpc_with_notif, + 'wrong-payload': self.do_wrong_payload, + 'rpc-resp-incorrect-id': self.do_rpc_resp_incorrect_id, + 'simple-notif': self.do_simple_notif, + 'multi-notif': self.do_multi_notif, + 'msg-with-id': self.do_msg_with_id, + 'msg-specifc-cb': self.do_msg_specific_cb, + 'server-req': self.do_server_req, + 'extra-hdr-fields': self.do_extra_hdr_fields, + 'hdr-without-len': self.do_hdr_without_len, + 'hdr-with-wrong-len': self.do_hdr_with_wrong_len, + 'hdr-with-negative-len': self.do_hdr_with_negative_len, + 'empty-header': self.do_empty_header, + 'empty-payload': self.do_empty_payload + } + if decoded['method'] in test_map: + test_map[decoded['method']](decoded) + else: + print("Error: Unsupported method: " + decoded['method']) + else: + print("Error: 'method' field is not found") + + except ValueError: + print("json decoding failed") + + def process_msgs(self, msgbuf): + while True: + sidx = msgbuf.find('Content-Length: ') + if sidx == -1: + return msgbuf + sidx += 16 + eidx = msgbuf.find('\r\n') + if eidx == -1: + return msgbuf + msglen = int(msgbuf[sidx:eidx]) + + hdrend = msgbuf.find('\r\n\r\n') + if hdrend == -1: + return msgbuf + + # Remove the header + msgbuf = msgbuf[hdrend + 4:] + payload = msgbuf[:msglen] + + self.process_msg(payload) + + # Remove the processed message + msgbuf = msgbuf[msglen:] + + def handle(self): + print("=== socket opened ===") + self.debug = False + msgbuf = '' + while True: + try: + received = self.request.recv(4096).decode('utf-8') + except socket.error: + print("=== socket error ===") + break + except IOError: + print("=== socket closed ===") + break + if received == '': + print("=== socket closed ===") + break + print("\nReceived:\n{0}".format(received)) + + # Write the received lines into the file for debugging + if self.debug: + with open("Xlspdebug.log", "a") as myfile: + myfile.write("\n<= recv\n" + received) + + # Can receive more than one line in a response or a partial line. + # Accumulate all the received characters and process one line at + # a time. + msgbuf += received + msgbuf = self.process_msgs(msgbuf) + +class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + pass + +def writePortInFile(port): + # Write the port number in Xportnr, so that the test knows it. + f = open("Xportnr", "w") + f.write("{0}".format(port)) + f.close() + +def main(host, port, server_class=ThreadedTCPServer): + # Wait half a second before opening the port to test waittime in ch_open(). + # We do want to get the port number, get that first. We cannot open the + # socket, guess a port is free. + if len(sys.argv) >= 2 and sys.argv[1] == 'delay': + port = 13684 + writePortInFile(port) + + print("Wait for it...") + time.sleep(0.5) + + server = server_class((host, port), ThreadedTCPRequestHandler) + ip, port = server.server_address[0:2] + + # Start a thread with the server. That thread will then start a new thread + # for each connection. + server_thread = threading.Thread(target=server.serve_forever) + server_thread.start() + + writePortInFile(port) + + print("Listening on port {0}".format(port)) + + # Main thread terminates, but the server continues running + # until server.shutdown() is called. + try: + while server_thread.is_alive(): + server_thread.join(1) + except (KeyboardInterrupt, SystemExit): + server.shutdown() + +if __name__ == "__main__": + main("localhost", 0) diff --git a/src/version.c b/src/version.c index b18de4c5e5..15cbdf86b1 100644 --- a/src/version.c +++ b/src/version.c @@ -750,6 +750,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 4648, /**/ 4647, /**/ From 46eea444d992c2ae985cabb775a5d283f8e16df3 Mon Sep 17 00:00:00 2001 From: Bram Moolenaar Date: Wed, 30 Mar 2022 10:51:39 +0100 Subject: [PATCH 10/20] Update runtime files --- runtime/autoload/clojurecomplete.vim | 8 +- runtime/autoload/python3complete.vim | 5 +- runtime/colors/lists/default.vim | 8 +- runtime/doc/autocmd.txt | 8 +- runtime/doc/builtin.txt | 19 ++-- runtime/doc/channel.txt | 2 +- runtime/doc/insert.txt | 4 +- runtime/doc/options.txt | 5 +- runtime/doc/repeat.txt | 16 ++-- runtime/doc/tags | 1 + runtime/doc/todo.txt | 22 ++--- runtime/doc/vim9.txt | 22 +++-- runtime/ftplugin/clojure.vim | 12 +-- runtime/ftplugin/ruby.vim | 42 +++++---- runtime/indent/clojure.vim | 2 +- runtime/indent/ruby.vim | 48 +++++++--- runtime/syntax/clojure.vim | 42 +++++---- runtime/syntax/debchangelog.vim | 6 +- runtime/syntax/debsources.vim | 6 +- runtime/syntax/eruby.vim | 12 ++- runtime/syntax/ruby.vim | 16 +++- runtime/syntax/tmux.vim | 127 +++++++++++++++++---------- 22 files changed, 259 insertions(+), 174 deletions(-) diff --git a/runtime/autoload/clojurecomplete.vim b/runtime/autoload/clojurecomplete.vim index 9f2c39081a..02262a6f91 100644 --- a/runtime/autoload/clojurecomplete.vim +++ b/runtime/autoload/clojurecomplete.vim @@ -4,12 +4,12 @@ " Former Maintainers: Sung Pae " URL: https://github.com/clojure-vim/clojure.vim " License: Vim (see :h license) -" Last Change: 2021-10-26 +" Last Change: 2022-03-24 " -*- COMPLETION WORDS -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj -" Clojure version 1.10.3 -let s:words = ["*","*'","*1","*2","*3","*agent*","*allow-unresolved-vars*","*assert*","*clojure-version*","*command-line-args*","*compile-files*","*compile-path*","*compiler-options*","*data-readers*","*default-data-reader-fn*","*e","*err*","*file*","*flush-on-newline*","*fn-loader*","*in*","*math-context*","*ns*","*out*","*print-dup*","*print-length*","*print-level*","*print-meta*","*print-namespace-maps*","*print-readably*","*read-eval*","*reader-resolver*","*source-path*","*suppress-read*","*unchecked-math*","*use-context-classloader*","*verbose-defrecords*","*warn-on-reflection*","+","+'","-","-'","->","->>","->ArrayChunk","->Eduction","->Vec","->VecNode","->VecSeq","-cache-protocol-fn","-reset-methods",".","..","/","<","<=","=","==",">",">=","EMPTY-NODE","Inst","PrintWriter-on","StackTraceElement->vec","Throwable->map","accessor","aclone","add-classpath","add-tap","add-watch","agent","agent-error","agent-errors","aget","alength","alias","all-ns","alter","alter-meta!","alter-var-root","amap","ancestors","and","any?","apply","areduce","array-map","as->","aset","aset-boolean","aset-byte","aset-char","aset-double","aset-float","aset-int","aset-long","aset-short","assert","assoc!","assoc","assoc-in","associative?","atom","await","await-for","await1","bases","bean","bigdec","bigint","biginteger","binding","bit-and","bit-and-not","bit-clear","bit-flip","bit-not","bit-or","bit-set","bit-shift-left","bit-shift-right","bit-test","bit-xor","boolean","boolean-array","boolean?","booleans","bound-fn","bound-fn*","bound?","bounded-count","butlast","byte","byte-array","bytes","bytes?","case","cast","cat","catch","char","char-array","char-escape-string","char-name-string","char?","chars","chunk","chunk-append","chunk-buffer","chunk-cons","chunk-first","chunk-next","chunk-rest","chunked-seq?","class","class?","clear-agent-errors","clojure-version","coll?","comment","commute","comp","comparator","compare","compare-and-set!","compile","complement","completing","concat","cond","cond->","cond->>","condp","conj!","conj","cons","constantly","construct-proxy","contains?","count","counted?","create-ns","create-struct","cycle","dec","dec'","decimal?","declare","dedupe","def","default-data-readers","definline","definterface","defmacro","defmethod","defmulti","defn","defn-","defonce","defprotocol","defrecord","defstruct","deftype","delay","delay?","deliver","denominator","deref","derive","descendants","destructure","disj!","disj","dissoc!","dissoc","distinct","distinct?","do","doall","dorun","doseq","dosync","dotimes","doto","double","double-array","double?","doubles","drop","drop-last","drop-while","eduction","empty","empty?","ensure","ensure-reduced","enumeration-seq","error-handler","error-mode","eval","even?","every-pred","every?","ex-cause","ex-data","ex-info","ex-message","extend","extend-protocol","extend-type","extenders","extends?","false?","ffirst","file-seq","filter","filterv","finally","find","find-keyword","find-ns","find-protocol-impl","find-protocol-method","find-var","first","flatten","float","float-array","float?","floats","flush","fn","fn","fn?","fnext","fnil","for","force","format","frequencies","future","future-call","future-cancel","future-cancelled?","future-done?","future?","gen-class","gen-interface","gensym","get","get-in","get-method","get-proxy-class","get-thread-bindings","get-validator","group-by","halt-when","hash","hash-combine","hash-map","hash-ordered-coll","hash-set","hash-unordered-coll","ident?","identical?","identity","if","if-let","if-not","if-some","ifn?","import","in-ns","inc","inc'","indexed?","init-proxy","inst-ms","inst-ms*","inst?","instance?","int","int-array","int?","integer?","interleave","intern","interpose","into","into-array","ints","io!","isa?","iterate","iterator-seq","juxt","keep","keep-indexed","key","keys","keyword","keyword?","last","lazy-cat","lazy-seq","let","let","letfn","line-seq","list","list*","list?","load","load-file","load-reader","load-string","loaded-libs","locking","long","long-array","longs","loop","loop","macroexpand","macroexpand-1","make-array","make-hierarchy","map","map-entry?","map-indexed","map?","mapcat","mapv","max","max-key","memfn","memoize","merge","merge-with","meta","method-sig","methods","min","min-key","mix-collection-hash","mod","monitor-enter","monitor-exit","munge","name","namespace","namespace-munge","nat-int?","neg-int?","neg?","new","newline","next","nfirst","nil?","nnext","not","not-any?","not-empty","not-every?","not=","ns","ns-aliases","ns-imports","ns-interns","ns-map","ns-name","ns-publics","ns-refers","ns-resolve","ns-unalias","ns-unmap","nth","nthnext","nthrest","num","number?","numerator","object-array","odd?","or","parents","partial","partition","partition-all","partition-by","pcalls","peek","persistent!","pmap","pop!","pop","pop-thread-bindings","pos-int?","pos?","pr","pr-str","prefer-method","prefers","primitives-classnames","print","print-ctor","print-dup","print-method","print-simple","print-str","printf","println","println-str","prn","prn-str","promise","proxy","proxy-call-with-super","proxy-mappings","proxy-name","proxy-super","push-thread-bindings","pvalues","qualified-ident?","qualified-keyword?","qualified-symbol?","quot","quote","rand","rand-int","rand-nth","random-sample","range","ratio?","rational?","rationalize","re-find","re-groups","re-matcher","re-matches","re-pattern","re-seq","read","read+string","read-line","read-string","reader-conditional","reader-conditional?","realized?","record?","recur","reduce","reduce-kv","reduced","reduced?","reductions","ref","ref-history-count","ref-max-history","ref-min-history","ref-set","refer","refer-clojure","reify","release-pending-sends","rem","remove","remove-all-methods","remove-method","remove-ns","remove-tap","remove-watch","repeat","repeatedly","replace","replicate","require","requiring-resolve","reset!","reset-meta!","reset-vals!","resolve","rest","restart-agent","resultset-seq","reverse","reversible?","rseq","rsubseq","run!","satisfies?","second","select-keys","send","send-off","send-via","seq","seq?","seqable?","seque","sequence","sequential?","set!","set","set-agent-send-executor!","set-agent-send-off-executor!","set-error-handler!","set-error-mode!","set-validator!","set?","short","short-array","shorts","shuffle","shutdown-agents","simple-ident?","simple-keyword?","simple-symbol?","slurp","some","some->","some->>","some-fn","some?","sort","sort-by","sorted-map","sorted-map-by","sorted-set","sorted-set-by","sorted?","special-symbol?","spit","split-at","split-with","str","string?","struct","struct-map","subs","subseq","subvec","supers","swap!","swap-vals!","symbol","symbol?","sync","tagged-literal","tagged-literal?","take","take-last","take-nth","take-while","tap>","test","the-ns","thread-bound?","throw","time","to-array","to-array-2d","trampoline","transduce","transient","tree-seq","true?","try","type","unchecked-add","unchecked-add-int","unchecked-byte","unchecked-char","unchecked-dec","unchecked-dec-int","unchecked-divide-int","unchecked-double","unchecked-float","unchecked-inc","unchecked-inc-int","unchecked-int","unchecked-long","unchecked-multiply","unchecked-multiply-int","unchecked-negate","unchecked-negate-int","unchecked-remainder-int","unchecked-short","unchecked-subtract","unchecked-subtract-int","underive","unquote","unquote-splicing","unreduced","unsigned-bit-shift-right","update","update-in","update-proxy","uri?","use","uuid?","val","vals","var","var-get","var-set","var?","vary-meta","vec","vector","vector-of","vector?","volatile!","volatile?","vreset!","vswap!","when","when-first","when-let","when-not","when-some","while","with-bindings","with-bindings*","with-in-str","with-loading-context","with-local-vars","with-meta","with-open","with-out-str","with-precision","with-redefs","with-redefs-fn","xml-seq","zero?","zipmap"] +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj +" Clojure version 1.11.0 +let s:words = ["&","*","*'","*1","*2","*3","*agent*","*allow-unresolved-vars*","*assert*","*clojure-version*","*command-line-args*","*compile-files*","*compile-path*","*compiler-options*","*data-readers*","*default-data-reader-fn*","*e","*err*","*file*","*flush-on-newline*","*fn-loader*","*in*","*math-context*","*ns*","*out*","*print-dup*","*print-length*","*print-level*","*print-meta*","*print-namespace-maps*","*print-readably*","*read-eval*","*reader-resolver*","*source-path*","*suppress-read*","*unchecked-math*","*use-context-classloader*","*verbose-defrecords*","*warn-on-reflection*","+","+'","-","-'","->","->>","->ArrayChunk","->Eduction","->Vec","->VecNode","->VecSeq","-cache-protocol-fn","-reset-methods",".","..","/","<","<=","=","==",">",">=","EMPTY-NODE","Inst","NaN?","PrintWriter-on","StackTraceElement->vec","Throwable->map","abs","accessor","aclone","add-classpath","add-tap","add-watch","agent","agent-error","agent-errors","aget","alength","alias","all-ns","alter","alter-meta!","alter-var-root","amap","ancestors","and","any?","apply","areduce","array-map","as->","aset","aset-boolean","aset-byte","aset-char","aset-double","aset-float","aset-int","aset-long","aset-short","assert","assoc","assoc!","assoc-in","associative?","atom","await","await-for","await1","bases","bean","bigdec","bigint","biginteger","binding","bit-and","bit-and-not","bit-clear","bit-flip","bit-not","bit-or","bit-set","bit-shift-left","bit-shift-right","bit-test","bit-xor","boolean","boolean-array","boolean?","booleans","bound-fn","bound-fn*","bound?","bounded-count","butlast","byte","byte-array","bytes","bytes?","case","case*","cast","cat","catch","char","char-array","char-escape-string","char-name-string","char?","chars","chunk","chunk-append","chunk-buffer","chunk-cons","chunk-first","chunk-next","chunk-rest","chunked-seq?","class","class?","clear-agent-errors","clojure-version","coll?","comment","commute","comp","comparator","compare","compare-and-set!","compile","complement","completing","concat","cond","cond->","cond->>","condp","conj","conj!","cons","constantly","construct-proxy","contains?","count","counted?","create-ns","create-struct","cycle","dec","dec'","decimal?","declare","dedupe","def","default-data-readers","definline","definterface","defmacro","defmethod","defmulti","defn","defn-","defonce","defprotocol","defrecord","defstruct","deftype","deftype*","delay","delay?","deliver","denominator","deref","derive","descendants","destructure","disj","disj!","dissoc","dissoc!","distinct","distinct?","do","doall","dorun","doseq","dosync","dotimes","doto","double","double-array","double?","doubles","drop","drop-last","drop-while","eduction","empty","empty?","ensure","ensure-reduced","enumeration-seq","error-handler","error-mode","eval","even?","every-pred","every?","ex-cause","ex-data","ex-info","ex-message","extend","extend-protocol","extend-type","extenders","extends?","false","false?","ffirst","file-seq","filter","filterv","finally","find","find-keyword","find-ns","find-protocol-impl","find-protocol-method","find-var","first","flatten","float","float-array","float?","floats","flush","fn","fn*","fn?","fnext","fnil","for","force","format","frequencies","future","future-call","future-cancel","future-cancelled?","future-done?","future?","gen-class","gen-interface","gensym","get","get-in","get-method","get-proxy-class","get-thread-bindings","get-validator","group-by","halt-when","hash","hash-combine","hash-map","hash-ordered-coll","hash-set","hash-unordered-coll","ident?","identical?","identity","if","if-let","if-not","if-some","ifn?","import","in-ns","inc","inc'","indexed?","infinite?","init-proxy","inst-ms","inst-ms*","inst?","instance?","int","int-array","int?","integer?","interleave","intern","interpose","into","into-array","ints","io!","isa?","iterate","iteration","iterator-seq","juxt","keep","keep-indexed","key","keys","keyword","keyword?","last","lazy-cat","lazy-seq","let","let*","letfn","letfn*","line-seq","list","list*","list?","load","load-file","load-reader","load-string","loaded-libs","locking","long","long-array","longs","loop","loop*","macroexpand","macroexpand-1","make-array","make-hierarchy","map","map-entry?","map-indexed","map?","mapcat","mapv","max","max-key","memfn","memoize","merge","merge-with","meta","method-sig","methods","min","min-key","mix-collection-hash","mod","monitor-enter","monitor-exit","munge","name","namespace","namespace-munge","nat-int?","neg-int?","neg?","new","newline","next","nfirst","nil","nil?","nnext","not","not-any?","not-empty","not-every?","not=","ns","ns-aliases","ns-imports","ns-interns","ns-map","ns-name","ns-publics","ns-refers","ns-resolve","ns-unalias","ns-unmap","nth","nthnext","nthrest","num","number?","numerator","object-array","odd?","or","parents","parse-boolean","parse-double","parse-long","parse-uuid","partial","partition","partition-all","partition-by","pcalls","peek","persistent!","pmap","pop","pop!","pop-thread-bindings","pos-int?","pos?","pr","pr-str","prefer-method","prefers","primitives-classnames","print","print-ctor","print-dup","print-method","print-simple","print-str","printf","println","println-str","prn","prn-str","promise","proxy","proxy-call-with-super","proxy-mappings","proxy-name","proxy-super","push-thread-bindings","pvalues","qualified-ident?","qualified-keyword?","qualified-symbol?","quot","quote","rand","rand-int","rand-nth","random-sample","random-uuid","range","ratio?","rational?","rationalize","re-find","re-groups","re-matcher","re-matches","re-pattern","re-seq","read","read+string","read-line","read-string","reader-conditional","reader-conditional?","realized?","record?","recur","reduce","reduce-kv","reduced","reduced?","reductions","ref","ref-history-count","ref-max-history","ref-min-history","ref-set","refer","refer-clojure","reify","reify*","release-pending-sends","rem","remove","remove-all-methods","remove-method","remove-ns","remove-tap","remove-watch","repeat","repeatedly","replace","replicate","require","requiring-resolve","reset!","reset-meta!","reset-vals!","resolve","rest","restart-agent","resultset-seq","reverse","reversible?","rseq","rsubseq","run!","satisfies?","second","select-keys","send","send-off","send-via","seq","seq-to-map-for-destructuring","seq?","seqable?","seque","sequence","sequential?","set","set!","set-agent-send-executor!","set-agent-send-off-executor!","set-error-handler!","set-error-mode!","set-validator!","set?","short","short-array","shorts","shuffle","shutdown-agents","simple-ident?","simple-keyword?","simple-symbol?","slurp","some","some->","some->>","some-fn","some?","sort","sort-by","sorted-map","sorted-map-by","sorted-set","sorted-set-by","sorted?","special-symbol?","spit","split-at","split-with","str","string?","struct","struct-map","subs","subseq","subvec","supers","swap!","swap-vals!","symbol","symbol?","sync","tagged-literal","tagged-literal?","take","take-last","take-nth","take-while","tap>","test","the-ns","thread-bound?","throw","time","to-array","to-array-2d","trampoline","transduce","transient","tree-seq","true","true?","try","type","unchecked-add","unchecked-add-int","unchecked-byte","unchecked-char","unchecked-dec","unchecked-dec-int","unchecked-divide-int","unchecked-double","unchecked-float","unchecked-inc","unchecked-inc-int","unchecked-int","unchecked-long","unchecked-multiply","unchecked-multiply-int","unchecked-negate","unchecked-negate-int","unchecked-remainder-int","unchecked-short","unchecked-subtract","unchecked-subtract-int","underive","unquote","unquote-splicing","unreduced","unsigned-bit-shift-right","update","update-in","update-keys","update-proxy","update-vals","uri?","use","uuid?","val","vals","var","var-get","var-set","var?","vary-meta","vec","vector","vector-of","vector?","volatile!","volatile?","vreset!","vswap!","when","when-first","when-let","when-not","when-some","while","with-bindings","with-bindings*","with-in-str","with-loading-context","with-local-vars","with-meta","with-open","with-out-str","with-precision","with-redefs","with-redefs-fn","xml-seq","zero?","zipmap"] " Simple word completion for special forms and public vars in clojure.core function! clojurecomplete#Complete(findstart, base) diff --git a/runtime/autoload/python3complete.vim b/runtime/autoload/python3complete.vim index 0635ef8249..ea0a331369 100644 --- a/runtime/autoload/python3complete.vim +++ b/runtime/autoload/python3complete.vim @@ -2,7 +2,7 @@ " Maintainer: " Previous Maintainer: Aaron Griffin " Version: 0.9 -" Last Updated: 2020 Oct 9 +" Last Updated: 2022 Mar 30 " " Roland Puntaier: this file contains adaptations for python3 and is parallel to pythoncomplete.vim " @@ -91,6 +91,9 @@ endfunction function! s:DefPython() py3 << PYTHONEOF +import warnings +warnings.simplefilter(action='ignore', category=FutureWarning) + import sys, tokenize, io, types from token import NAME, DEDENT, NEWLINE, STRING diff --git a/runtime/colors/lists/default.vim b/runtime/colors/lists/default.vim index 5940048129..f05a1ceeae 100644 --- a/runtime/colors/lists/default.vim +++ b/runtime/colors/lists/default.vim @@ -1,5 +1,5 @@ " Maintainer: Drew Vogel -" Last Change: 2021 Jul 25 +" Last Change: 2022 Mar 20 " " Replaced rgb.txt as the source of de facto standard color names. This is " sourced each time the colorscheme command is run. It is also sourced each @@ -430,6 +430,8 @@ call extend(v:colornames, { \ 'yellow2': '#eeee00', \ 'yellow3': '#cdcd00', \ 'yellow4': '#8b8b00', + \ 'dark yellow': '#8b8b00', + \ 'darkyellow': '#8b8b00', \ 'gold1': '#ffd700', \ 'gold2': '#eec900', \ 'gold3': '#cdad00', @@ -506,6 +508,8 @@ call extend(v:colornames, { \ 'orangered2': '#ee4000', \ 'orangered3': '#cd3700', \ 'orangered4': '#8b2500', + \ 'light red': '#ff8b8b', + \ 'lightred': '#ff8b8b', \ 'red1': '#ff0000', \ 'red2': '#ee0000', \ 'red3': '#cd0000', @@ -538,6 +542,8 @@ call extend(v:colornames, { \ 'violetred2': '#ee3a8c', \ 'violetred3': '#cd3278', \ 'violetred4': '#8b2252', + \ 'light magenta': '#ff8bff', + \ 'lightmagenta': '#ff8bff', \ 'magenta1': '#ff00ff', \ 'magenta2': '#ee00ee', \ 'magenta3': '#cd00cd', diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index b1120e9023..16bbbf5807 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -1,4 +1,4 @@ -*autocmd.txt* For Vim version 8.2. Last change: 2022 Mar 04 +*autocmd.txt* For Vim version 8.2. Last change: 2022 Mar 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -690,9 +690,9 @@ CursorHoldI Just like CursorHold, but in Insert mode. CursorMoved After the cursor was moved in Normal or Visual mode. Also when the text of the cursor line has been changed, e.g., with "x", "rx" or "p". - Not triggered when there is typeahead, while - executing commands in a script file, when - an operator is pending or when moving to + Not always triggered when there is typeahead, + while executing commands in a script file, + when an operator is pending or when moving to another window while remaining at the same cursor position. For an example see |match-parens|. diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 2d615c5ac2..ba741e23a4 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 8.2. Last change: 2022 Mar 08 +*builtin.txt* For Vim version 8.2. Last change: 2022 Mar 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -1562,14 +1562,15 @@ confirm({msg} [, {choices} [, {default} [, {type}]]]) or another valid interrupt key, confirm() returns 0. An example: > - :let choice = confirm("What do you want?", "&Apples\n&Oranges\n&Bananas", 2) - :if choice == 0 - : echo "make up your mind!" - :elseif choice == 3 - : echo "tasteful" - :else - : echo "I prefer bananas myself." - :endif + let choice = confirm("What do you want?", + \ "&Apples\n&Oranges\n&Bananas", 2) + if choice == 0 + echo "make up your mind!" + elseif choice == 3 + echo "tasteful" + else + echo "I prefer bananas myself." + endif < In a GUI dialog, buttons are used. The layout of the buttons depends on the 'v' flag in 'guioptions'. If it is included, the buttons are always put vertically. Otherwise, confirm() diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 6edb50bffd..f112119dc6 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -1,4 +1,4 @@ -*channel.txt* For Vim version 8.2. Last change: 2022 Feb 27 +*channel.txt* For Vim version 8.2. Last change: 2022 Mar 26 VIM REFERENCE MANUAL by Bram Moolenaar diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 5dc3348dbe..6ff7d453bf 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1,4 +1,4 @@ -*insert.txt* For Vim version 8.2. Last change: 2022 Mar 13 +*insert.txt* For Vim version 8.2. Last change: 2022 Mar 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -800,7 +800,7 @@ If the previous expansion was split, because it got longer than 'textwidth', then just the text in the current line will be used. If the match found is at the end of a line, then the first word in the next -line will be inserted and the message "word from next line" displayed, if +line will be inserted and the message "Word from other line" displayed, if this word is accepted the next CTRL-X CTRL-P or CTRL-X CTRL-N will search for those lines starting with this word. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 5bafaff3d4..bdc25223f4 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 8.2. Last change: 2022 Mar 13 +*options.txt* For Vim version 8.2. Last change: 2022 Mar 29 VIM REFERENCE MANUAL by Bram Moolenaar @@ -987,7 +987,8 @@ A jump table for the options with a short description can be found at |Q_op|. nostop like start, except CTRL-W and CTRL-U do not stop at the start of insert. - When the value is empty, Vi compatible backspacing is used. + When the value is empty, Vi compatible backspacing is used, none of + the ways mentioned for the items above are possible. For backwards compatibility with version 5.4 and earlier: value effect ~ diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 1ee547ce04..42852219cb 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -1,4 +1,4 @@ -*repeat.txt* For Vim version 8.2. Last change: 2022 Jan 21 +*repeat.txt* For Vim version 8.2. Last change: 2022 Mar 30 VIM REFERENCE MANUAL by Bram Moolenaar @@ -210,22 +210,26 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. To source a range of lines that doesn't start with the |:vim9script| command in Vim9 script context, the - |:vim9cmd| modifier can be used. + |:vim9cmd| modifier can be used. If you use a Visual + selection and type ":", the range in the form "'<,'>" + can come before it: > + :'<,'>vim9cmd source +< Otherwise the range goes after the modifier and must + have a colon prefixed, like all Vim9 ranges: > + :vim9cmd :5,9source - When a range of lines in a buffer is sourced in the +< When a range of lines in a buffer is sourced in the Vim9 script context, the previously defined script-local variables and functions are not cleared. This works like the range started with the ":vim9script noclear" command. The "++clear" argument can be used to clear the script-local variables and functions before sourcing the script. This works like - the range started with the |:vimscript| command + the range started with the `:vim9script` command without the "noclear" argument. See |vim9-reload| for more information. Examples: > - :4,5source - :vim9cmd :'<,'>source :10,18source ++clear *:source!* diff --git a/runtime/doc/tags b/runtime/doc/tags index e64bdd5769..e6add95f82 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -8000,6 +8000,7 @@ lace.vim syntax.txt /*lace.vim* lambda eval.txt /*lambda* lang-variable eval.txt /*lang-variable* language-mapping map.txt /*language-mapping* +language-server-protocol channel.txt /*language-server-protocol* last-pattern pattern.txt /*last-pattern* last-position-jump usr_05.txt /*last-position-jump* last_buffer_nr() builtin.txt /*last_buffer_nr()* diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt index fd0817def6..916b1db1ad 100644 --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -1,4 +1,4 @@ -*todo.txt* For Vim version 8.2. Last change: 2022 Mar 18 +*todo.txt* For Vim version 8.2. Last change: 2022 Mar 30 VIM REFERENCE MANUAL by Bram Moolenaar @@ -38,6 +38,9 @@ browser use: https://github.com/vim/vim/issues/1234 *known-bugs* -------------------- Known bugs and current work ----------------------- +Allow for: "import autoload '../lib/script.vim'" + avoids going through 'runtimepath' and avoids name collisions. + Really drop the Athena and NeXtaw GUI? Decide end of March. Once Vim9 is stable: @@ -48,6 +51,7 @@ Once Vim9 is stable: vim9instr.c vim9script.c vim9type.c +- Adjust intro message to say "help version9". Further Vim9 improvements, possibly after launch: - Check performance with callgrind and kcachegrind. @@ -417,13 +421,6 @@ register, then "" doesn't contain anything. Make it still follow "+. File marks merging has duplicates since 7.4.1925. (Ingo Karkat, #5733) -"make test_gui" crashed in submenu_change(). Fix and remove workaround in -add_pixmap_args(). -Athena is OK. -Motif: Build on Ubuntu can't enter any text in dialog text fields. -Running test_gui and test_gui_init with Motif sometimes kills the window -manager. Problem with Motif? - When editing a file with ":edit" the output of :swapname is relative, while editing it with "vim file" it is absolute. (#355) Which one should it be? @@ -478,11 +475,6 @@ Test loose_clipboard() by selecting text before suspending. Undo puts cursor in wrong line after "cG" undo. -Implement completion for "breakadd". Should expand the second argument, e.g. -"func", and then function names after ":breakadd func". Including -script-local functions. -Also for ":profile". - :unmap gives error but does remove the mapping. (Antony Scriven, 2019 Dec 19) @@ -2501,10 +2493,6 @@ Works OK when 'cmdheight' is 2. 8 Use a mechanism similar to omni completion to figure out the kind of tab for CTRL-] and jump to the appropriate matching tag (if there are several). - Alternative: be able to define a function that takes the tag name and uses - taglist() to find the right location. With indication of using CTRL-] so - that the context can be taken into account. (Robert Webb) -Patch by Christian Brabandt, 2013 May 31. The utf class table is missing some entries: 0x2212, minus sign diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt index f494880e28..7af10024af 100644 --- a/runtime/doc/vim9.txt +++ b/runtime/doc/vim9.txt @@ -1,4 +1,4 @@ -*vim9.txt* For Vim version 8.2. Last change: 2022 Mar 18 +*vim9.txt* For Vim version 8.2. Last change: 2022 Mar 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -184,8 +184,8 @@ For now you will need to pass the dictionary explicitly: > def DictFunc(d: dict, arg: string) echo d[arg] enddef - var d = {item: 'value', func: DictFunc} - d.func(d, 'item') + var ad = {item: 'value', func: DictFunc} + ad.func(d, 'item') You can call a legacy dict function though: > func Legacy() dict @@ -376,13 +376,23 @@ And with autocommands: > } Although using a :def function probably works better. + *E1022* *E1103* *E1130* *E1131* *E1133* *E1134* *E1235* Declaring a variable with a type but without an initializer will initialize to false (for bool), empty (for string, list, dict, etc.) or zero (for number, any, etc.). This matters especially when using the "any" type, the value will -default to the number zero. - *E1016* *E1052* *E1066* +default to the number zero. For example, when declaring a list, items can be +added: > + var myList: list + myList->add(7) + +Initializing a variable to a null value, e.g. `null_list`, differs from not +initializing the variable. This throws an error: > + var myList = null_list + myList->add(7) # E1130: Cannot add to null list + +< *E1016* *E1052* *E1066* In Vim9 script `:let` cannot be used. An existing variable is assigned to without any command. The same for global, window, tab, buffer and Vim variables, because they are not really declared. Those can also be deleted @@ -1243,7 +1253,7 @@ Closures defined in a loop will share the same context. For example: > A closure must be compiled in the context that it is defined in, so that variables in that context can be found. This mostly happens correctly, except when a function is marked for debugging with `breakadd` after it was compiled. -Make sure the define the breakpoint before compiling the outerh function. +Make sure to define the breakpoint before compiling the outer function. The "inloop" variable will exist only once, all closures put in the list refer to the same instance, which in the end will have the value 4. This is diff --git a/runtime/ftplugin/clojure.vim b/runtime/ftplugin/clojure.vim index 81d53b1227..c922d75699 100644 --- a/runtime/ftplugin/clojure.vim +++ b/runtime/ftplugin/clojure.vim @@ -5,7 +5,7 @@ " Meikel Brandmeyer " URL: https://github.com/clojure-vim/clojure.vim " License: Vim (see :h license) -" Last Change: 2021-10-26 +" Last Change: 2022-03-24 if exists("b:did_ftplugin") finish @@ -43,7 +43,7 @@ setlocal commentstring=;\ %s " specially and hence are not indented specially. " " -*- LISPWORDS -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj setlocal lispwords=as->,binding,bound-fn,case,catch,cond->,cond->>,condp,def,definline,definterface,defmacro,defmethod,defmulti,defn,defn-,defonce,defprotocol,defrecord,defstruct,deftest,deftest-,deftype,doseq,dotimes,doto,extend,extend-protocol,extend-type,fn,for,if,if-let,if-not,if-some,let,letfn,locking,loop,ns,proxy,reify,set-test,testing,when,when-first,when-let,when-not,when-some,while,with-bindings,with-in-str,with-local-vars,with-open,with-precision,with-redefs,with-redefs-fn,with-test " Provide insert mode completions for special forms and clojure.core. As @@ -66,10 +66,10 @@ endif " Filter files in the browse dialog if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") - let b:browsefilter = "Clojure Source Files (*.clj)\t*.clj\n" . - \ "ClojureScript Source Files (*.cljs)\t*.cljs\n" . - \ "Java Source Files (*.java)\t*.java\n" . - \ "All Files (*.*)\t*.*\n" + let b:browsefilter = "All Files\t*\n" . + \ "Clojure Files\t*.clj;*.cljc;*.cljs;*.cljx\n" . + \ "EDN Files\t*.edn\n" . + \ "Java Files\t*.java\n" let b:undo_ftplugin .= ' | unlet! b:browsefilter' endif diff --git a/runtime/ftplugin/ruby.vim b/runtime/ftplugin/ruby.vim index 4a476fd8cf..8c1f47731c 100644 --- a/runtime/ftplugin/ruby.vim +++ b/runtime/ftplugin/ruby.vim @@ -3,7 +3,7 @@ " Maintainer: Tim Pope " URL: https://github.com/vim-ruby/vim-ruby " Release Coordinator: Doug Kearns -" Last Change: 2020 Feb 13 +" Last Change: 2022 Mar 21 if (exists("b:did_ftplugin")) finish @@ -53,7 +53,7 @@ endif " TODO: "setlocal define=^\\s*def -setlocal comments=:# +setlocal comments=b:# setlocal commentstring=#\ %s if !exists('g:ruby_version_paths') @@ -87,8 +87,14 @@ endfunction function! s:build_path(path) abort let path = join(map(copy(a:path), 'v:val ==# "." ? "" : v:val'), ',') - if &g:path !~# '\v^%(\.,)=%(/%(usr|emx)/include,)=,$' - let path = substitute(&g:path,',,$',',','') . ',' . path + if &g:path =~# '\v^%(\.,)=%(/%(usr|emx)/include,)=,$' + let path = path . ',.,,' + elseif &g:path =~# ',\.,,$' + let path = &g:path[0:-4] . path . ',.,,' + elseif &g:path =~# ',,$' + let path = &g:path[0:-2] . path . ',,' + else + let path = substitute(&g:path, '[^,]\zs$', ',', '') . path endif return path endfunction @@ -164,6 +170,8 @@ let b:undo_ftplugin .= "| sil! cunmap | sil! cunmap