diff options
Diffstat (limited to '')
171 files changed, 7979 insertions, 2899 deletions
diff --git a/src/GvimExt/gvimext.cpp b/src/GvimExt/gvimext.cpp index e58b142..f98423c 100644 --- a/src/GvimExt/gvimext.cpp +++ b/src/GvimExt/gvimext.cpp @@ -55,9 +55,11 @@ getGvimName(char *name, int runtime) HKEY keyhandle; DWORD hlen; - // Get the location of gvim from the registry. + // Get the location of gvim from the registry. Try + // HKEY_CURRENT_USER first, then fall back to HKEY_LOCAL_MACHINE if + // not found. name[0] = 0; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0, + if (RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Vim\\Gvim", 0, KEY_READ, &keyhandle) == ERROR_SUCCESS) { hlen = BUFSIZE; @@ -69,6 +71,19 @@ getGvimName(char *name, int runtime) RegCloseKey(keyhandle); } + if ((name[0] == 0) && + (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0, + KEY_READ, &keyhandle) == ERROR_SUCCESS)) + { + hlen = BUFSIZE; + if (RegQueryValueEx(keyhandle, "path", 0, NULL, (BYTE *)name, &hlen) + != ERROR_SUCCESS) + name[0] = 0; + else + name[hlen] = 0; + RegCloseKey(keyhandle); + } + // Registry didn't work, use the search path. if (name[0] == 0) strcpy(name, searchpath((char *)"gvim.exe")); diff --git a/src/Make_ami.mak b/src/Make_ami.mak index 9e9ebe3..09cdd3c 100644 --- a/src/Make_ami.mak +++ b/src/Make_ami.mak @@ -114,6 +114,7 @@ SRC += \ float.c \ fold.c \ getchar.c \ + gc.c \ hardcopy.c \ hashtab.c \ help.c \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak index 7afb6e0..20ed903 100644 --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -797,6 +797,7 @@ OBJ = \ $(OUTDIR)/float.o \ $(OUTDIR)/fold.o \ $(OUTDIR)/getchar.o \ + $(OUTDIR)/gc.o \ $(OUTDIR)/gui_xim.o \ $(OUTDIR)/hardcopy.o \ $(OUTDIR)/hashtab.o \ diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index 4d03a72..e58814b 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -718,6 +718,7 @@ OBJ = \ $(OUTDIR)\float.obj \ $(OUTDIR)\fold.obj \ $(OUTDIR)\getchar.obj \ + $(OUTDIR)\gc.obj \ $(OUTDIR)\gui_xim.obj \ $(OUTDIR)\hardcopy.obj \ $(OUTDIR)\hashtab.obj \ @@ -1575,6 +1576,8 @@ $(OUTDIR)/fold.obj: $(OUTDIR) fold.c $(INCL) $(OUTDIR)/getchar.obj: $(OUTDIR) getchar.c $(INCL) +$(OUTDIR)/gc.obj: $(OUTDIR) gc.c $(INCL) + $(OUTDIR)/gui_xim.obj: $(OUTDIR) gui_xim.c $(INCL) $(OUTDIR)/hardcopy.obj: $(OUTDIR) hardcopy.c $(INCL) version.h @@ -1915,6 +1918,7 @@ proto.h: \ proto/findfile.pro \ proto/float.pro \ proto/getchar.pro \ + proto/gc.pro \ proto/gui_xim.pro \ proto/hardcopy.pro \ proto/hashtab.pro \ diff --git a/src/Make_vms.mms b/src/Make_vms.mms index f050c9d..559c2f8 100644 --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -369,6 +369,7 @@ SRC = \ float.c \ fold.c \ getchar.c \ + gc.c \ gui_xim.c \ hardcopy.c \ hashtab.c \ @@ -500,6 +501,7 @@ OBJ = \ float.obj \ fold.obj \ getchar.obj \ + gc.obj \ gui_xim.obj \ hardcopy.obj \ hashtab.obj \ @@ -942,6 +944,10 @@ getchar.obj : getchar.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ errors.h globals.h +gc.obj : gc.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + errors.h globals.h gui_xim.obj : gui_xim.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ diff --git a/src/Makefile b/src/Makefile index 29f63dc..71578eb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1520,6 +1520,7 @@ BASIC_SRC = \ float.c \ fold.c \ getchar.c \ + gc.c \ gui_xim.c \ hardcopy.c \ hashtab.c \ @@ -1682,6 +1683,7 @@ OBJ_COMMON = \ objects/float.o \ objects/fold.o \ objects/getchar.o \ + objects/gc.o \ objects/gui_xim.o \ objects/hardcopy.o \ objects/hashtab.o \ @@ -1860,6 +1862,7 @@ PRO_AUTO = \ float.pro \ fold.pro \ getchar.pro \ + gc.pro \ gui_xim.pro \ gui_beval.pro \ hardcopy.pro \ @@ -2340,7 +2343,7 @@ installrtbase: $(HELPSOURCE)/vim.1 $(DEST_VIM) $(VIMTARGET) $(DEST_RT) \ $(DEST_HELP) $(DEST_PRINT) $(DEST_COL) \ $(DEST_SYN) $(DEST_SYN)/modula2 $(DEST_SYN)/modula2/opt $(DEST_SYN)/shared \ $(DEST_IND) $(DEST_FTP) \ - $(DEST_AUTO) $(DEST_AUTO)/dist $(DEST_AUTO)/xml $(DEST_AUTO)/zig \ + $(DEST_AUTO) $(DEST_AUTO)/dist $(DEST_AUTO)/xml \ $(DEST_AUTO)/rust $(DEST_AUTO)/cargo \ $(DEST_IMPORT) $(DEST_IMPORT)/dist \ $(DEST_PLUG) $(DEST_TUTOR) $(DEST_SPELL) $(DEST_COMP) @@ -2427,8 +2430,6 @@ installrtbase: $(HELPSOURCE)/vim.1 $(DEST_VIM) $(VIMTARGET) $(DEST_RT) \ cd $(DEST_AUTO)/dist; chmod $(HELPMOD) *.vim cd $(AUTOSOURCE)/xml; $(INSTALL_DATA) *.vim $(DEST_AUTO)/xml cd $(DEST_AUTO)/xml; chmod $(HELPMOD) *.vim - cd $(AUTOSOURCE)/zig; $(INSTALL_DATA) *.vim $(DEST_AUTO)/zig - cd $(DEST_AUTO)/zig; chmod $(HELPMOD) *.vim cd $(AUTOSOURCE)/cargo; $(INSTALL_DATA) *.vim $(DEST_AUTO)/cargo cd $(DEST_AUTO)/cargo; chmod $(HELPMOD) *.vim cd $(AUTOSOURCE)/rust; $(INSTALL_DATA) *.vim $(DEST_AUTO)/rust @@ -2673,7 +2674,7 @@ $(DESTDIR)$(exec_prefix) $(DEST_BIN) \ $(DEST_IND) $(DEST_FTP) \ $(DEST_LANG) $(DEST_KMAP) $(DEST_COMP) $(DEST_MACRO) \ $(DEST_PACK) $(DEST_TOOLS) $(DEST_TUTOR) $(DEST_SPELL) \ - $(DEST_AUTO) $(DEST_AUTO)/dist $(DEST_AUTO)/xml $(DEST_AUTO)/zig \ + $(DEST_AUTO) $(DEST_AUTO)/dist $(DEST_AUTO)/xml \ $(DEST_AUTO)/cargo $(DEST_AUTO)/rust \ $(DEST_IMPORT) $(DEST_IMPORT)/dist $(DEST_PLUG): $(MKDIR_P) $@ @@ -2865,10 +2866,10 @@ uninstall_runtime: -rmdir $(DEST_SYN) $(DEST_IND) -rm -rf $(DEST_FTP)/*.vim $(DEST_FTP)/README.txt $(DEST_FTP)/logtalk.dict -rm -f $(DEST_AUTO)/*.vim $(DEST_AUTO)/README.txt - -rm -f $(DEST_AUTO)/dist/*.vim $(DEST_AUTO)/xml/*.vim $(DEST_AUTO)/zig/*.vim $(DEST_AUTO)/cargo/*.vim $(DEST_AUTO)/rust/*.vim + -rm -f $(DEST_AUTO)/dist/*.vim $(DEST_AUTO)/xml/*.vim $(DEST_AUTO)/cargo/*.vim $(DEST_AUTO)/rust/*.vim -rm -f $(DEST_IMPORT)/dist/*.vim -rm -f $(DEST_PLUG)/*.vim $(DEST_PLUG)/README.txt - -rmdir $(DEST_FTP) $(DEST_AUTO)/dist $(DEST_AUTO)/xml $(DEST_AUTO)/zig $(DEST_AUTO)/cargo $(DEST_AUTO)/rust $(DEST_AUTO) + -rmdir $(DEST_FTP) $(DEST_AUTO)/dist $(DEST_AUTO)/xml $(DEST_AUTO)/cargo $(DEST_AUTO)/rust $(DEST_AUTO) -rmdir $(DEST_IMPORT)/dist $(DEST_IMPORT) -rmdir $(DEST_PLUG) $(DEST_RT) # This will fail when other Vim versions are installed, no worries. @@ -3224,6 +3225,9 @@ objects/fold.o: fold.c objects/getchar.o: getchar.c $(CCC) -o $@ getchar.c +objects/gc.o: gc.c + $(CCC) -o $@ gc.c + objects/hardcopy.o: hardcopy.c $(CCC) -o $@ hardcopy.c @@ -3875,6 +3879,11 @@ objects/getchar.o: getchar.c vim.h protodef.h auto/config.h feature.h os_unix.h proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \ libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \ globals.h errors.h +objects/gc.o: gc.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \ + libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \ + globals.h errors.h objects/gui_xim.o: gui_xim.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \ diff --git a/src/README.md b/src/README.md index f3806d2..4042988 100644 --- a/src/README.md +++ b/src/README.md @@ -49,6 +49,7 @@ filepath.c | dealing with file names and paths findfile.c | search for files in 'path' fold.c | folding getchar.c | getting characters and key mapping +gc.c | garbage collection help.c | vim help related functions highlight.c | syntax highlighting indent.c | text indentation diff --git a/src/auto/configure b/src/auto/configure index 0e0cf8e..98b9580 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -6502,11 +6502,13 @@ printf "%s\n" "$vi_cv_perl_xsubpp" >&6; } -e 's/-flto\(=auto\)\? //' \ -e 's/-W[^ ]*//g' \ -e 's/-D_FORTIFY_SOURCE=.//g'` - perllibs=`cd $srcdir; $vi_cv_path_perl -MExtUtils::Embed -e 'ldopts' | \ + perllibs=`cd $srcdir; $vi_cv_path_perl -MExtUtils::Embed -e 'ldopts' | \ sed -e '/Warning/d' -e '/Note (probably harmless)/d' \ + -e 's/-specs=[^ ]*//g' \ -e 's/-bE:perl.exp//' -e 's/-lc //'` - perlldflags=`cd $srcdir; $vi_cv_path_perl -MExtUtils::Embed \ - -e 'ccdlflags' | sed -e 's/-bE:perl.exp//'` + perlldflags=`cd $srcdir; $vi_cv_path_perl -MExtUtils::Embed \ + -e 'ccdlflags' | sed -e 's/-bE:perl.exp//' \ + -e 's/-specs=[^ ]*//g' ` { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compile and link flags for Perl are sane" >&5 printf %s "checking if compile and link flags for Perl are sane... " >&6; } @@ -11405,55 +11407,6 @@ printf "%s\n" "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext -ac_fn_c_check_header_compile "$LINENO" "elf.h" "ac_cv_header_elf_h" "$ac_includes_default" -if test "x$ac_cv_header_elf_h" = xyes -then : - HAS_ELF=1 -fi - -if test "$HAS_ELF" = 1; then - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for main in -lelf" >&5 -printf %s "checking for main in -lelf... " >&6; } -if test ${ac_cv_lib_elf_main+y} -then : - printf %s "(cached) " >&6 -else $as_nop - ac_check_lib_save_LIBS=$LIBS -LIBS="-lelf $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - - -int -main (void) -{ -return main (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO" -then : - ac_cv_lib_elf_main=yes -else $as_nop - ac_cv_lib_elf_main=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_elf_main" >&5 -printf "%s\n" "$ac_cv_lib_elf_main" >&6; } -if test "x$ac_cv_lib_elf_main" = xyes -then : - printf "%s\n" "#define HAVE_LIBELF 1" >>confdefs.h - - LIBS="-lelf $LIBS" - -fi - -fi - ac_header_dirent=no for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h; do as_ac_Header=`printf "%s\n" "ac_cv_header_dirent_$ac_hdr" | $as_tr_sh` diff --git a/src/autocmd.c b/src/autocmd.c index bce57cb..8380f8a 100644 --- a/src/autocmd.c +++ b/src/autocmd.c @@ -3391,6 +3391,9 @@ f_autocmd_get(typval_T *argvars, typval_T *rettv) { char_u *group_name; + if (ap->pat == NULL) // pattern has been removed + continue; + if (group != AUGROUP_ALL && group != ap->group) continue; diff --git a/src/buffer.c b/src/buffer.c index 58e9718..cbec9b9 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -2429,6 +2429,7 @@ free_buf_options( clear_string_option(&buf->b_p_lop); clear_string_option(&buf->b_p_cinsd); clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cot); clear_string_option(&buf->b_p_cpt); #ifdef FEAT_COMPL_FUNC clear_string_option(&buf->b_p_cfu); diff --git a/src/bufwrite.c b/src/bufwrite.c index bf79ad5..c9d9875 100644 --- a/src/bufwrite.c +++ b/src/bufwrite.c @@ -690,7 +690,7 @@ buf_write( int write_undo_file = FALSE; context_sha256_T sha_ctx; #endif - unsigned int bkc = get_bkc_value(buf); + unsigned int bkc = get_bkc_flags(buf); pos_T orig_start = buf->b_op_start; pos_T orig_end = buf->b_op_end; diff --git a/src/charset.c b/src/charset.c index 470698f..9aa402a 100644 --- a/src/charset.c +++ b/src/charset.c @@ -765,10 +765,22 @@ linetabsize_str(char_u *s) linetabsize_col(int startcol, char_u *s) { chartabsize_T cts; + vimlong_T vcol; init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); + vcol = cts.cts_vcol; + while (*cts.cts_ptr != NUL) - cts.cts_vcol += lbr_chartabsize_adv(&cts); + { + vcol += lbr_chartabsize_adv(&cts); + if (vcol > MAXCOL) + { + cts.cts_vcol = MAXCOL; + break; + } + else + cts.cts_vcol = (int)vcol; + } clear_chartabsize_arg(&cts); return (int)cts.cts_vcol; } @@ -840,22 +852,33 @@ linetabsize_no_outer(win_T *wp, linenr_T lnum) void win_linetabsize_cts(chartabsize_T *cts, colnr_T len) { + vimlong_T vcol = cts->cts_vcol; #ifdef FEAT_PROP_POPUP cts->cts_with_trailing = len == MAXCOL; #endif for ( ; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len); MB_PTR_ADV(cts->cts_ptr)) - cts->cts_vcol += win_lbr_chartabsize(cts, NULL); + { + vcol += win_lbr_chartabsize(cts, NULL); + if (vcol > MAXCOL) + { + cts->cts_vcol = MAXCOL; + break; + } + else + cts->cts_vcol = (int)vcol; + } #ifdef FEAT_PROP_POPUP // check for a virtual text at the end of a line or on an empty line if (len == MAXCOL && cts->cts_has_prop_with_text && *cts->cts_ptr == NUL) { (void)win_lbr_chartabsize(cts, NULL); - cts->cts_vcol += cts->cts_cur_text_width; + vcol += cts->cts_cur_text_width; // when properties are above or below the empty line must also be // counted if (cts->cts_ptr == cts->cts_line && cts->cts_prop_lines > 0) - ++cts->cts_vcol; + ++vcol; + cts->cts_vcol = vcol > MAXCOL ? MAXCOL : (int)vcol; } #endif } diff --git a/src/cmdexpand.c b/src/cmdexpand.c index 1008bf9..8d8bf06 100644 --- a/src/cmdexpand.c +++ b/src/cmdexpand.c @@ -438,6 +438,28 @@ cmdline_compl_startcol(void) } /* + * Returns the current cmdline completion pattern. + */ + char_u * +cmdline_compl_pattern(void) +{ + expand_T *xp = get_cmdline_info()->xpc; + + return xp == NULL ? NULL : xp->xp_orig; +} + +/* + * Returns TRUE if fuzzy cmdline completion is active, FALSE otherwise. + */ + int +cmdline_compl_is_fuzzy(void) +{ + expand_T *xp = get_cmdline_info()->xpc; + + return xp != NULL && cmdline_fuzzy_completion_supported(xp); +} + +/* * Return the number of characters that should be skipped in a status match. * These are backslashes used for escaping. Do show backslashes in help tags. */ diff --git a/src/cmdhist.c b/src/cmdhist.c index 6342f02..684c08e 100644 --- a/src/cmdhist.c +++ b/src/cmdhist.c @@ -297,11 +297,11 @@ static int last_maptick = -1; // last seen maptick add_to_history( int histype, char_u *new_entry, + size_t new_entrylen, int in_map, // consider maptick when inside a mapping int sep) // separator character used (search hist) { histentry_T *hisptr; - int len; if (hislen == 0) // no history return; @@ -336,10 +336,9 @@ add_to_history( vim_free(hisptr->hisstr); // Store the separator after the NUL of the string. - len = (int)STRLEN(new_entry); - hisptr->hisstr = vim_strnsave(new_entry, len + 2); + hisptr->hisstr = vim_strnsave(new_entry, new_entrylen + 2); if (hisptr->hisstr != NULL) - hisptr->hisstr[len + 1] = sep; + hisptr->hisstr[new_entrylen + 1] = sep; hisptr->hisnum = ++hisnum[histype]; hisptr->viminfo = FALSE; @@ -566,7 +565,7 @@ f_histadd(typval_T *argvars UNUSED, typval_T *rettv) return; init_history(); - add_to_history(histype, str, FALSE, NUL); + add_to_history(histype, str, STRLEN(str), FALSE, NUL); rettv->vval.v_number = TRUE; } @@ -768,7 +767,8 @@ ex_history(exarg_T *eap) if (i == hislen) i = 0; if (hist[i].hisstr != NULL - && hist[i].hisnum >= j && hist[i].hisnum <= k) + && hist[i].hisnum >= j && hist[i].hisnum <= k + && !message_filtered(hist[i].hisstr)) { msg_putchar('\n'); sprintf((char *)IObuff, "%c%6d ", i == idx ? '>' : ' ', diff --git a/src/configure.ac b/src/configure.ac index f6e54b3..946fe52 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -1173,13 +1173,17 @@ if test "$enable_perlinterp" = "yes" -o "$enable_perlinterp" = "dynamic"; then -e 's/-W[[^ ]]*//g' \ -e 's/-D_FORTIFY_SOURCE=.//g'` dnl Remove "-lc", it breaks on FreeBSD when using "-pthread". + dnl Remove -specs=<file-path>, the hardened flags cause relocation errors perllibs=`cd $srcdir; $vi_cv_path_perl -MExtUtils::Embed -e 'ldopts' | \ sed -e '/Warning/d' -e '/Note (probably harmless)/d' \ + -e 's/-specs=[[^ ]*]//g' \ -e 's/-bE:perl.exp//' -e 's/-lc //'` dnl Don't add perl lib to $LIBS: if it's not in LD_LIBRARY_PATH dnl a test in configure may fail because of that. + dnl Remove -specs=<file-path>, the hardened flags cause relocation errors perlldflags=`cd $srcdir; $vi_cv_path_perl -MExtUtils::Embed \ - -e 'ccdlflags' | sed -e 's/-bE:perl.exp//'` + -e 'ccdlflags' | sed -e 's/-bE:perl.exp//' \ + -e 's/-specs=[[^ ]*]//g' ` dnl check that compiling a simple program still works with the flags dnl added for Perl. @@ -3303,13 +3307,6 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [int x __attribute__((u AC_MSG_RESULT(yes); AC_DEFINE(HAVE_ATTRIBUTE_UNUSED), AC_MSG_RESULT(no)) -dnl Checks for header files. -AC_CHECK_HEADER(elf.h, HAS_ELF=1) -dnl AC_CHECK_HEADER(dwarf.h, SVR4=1) -if test "$HAS_ELF" = 1; then - AC_CHECK_LIB(elf, main) -fi - AC_HEADER_DIRENT dnl If sys/wait.h is not found it might still exist but not be POSIX @@ -1092,6 +1092,33 @@ failret: } /* + * Evaluate a literal dictionary: #{key: val, key: val} + * "*arg" points to the "#". + * On return, "*arg" points to the character after the Dict. + * Return OK or FAIL. Returns NOTDONE for {expr}. + */ + int +eval_lit_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg) +{ + int vim9script = in_vim9script(); + int ret = OK; + + if (vim9script) + { + ret = vim9_bad_comment(*arg) ? FAIL : NOTDONE; + } + else if ((*arg)[1] == '{') + { + ++*arg; + ret = eval_dict(arg, rettv, evalarg, TRUE); + } + else + ret = NOTDONE; + + return ret; +} + +/* * Go over all entries in "d2" and add them to "d1". * When "action" is "error" then a duplicate key is an error. * When "action" is "force" then a duplicate key is overwritten. diff --git a/src/drawline.c b/src/drawline.c index 81577be..e388bdb 100644 --- a/src/drawline.c +++ b/src/drawline.c @@ -296,10 +296,9 @@ get_sign_display_info( if (nrcol) { wlv->c_extra = NUL; - sprintf((char *)wlv->extra, "%-*c ", - number_width(wp), SIGN_BYTE); + wlv->n_extra = vim_snprintf((char *)wlv->extra, sizeof(wlv->extra), + "%-*c ", number_width(wp), SIGN_BYTE); wlv->p_extra = wlv->extra; - wlv->n_extra = (int)STRLEN(wlv->p_extra); } else wlv->c_extra = SIGN_BYTE; @@ -310,10 +309,9 @@ get_sign_display_info( if (nrcol) { wlv->c_extra = NUL; - sprintf((char *)wlv->extra, "%-*c ", number_width(wp), - MULTISIGN_BYTE); + wlv->n_extra = vim_snprintf((char *)wlv->extra, sizeof(wlv->extra), + "%-*c ", number_width(wp), MULTISIGN_BYTE); wlv->p_extra = wlv->extra; - wlv->n_extra = (int)STRLEN(wlv->p_extra); } else wlv->c_extra = MULTISIGN_BYTE; @@ -332,17 +330,18 @@ get_sign_display_info( if (nrcol) { int width = number_width(wp) - 2; - int n; - for (n = 0; n < width; n++) - wlv->extra[n] = ' '; - vim_snprintf((char *)wlv->extra + n, - sizeof(wlv->extra) - n, "%s ", wlv->p_extra); + vim_memset(wlv->extra, ' ', width); + wlv->n_extra = width; + wlv->n_extra += vim_snprintf((char *)wlv->extra + width, + sizeof(wlv->extra) - width, "%s ", wlv->p_extra); wlv->p_extra = wlv->extra; } + else + wlv->n_extra = (int)STRLEN(wlv->p_extra); + wlv->c_extra = NUL; wlv->c_final = NUL; - wlv->n_extra = (int)STRLEN(wlv->p_extra); } if (use_cursor_line_highlight(wp, wlv->lnum) @@ -417,7 +416,7 @@ handle_lnum_col( } } - sprintf((char *)wlv->extra, fmt, number_width(wp), num); + vim_snprintf((char *)wlv->extra, sizeof(wlv->extra), fmt, number_width(wp), num); if (wp->w_skipcol > 0 && wlv->startrow == 0) for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' '; ++wlv->p_extra) @@ -621,25 +620,26 @@ textprop_size_after_trunc( { int space = (flags & (TP_FLAG_ALIGN_BELOW | TP_FLAG_ALIGN_ABOVE)) ? wp->w_width - win_col_off(wp) : added; - int len = (int)STRLEN(text); int strsize = 0; - int n_used; + char_u *p; - // if the remaining size is to small and 'wrap' is set we wrap anyway and + // if the remaining size is too small and 'wrap' is set we wrap anyway and // use the next line if (space < PROP_TEXT_MIN_CELLS && wp->w_p_wrap) space += wp->w_width; if (flags & (TP_FLAG_ALIGN_BELOW | TP_FLAG_ALIGN_ABOVE)) space -= padding; - for (n_used = 0; n_used < len; n_used += (*mb_ptr2len)(text + n_used)) + + for (p = text; *p != NUL; p += (*mb_ptr2len)(p)) { - int clen = ptr2cells(text + n_used); + int clen = ptr2cells(p); if (strsize + clen > space) break; strsize += clen; } - *n_used_ptr = n_used; + *n_used_ptr = (int)(p - text); + return strsize; } @@ -1801,13 +1801,13 @@ win_line( pos = wp->w_cursor; wp->w_cursor.lnum = lnum; wp->w_cursor.col = linecol; - len = spell_move_to(wp, FORWARD, TRUE, TRUE, &spell_hlf); + len = spell_move_to(wp, FORWARD, SMT_ALL, TRUE, &spell_hlf); // spell_move_to() may call ml_get() and make "line" invalid line = ml_get_buf(wp->w_buffer, lnum, FALSE); ptr = line + linecol; - if (len == 0 || (int)wp->w_cursor.col > ptr - line) + if (len == 0 || (int)wp->w_cursor.col > linecol) { // no bad word found at line start, don't check until end of a // word @@ -2821,15 +2821,16 @@ win_line( // head byte at end of line mb_l = 1; transchar_nonprint(wp->w_buffer, wlv.extra, c); + wlv.n_extra = (int)STRLEN(wlv.extra) - 1; } else { // illegal tail byte mb_l = 2; STRCPY(wlv.extra, "XX"); + wlv.n_extra = 1; } wlv.p_extra = wlv.extra; - wlv.n_extra = (int)STRLEN(wlv.extra) - 1; wlv.c_extra = NUL; wlv.c_final = NUL; c = *wlv.p_extra++; @@ -3388,11 +3389,16 @@ win_line( c = *wlv.p_extra; p = alloc(wlv.n_extra + 1); - vim_memset(p, ' ', wlv.n_extra); - STRNCPY(p, wlv.p_extra + 1, STRLEN(wlv.p_extra) - 1); - p[wlv.n_extra] = NUL; - vim_free(wlv.p_extra_free); - wlv.p_extra_free = wlv.p_extra = p; + if (p == NULL) + wlv.n_extra = 0; + else + { + vim_memset(p, ' ', wlv.n_extra); + STRNCPY(p, wlv.p_extra + 1, STRLEN(wlv.p_extra) - 1); + p[wlv.n_extra] = NUL; + vim_free(wlv.p_extra_free); + wlv.p_extra_free = wlv.p_extra = p; + } } else #endif @@ -4251,7 +4257,7 @@ win_line( if (!wp->w_p_wrap && text_prop_follows && !text_prop_above) { // do not output more of the line, only the "below" prop - ptr += STRLEN(ptr); + ptr = line + (size_t)ml_get_buf_len(wp->w_buffer, lnum); # ifdef FEAT_LINEBREAK wlv.dont_use_showbreak = TRUE; # endif diff --git a/src/drawscreen.c b/src/drawscreen.c index f8818ff..9096c28 100644 --- a/src/drawscreen.c +++ b/src/drawscreen.c @@ -1781,7 +1781,7 @@ win_update(win_T *wp) if (j < wp->w_height - 2) // not too far off { i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1, - TRUE); + wp->w_height); #ifdef FEAT_DIFF // insert extra lines for previously invisible filler lines if (wp->w_lines[0].wl_lnum != wp->w_topline) @@ -519,9 +519,10 @@ edit( if ( #ifdef FEAT_VARTABS - curwin->w_wcol < mincol - tabstop_at( - get_nolist_virtcol(), curbuf->b_p_ts, - curbuf->b_p_vts_array) + curwin->w_wcol < mincol - tabstop_at(get_nolist_virtcol(), + curbuf->b_p_ts, + curbuf->b_p_vts_array, + FALSE) #else (int)curwin->w_wcol < mincol - curbuf->b_p_ts #endif @@ -1310,7 +1311,7 @@ docomplete: c = ins_ctrl_ey(c); break; - default: + default: #ifdef UNIX if (c == intr_char) // special interrupt char goto do_intr; @@ -1842,7 +1843,7 @@ backspace_until_column(int col) * Only matters when there are composing characters. * Return TRUE when something was deleted. */ - static int + static int del_char_after_col(int limit_col UNUSED) { if (enc_utf8 && limit_col >= 0) @@ -22,13 +22,6 @@ #define NAMESPACE_CHAR (char_u *)"abglstvw" -/* - * When recursively copying lists and dicts we need to remember which ones we - * have done to avoid endless recursiveness. This unique ID is used for that. - * The last bit is used for previous_funccal, ignored when comparing. - */ -static int current_copyID = 0; - static int eval2(char_u **arg, typval_T *rettv, evalarg_T *evalarg); static int eval3(char_u **arg, typval_T *rettv, evalarg_T *evalarg); static int eval4(char_u **arg, typval_T *rettv, evalarg_T *evalarg); @@ -39,7 +32,6 @@ static int eval8(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int want_str static int eval9(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int want_string); static int eval9_leader(typval_T *rettv, int numeric_only, char_u *start_leader, char_u **end_leaderp); -static int free_unref_items(int copyID); static char_u *make_expanded_name(char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end); /* @@ -250,93 +242,146 @@ eval_expr_get_funccal(typval_T *expr, typval_T *rettv) } /* - * Evaluate an expression, which can be a function, partial or string. + * Evaluate a partial. * Pass arguments "argv[argc]". - * If "want_func" is TRUE treat a string as a function name, not an expression. * "fc_arg" is from eval_expr_get_funccal() or NULL; * Return the result in "rettv" and OK or FAIL. */ - int -eval_expr_typval( - typval_T *expr, - int want_func, - typval_T *argv, - int argc, - funccall_T *fc_arg, - typval_T *rettv) + static int +eval_expr_partial( + typval_T *expr, + typval_T *argv, + int argc, + funccall_T *fc_arg, + typval_T *rettv) { - char_u *s; - char_u buf[NUMBUFLEN]; - funcexe_T funcexe; + partial_T *partial = expr->vval.v_partial; - if (expr->v_type == VAR_PARTIAL) + if (partial == NULL) + return FAIL; + + if (partial->pt_func != NULL + && partial->pt_func->uf_def_status != UF_NOT_COMPILED) { - partial_T *partial = expr->vval.v_partial; + funccall_T *fc = fc_arg != NULL ? fc_arg + : create_funccal(partial->pt_func, rettv); + int r; - if (partial == NULL) + if (fc == NULL) return FAIL; - if (partial->pt_func != NULL - && partial->pt_func->uf_def_status != UF_NOT_COMPILED) - { - funccall_T *fc = fc_arg != NULL ? fc_arg - : create_funccal(partial->pt_func, rettv); - int r; - - if (fc == NULL) - return FAIL; - - // Shortcut to call a compiled function with minimal overhead. - r = call_def_function(partial->pt_func, argc, argv, - DEF_USE_PT_ARGV, partial, NULL, fc, rettv); - if (fc_arg == NULL) - remove_funccal(); - if (r == FAIL) - return FAIL; - } - else - { - s = partial_name(partial); - if (s == NULL || *s == NUL) - return FAIL; - CLEAR_FIELD(funcexe); - funcexe.fe_evaluate = TRUE; - funcexe.fe_partial = partial; - if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) - return FAIL; - } - } - else if (expr->v_type == VAR_INSTR) - { - return exe_typval_instr(expr, rettv); + // Shortcut to call a compiled function with minimal overhead. + r = call_def_function(partial->pt_func, argc, argv, DEF_USE_PT_ARGV, + partial, NULL, fc, rettv); + if (fc_arg == NULL) + remove_funccal(); + if (r == FAIL) + return FAIL; } - else if (expr->v_type == VAR_FUNC || want_func) + else { - s = expr->v_type == VAR_FUNC - ? expr->vval.v_string - : tv_get_string_buf_chk_strict(expr, buf, in_vim9script()); + char_u *s = partial_name(partial); + funcexe_T funcexe; + if (s == NULL || *s == NUL) return FAIL; + CLEAR_FIELD(funcexe); funcexe.fe_evaluate = TRUE; + funcexe.fe_partial = partial; if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) return FAIL; } + + return OK; +} + +/* + * Evaluate an expression which is a function. + * Pass arguments "argv[argc]". + * Return the result in "rettv" and OK or FAIL. + */ + static int +eval_expr_func( + typval_T *expr, + typval_T *argv, + int argc, + typval_T *rettv) +{ + funcexe_T funcexe; + char_u buf[NUMBUFLEN]; + char_u *s; + + if (expr->v_type == VAR_FUNC) + s = expr->vval.v_string; else - { s = tv_get_string_buf_chk_strict(expr, buf, in_vim9script()); - if (s == NULL) - return FAIL; - s = skipwhite(s); - if (eval1_emsg(&s, rettv, NULL) == FAIL) - return FAIL; - if (*skipwhite(s) != NUL) // check for trailing chars after expr - { - clear_tv(rettv); - semsg(_(e_invalid_expression_str), s); - return FAIL; - } + if (s == NULL || *s == NUL) + return FAIL; + + CLEAR_FIELD(funcexe); + funcexe.fe_evaluate = TRUE; + if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) + return FAIL; + + return OK; +} + +/* + * Evaluate an expression, which is a string. + * Return the result in "rettv" and OK or FAIL. + */ + static int +eval_expr_string( + typval_T *expr, + typval_T *rettv) +{ + char_u *s; + char_u buf[NUMBUFLEN]; + + s = tv_get_string_buf_chk_strict(expr, buf, in_vim9script()); + if (s == NULL) + return FAIL; + + s = skipwhite(s); + if (eval1_emsg(&s, rettv, NULL) == FAIL) + return FAIL; + + if (*skipwhite(s) != NUL) // check for trailing chars after expr + { + clear_tv(rettv); + semsg(_(e_invalid_expression_str), s); + return FAIL; } + + return OK; +} + +/* + * Evaluate an expression, which can be a function, partial or string. + * Pass arguments "argv[argc]". + * If "want_func" is TRUE treat a string as a function name, not an expression. + * "fc_arg" is from eval_expr_get_funccal() or NULL; + * Return the result in "rettv" and OK or FAIL. + */ + int +eval_expr_typval( + typval_T *expr, + int want_func, + typval_T *argv, + int argc, + funccall_T *fc_arg, + typval_T *rettv) +{ + if (expr->v_type == VAR_PARTIAL) + return eval_expr_partial(expr, argv, argc, fc_arg, rettv); + else if (expr->v_type == VAR_INSTR) + return exe_typval_instr(expr, rettv); + else if (expr->v_type == VAR_FUNC || want_func) + return eval_expr_func(expr, argv, argc, rettv); + else + return eval_expr_string(expr, rettv); + return OK; } @@ -1117,41 +1162,40 @@ get_lval_check_access( ch_log(NULL, "LKVAR: get_lval_check_access(), cl_exec %p, cl %p, %c", (void*)cl_exec, (void*)cl, *p); #endif - if (cl_exec == NULL || cl_exec != cl) + if (cl_exec != NULL && cl_exec == cl) + return OK; + + char *msg = NULL; + switch (om->ocm_access) { - char *msg = NULL; - switch (om->ocm_access) - { - case VIM_ACCESS_PRIVATE: - msg = e_cannot_access_protected_variable_str; + case VIM_ACCESS_PRIVATE: + msg = e_cannot_access_protected_variable_str; + break; + case VIM_ACCESS_READ: + // If [idx] or .key following, read only OK. + if (*p == '[' || *p == '.') break; - case VIM_ACCESS_READ: - // If [idx] or .key following, read only OK. - if (*p == '[' || *p == '.') - break; - if ((flags & GLV_READ_ONLY) == 0) + if ((flags & GLV_READ_ONLY) == 0) + { + if (IS_ENUM(cl)) { - if (IS_ENUM(cl)) - { - if (om->ocm_type->tt_type == VAR_OBJECT) - semsg(_(e_enumvalue_str_cannot_be_modified), - cl->class_name, om->ocm_name); - else - msg = e_variable_is_not_writable_str; - } + if (om->ocm_type->tt_type == VAR_OBJECT) + semsg(_(e_enumvalue_str_cannot_be_modified), + cl->class_name, om->ocm_name); else msg = e_variable_is_not_writable_str; } - break; - case VIM_ACCESS_ALL: - break; - } - if (msg != NULL) - { - emsg_var_cl_define(msg, om->ocm_name, 0, cl); - return FAIL; - } - + else + msg = e_variable_is_not_writable_str; + } + break; + case VIM_ACCESS_ALL: + break; + } + if (msg != NULL) + { + emsg_var_cl_define(msg, om->ocm_name, 0, cl); + return FAIL; } return OK; } @@ -1170,12 +1214,10 @@ get_lval_check_access( static char_u * get_lval_imported( lval_T *lp, - typval_T *rettv, scid_T imp_sid, char_u *p, dictitem_T **dip, - int fne_flags, - int vim9script) + int fne_flags) { ufunc_T *ufunc; type_T *type = NULL; @@ -1197,16 +1239,6 @@ get_lval_imported( TRUE) == -1) goto failed; - if (vim9script && type != NULL) - { - where_T where = WHERE_INIT; - - // In a vim9 script, do type check and make sure the variable is - // writable. - if (check_typval_type(type, rettv, where) == FAIL) - goto failed; - } - // Get the typval for the exported item hashtab_T *ht = &SCRIPT_VARS(imp_sid); if (ht == NULL) @@ -1232,6 +1264,7 @@ get_lval_imported( goto failed; lp->ll_tv = &di->di_tv; + lp->ll_valtype = type; success: rc = OK; @@ -1241,6 +1274,674 @@ failed: return rc == OK ? p : NULL; } +typedef enum { + GLV_FAIL, + GLV_OK, + GLV_STOP +} glv_status_T; + +/* + * Get an Dict lval variable that can be assigned a value to: "name", + * "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc. + * "name" points to the start of the name. + * If "rettv" is not NULL it points to the value to be assigned. + * "unlet" is TRUE for ":unlet": slightly different behavior when something is + * wrong; must end in space or cmd separator. + * + * flags: + * GLV_QUIET: do not give error messages + * GLV_READ_ONLY: will not change the variable + * GLV_NO_AUTOLOAD: do not use script autoloading + * + * The Dict is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on + * failure. Returns GLV_STOP to stop processing the characters following + * 'key_end'. + */ + static int +get_lval_dict_item( + lval_T *lp, + char_u *name, + char_u *key, + int len, + char_u **key_end, + typval_T *var1, + int flags, + int unlet, + typval_T *rettv) +{ + int quiet = flags & GLV_QUIET; + char_u *p = *key_end; + + if (len == -1) + { + // "[key]": get key from "var1" + key = tv_get_string_chk(var1); // is number or string + if (key == NULL) + return GLV_FAIL; + } + lp->ll_list = NULL; + lp->ll_object = NULL; + lp->ll_class = NULL; + + // a NULL dict is equivalent with an empty dict + if (lp->ll_tv->vval.v_dict == NULL) + { + lp->ll_tv->vval.v_dict = dict_alloc(); + if (lp->ll_tv->vval.v_dict == NULL) + return GLV_FAIL; + ++lp->ll_tv->vval.v_dict->dv_refcount; + } + lp->ll_dict = lp->ll_tv->vval.v_dict; + + lp->ll_di = dict_find(lp->ll_dict, key, len); + + // When assigning to a scope dictionary check that a function and + // variable name is valid (only variable name unless it is l: or + // g: dictionary). Disallow overwriting a builtin function. + if (rettv != NULL && lp->ll_dict->dv_scope != 0) + { + int prevval; + + if (len != -1) + { + prevval = key[len]; + key[len] = NUL; + } + else + prevval = 0; // avoid compiler warning + int wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE + && (rettv->v_type == VAR_FUNC + || rettv->v_type == VAR_PARTIAL) + && var_wrong_func_name(key, lp->ll_di == NULL)) + || !valid_varname(key, -1, TRUE); + if (len != -1) + key[len] = prevval; + if (wrong) + return GLV_FAIL; + } + + if (lp->ll_valtype != NULL) + // use the type of the member + lp->ll_valtype = lp->ll_valtype->tt_member; + + if (lp->ll_di == NULL) + { + // Can't add "v:" or "a:" variable. + if (lp->ll_dict == get_vimvar_dict() + || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) + { + semsg(_(e_illegal_variable_name_str), name); + return GLV_FAIL; + } + + // Key does not exist in dict: may need to add it. + if (*p == '[' || *p == '.' || unlet) + { + if (!quiet) + semsg(_(e_key_not_present_in_dictionary_str), key); + return GLV_FAIL; + } + if (len == -1) + lp->ll_newkey = vim_strsave(key); + else + lp->ll_newkey = vim_strnsave(key, len); + if (lp->ll_newkey == NULL) + p = NULL; + + *key_end = p; + return GLV_STOP; + } + // existing variable, need to check if it can be changed + else if ((flags & GLV_READ_ONLY) == 0 + && (var_check_ro(lp->ll_di->di_flags, name, FALSE) + || var_check_lock(lp->ll_di->di_flags, name, FALSE))) + return GLV_FAIL; + + lp->ll_tv = &lp->ll_di->di_tv; + + return GLV_OK; +} + +/* + * Get an blob lval variable that can be assigned a value to: "name", + * "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc. + * + * 'var1' specifies the starting blob index and 'var2' specifies the ending + * blob index. If the first index is not specified in a range, then 'empty1' + * is TRUE. If 'quiet' is TRUE, then error messages are not displayed for + * invalid indexes. + * + * The blob is returned in 'lp'. Returns OK on success and FAIL on failure. + */ + static int +get_lval_blob( + lval_T *lp, + typval_T *var1, + typval_T *var2, + int empty1, + int quiet) +{ + long bloblen = blob_len(lp->ll_tv->vval.v_blob); + + // Get the number and item for the only or first index of the List. + if (empty1) + lp->ll_n1 = 0; + else + // is number or string + lp->ll_n1 = (long)tv_get_number(var1); + + if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL) + return FAIL; + if (lp->ll_range && !lp->ll_empty2) + { + lp->ll_n2 = (long)tv_get_number(var2); + if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) + return FAIL; + } + + if (!lp->ll_range) + // Indexing a single byte in a blob. So the rhs type is a + // number. + lp->ll_valtype = &t_number; + + lp->ll_blob = lp->ll_tv->vval.v_blob; + lp->ll_tv = NULL; + + return OK; +} + +/* + * Get a List lval variable that can be assigned a value to: "name", + * "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc. + * + * 'var1' specifies the starting List index and 'var2' specifies the ending + * List index. If the first index is not specified in a range, then 'empty1' + * is TRUE. If 'quiet' is TRUE, then error messages are not displayed for + * invalid indexes. + * + * The List is returned in 'lp'. Returns OK on success and FAIL on failure. + */ + static int +get_lval_list( + lval_T *lp, + typval_T *var1, + typval_T *var2, + int empty1, + int flags, + int quiet) +{ + /* + * Get the number and item for the only or first index of the List. + */ + if (empty1) + lp->ll_n1 = 0; + else + // is number or string + lp->ll_n1 = (long)tv_get_number(var1); + + lp->ll_dict = NULL; + lp->ll_object = NULL; + lp->ll_class = NULL; + lp->ll_list = lp->ll_tv->vval.v_list; + lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, + (flags & GLV_ASSIGN_WITH_OP) == 0, quiet); + if (lp->ll_li == NULL) + return FAIL; + + if (lp->ll_valtype != NULL && !lp->ll_range) + // use the type of the member + lp->ll_valtype = lp->ll_valtype->tt_member; + + /* + * May need to find the item or absolute index for the second + * index of a range. + * When no index given: "lp->ll_empty2" is TRUE. + * Otherwise "lp->ll_n2" is set to the second index. + */ + if (lp->ll_range && !lp->ll_empty2) + { + lp->ll_n2 = (long)tv_get_number(var2); + // is number or string + if (check_range_index_two(lp->ll_list, + &lp->ll_n1, lp->ll_li, &lp->ll_n2, quiet) == FAIL) + return FAIL; + } + + lp->ll_tv = &lp->ll_li->li_tv; + + return OK; +} + +/* + * Get a class or object lval method in class "cl". The 'key' argument points + * to the method name and 'key_end' points to the character after 'key'. + * 'v_type' is VAR_CLASS or VAR_OBJECT. + * + * The method index, method function pointer and method type are returned in + * "lp". + */ + static void +get_lval_oc_method( + lval_T *lp, + class_T *cl, + char_u *key, + char_u *key_end, + vartype_T v_type) +{ + // Look for a method with this name. + // round 1: class functions (skipped for an object) + // round 2: object methods + for (int round = v_type == VAR_OBJECT ? 2 : 1; round <= 2; ++round) + { + int m_idx; + ufunc_T *fp; + + fp = method_lookup(cl, round == 1 ? VAR_CLASS : VAR_OBJECT, + key, key_end - key, &m_idx); + lp->ll_oi = m_idx; + if (fp != NULL) + { + lp->ll_ufunc = fp; + lp->ll_valtype = fp->uf_func_type; + break; + } + } +} + +/* + * Get a class or object lval variable in class "cl". The "key" argument + * points to the variable name and "key_end" points to the character after + * "key". "v_type" is VAR_CLASS or VAR_OBJECT. "cl_exec" is the class that is + * executing, or NULL. + * + * The variable index, typval and type are returned in "lp". Returns FAIL if + * the variable is not writable. Otherwise returns OK. + */ + static int +get_lval_oc_variable( + lval_T *lp, + class_T *cl, + char_u *key, + char_u *key_end, + vartype_T v_type, + class_T *cl_exec, + int flags) +{ + int m_idx; + ocmember_T *om; + + om = member_lookup(cl, v_type, key, key_end - key, &m_idx); + lp->ll_oi = m_idx; + if (om == NULL) + return OK; + + // Check variable is accessible + if (get_lval_check_access(cl_exec, cl, om, key_end, flags) == FAIL) + return FAIL; + + // When lhs is used to modify the variable, check it is not a read-only + // variable. + if ((flags & GLV_READ_ONLY) == 0 && (*key_end != '.' && *key_end != '[') + && oc_var_check_ro(cl, om)) + return FAIL; + + lp->ll_valtype = om->ocm_type; + + if (v_type == VAR_OBJECT) + lp->ll_tv = ((typval_T *)(lp->ll_tv->vval.v_object + 1)) + m_idx; + else + lp->ll_tv = &cl->class_members_tv[m_idx]; + + return OK; +} + +/* + * Get a Class or Object lval variable or method that can be assigned a value + * to: "name", "name.key", "name.key[expr]" etc. + * + * The 'key' argument points to the member name and 'key_end' points to the + * character after 'key'. 'v_type' is VAR_CLASS or VAR_OBJECT. 'cl_exec' is + * the class that is executing, or NULL. If 'quiet' is TRUE, then error + * messages are not displayed for invalid indexes. + * + * The Class or Object is returned in 'lp'. Returns OK on success and FAIL on + * failure. + */ + static int +get_lval_class_or_obj( + lval_T *lp, + char_u *key, + char_u *key_end, + vartype_T v_type, + class_T *cl_exec, + int flags, + int quiet) +{ + lp->ll_dict = NULL; + lp->ll_list = NULL; + + class_T *cl; + if (v_type == VAR_OBJECT) + { + if (lp->ll_tv->vval.v_object == NULL) + { + if (!quiet) + emsg(_(e_using_null_object)); + return FAIL; + } + cl = lp->ll_tv->vval.v_object->obj_class; + lp->ll_object = lp->ll_tv->vval.v_object; + } + else + { + cl = lp->ll_tv->vval.v_class; + lp->ll_object = NULL; + } + lp->ll_class = cl; + + if (cl == NULL) + // TODO: what if class is NULL? + return OK; + + lp->ll_valtype = NULL; + + if (flags & GLV_PREFER_FUNC) + get_lval_oc_method(lp, cl, key, key_end, v_type); + + // Look for object/class member variable + if (lp->ll_valtype == NULL) + { + if (get_lval_oc_variable(lp, cl, key, key_end, v_type, cl_exec, flags) + == FAIL) + return FAIL; + } + + if (lp->ll_valtype == NULL) + { + member_not_found_msg(cl, v_type, key, key_end - key); + return FAIL; + } + + return OK; +} + +/* + * Check whether dot (".") is allowed after the variable "name" with type + * "v_type". Only Dict, Class and Object types support a dot after the name. + * Returns TRUE if dot is allowed after the name. + */ + static int +dot_allowed_after_type(char_u *name, vartype_T v_type, int quiet) +{ + if (v_type != VAR_DICT && v_type != VAR_OBJECT && v_type != VAR_CLASS) + { + if (!quiet) + semsg(_(e_dot_not_allowed_after_str_str), + vartype_name(v_type), name); + return FALSE; + } + + return TRUE; +} + +/* + * Check whether left bracket ("[") is allowed after the variable "name" with + * type "v_type". Only Dict, List and Blob types support a bracket after the + * variable name. Returns TRUE if bracket is allowed after the name. + */ + static int +bracket_allowed_after_type(char_u *name, vartype_T v_type, int quiet) +{ + if (v_type == VAR_CLASS || v_type == VAR_OBJECT) + { + if (!quiet) + semsg(_(e_index_not_allowed_after_str_str), + vartype_name(v_type), name); + return FALSE; + } + + return TRUE; +} + +/* + * Check whether the variable "name" with type "v_type" can be followed by an + * index. Only Dict, List, Blob, Object and Class types support indexing. + * Returns TRUE if indexing is allowed after the name. + */ + static int +index_allowed_after_type(char_u *name, vartype_T v_type, int quiet) +{ + if (v_type != VAR_LIST && v_type != VAR_DICT && v_type != VAR_BLOB && + v_type != VAR_OBJECT && v_type != VAR_CLASS) + { + if (!quiet) + semsg(_(e_index_not_allowed_after_str_str), + vartype_name(v_type), name); + return FALSE; + } + + return TRUE; +} + +/* + * Get the lval of a list/dict/blob/object/class subitem starting at "p". Loop + * until no more [idx] or .key is following. + * + * If "rettv" is not NULL it points to the value to be assigned. + * "unlet" is TRUE for ":unlet". + * + * Returns a pointer to the character after the subscript on success or NULL on + * failure. + */ + static char_u * +get_lval_subscript( + lval_T *lp, + char_u *p, + char_u *name, + typval_T *rettv, + hashtab_T *ht, + dictitem_T *v, + int unlet, + int flags, // GLV_ values + class_T *cl_exec) +{ + int vim9script = in_vim9script(); + int quiet = flags & GLV_QUIET; + char_u *key = NULL; + int len; + typval_T var1; + typval_T var2; + int empty1 = FALSE; + int rc = FAIL; + + /* + * Loop until no more [idx] or .key is following. + */ + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; + + while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) + { + vartype_T v_type = lp->ll_tv->v_type; + + if (*p == '.' && !dot_allowed_after_type(name, v_type, quiet)) + goto done; + + if (*p == '[' && !bracket_allowed_after_type(name, v_type, quiet)) + goto done; + + if (!index_allowed_after_type(name, v_type, quiet)) + goto done; + + // A NULL list/blob works like an empty list/blob, allocate one now. + int r = OK; + if (v_type == VAR_LIST && lp->ll_tv->vval.v_list == NULL) + r = rettv_list_alloc(lp->ll_tv); + else if (v_type == VAR_BLOB && lp->ll_tv->vval.v_blob == NULL) + r = rettv_blob_alloc(lp->ll_tv); + if (r == FAIL) + goto done; + + if (lp->ll_range) + { + if (!quiet) + emsg(_(e_slice_must_come_last)); + goto done; + } +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: get_lval() loop: p: %s, type: %s", p, + vartype_name(v_type)); +#endif + + if (vim9script && lp->ll_valtype == NULL + && v != NULL + && lp->ll_tv == &v->di_tv + && ht != NULL && ht == get_script_local_ht()) + { + svar_T *sv = find_typval_in_script(lp->ll_tv, 0, TRUE); + + // Vim9 script local variable: get the type + if (sv != NULL) + { + lp->ll_valtype = sv->sv_type; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: ... loop: vim9 assign type: %s", + vartype_name(lp->ll_valtype->tt_type)); +#endif + } + } + + len = -1; + if (*p == '.') + { + key = p + 1; + + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) + ; + if (len == 0) + { + if (!quiet) + emsg(_(e_cannot_use_empty_key_for_dictionary)); + goto done; + } + p = key + len; + } + else + { + // Get the index [expr] or the first index [expr: ]. + p = skipwhite(p + 1); + if (*p == ':') + empty1 = TRUE; + else + { + empty1 = FALSE; + if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) // recursive! + goto done; + if (tv_get_string_chk(&var1) == NULL) + // not a number or string + goto done; + p = skipwhite(p); + } + + // Optionally get the second index [ :expr]. + if (*p == ':') + { + if (v_type == VAR_DICT) + { + if (!quiet) + emsg(_(e_cannot_slice_dictionary)); + goto done; + } + if (rettv != NULL + && !(rettv->v_type == VAR_LIST + && rettv->vval.v_list != NULL) + && !(rettv->v_type == VAR_BLOB + && rettv->vval.v_blob != NULL)) + { + if (!quiet) + emsg(_(e_slice_requires_list_or_blob_value)); + goto done; + } + p = skipwhite(p + 1); + if (*p == ']') + lp->ll_empty2 = TRUE; + else + { + lp->ll_empty2 = FALSE; + // recursive! + if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) + goto done; + if (tv_get_string_chk(&var2) == NULL) + // not a number or string + goto done; + } + lp->ll_range = TRUE; + } + else + lp->ll_range = FALSE; + + if (*p != ']') + { + if (!quiet) + emsg(_(e_missing_closing_square_brace)); + goto done; + } + + // Skip to past ']'. + ++p; + } +#ifdef LOG_LOCKVAR + if (len == -1) + ch_log(NULL, "LKVAR: ... loop: p: %s, '[' key: %s", p, + empty1 ? ":" : (char*)tv_get_string(&var1)); + else + ch_log(NULL, "LKVAR: ... loop: p: %s, '.' key: %s", p, key); +#endif + + if (v_type == VAR_DICT) + { + glv_status_T glv_status; + + glv_status = get_lval_dict_item(lp, name, key, len, &p, &var1, + flags, unlet, rettv); + if (glv_status == GLV_FAIL) + goto done; + if (glv_status == GLV_STOP) + break; + } + else if (v_type == VAR_BLOB) + { + if (get_lval_blob(lp, &var1, &var2, empty1, quiet) == FAIL) + goto done; + + break; + } + else if (v_type == VAR_LIST) + { + if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL) + goto done; + } + else // v_type == VAR_CLASS || v_type == VAR_OBJECT + { + if (get_lval_class_or_obj(lp, key, p, v_type, cl_exec, flags, + quiet) == FAIL) + goto done; + } + + clear_tv(&var1); + clear_tv(&var2); + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; + } + + rc = OK; + +done: + clear_tv(&var1); + clear_tv(&var2); + return rc == OK ? p : NULL; +} + /* * Get an lval: variable, Dict item or List item that can be assigned a value * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", @@ -1274,11 +1975,6 @@ get_lval( char_u *expr_start, *expr_end; int cc; dictitem_T *v = NULL; - typval_T var1; - typval_T var2; - int empty1 = FALSE; - char_u *key = NULL; - int len; hashtab_T *ht = NULL; int quiet = flags & GLV_QUIET; int writing = 0; @@ -1410,8 +2106,7 @@ get_lval( if (import != NULL) { p++; // skip '.' - p = get_lval_imported(lp, rettv, import->imp_sid, p, &v, - fne_flags, vim9script); + p = get_lval_imported(lp, import->imp_sid, p, &v, fne_flags); if (p == NULL) return NULL; } @@ -1456,447 +2151,21 @@ get_lval( return NULL; } - /* - * Loop until no more [idx] or .key is following. - */ - var1.v_type = VAR_UNKNOWN; - var2.v_type = VAR_UNKNOWN; - while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) - { - vartype_T v_type = lp->ll_tv->v_type; - - if (*p == '.' && v_type != VAR_DICT - && v_type != VAR_OBJECT - && v_type != VAR_CLASS) - { - if (!quiet) - semsg(_(e_dot_not_allowed_after_str_str), - vartype_name(v_type), name); - return NULL; - } - if (v_type != VAR_LIST - && v_type != VAR_DICT - && v_type != VAR_BLOB - && v_type != VAR_OBJECT - && v_type != VAR_CLASS) - { - if (!quiet) - semsg(_(e_index_not_allowed_after_str_str), - vartype_name(v_type), name); - return NULL; - } + // If the next character is a "." or a "[", then process the subitem. + p = get_lval_subscript(lp, p, name, rettv, ht, v, unlet, flags, cl_exec); + if (p == NULL) + return NULL; - // A NULL list/blob works like an empty list/blob, allocate one now. - int r = OK; - if (v_type == VAR_LIST && lp->ll_tv->vval.v_list == NULL) - r = rettv_list_alloc(lp->ll_tv); - else if (v_type == VAR_BLOB && lp->ll_tv->vval.v_blob == NULL) - r = rettv_blob_alloc(lp->ll_tv); - if (r == FAIL) - return NULL; + if (vim9script && lp->ll_valtype != NULL && rettv != NULL) + { + where_T where = WHERE_INIT; - if (lp->ll_range) - { - if (!quiet) - emsg(_(e_slice_must_come_last)); + // In a vim9 script, do type check and make sure the variable is + // writable. + if (check_typval_type(lp->ll_valtype, rettv, where) == FAIL) return NULL; - } -#ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: get_lval() loop: p: %s, type: %s", p, - vartype_name(v_type)); -#endif - - if (vim9script && lp->ll_valtype == NULL - && v != NULL - && lp->ll_tv == &v->di_tv - && ht != NULL && ht == get_script_local_ht()) - { - svar_T *sv = find_typval_in_script(lp->ll_tv, 0, TRUE); - - // Vim9 script local variable: get the type - if (sv != NULL) - { - lp->ll_valtype = sv->sv_type; -#ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: ... loop: vim9 assign type: %s", - vartype_name(lp->ll_valtype->tt_type)); -#endif - } - } - - len = -1; - if (*p == '.') - { - key = p + 1; - for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) - ; - if (len == 0) - { - if (!quiet) - emsg(_(e_cannot_use_empty_key_for_dictionary)); - return NULL; - } - p = key + len; - } - else - { - // Get the index [expr] or the first index [expr: ]. - p = skipwhite(p + 1); - if (*p == ':') - empty1 = TRUE; - else - { - empty1 = FALSE; - if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) // recursive! - return NULL; - if (tv_get_string_chk(&var1) == NULL) - { - // not a number or string - clear_tv(&var1); - return NULL; - } - p = skipwhite(p); - } - - // Optionally get the second index [ :expr]. - if (*p == ':') - { - if (v_type == VAR_DICT) - { - if (!quiet) - emsg(_(e_cannot_slice_dictionary)); - clear_tv(&var1); - return NULL; - } - if (rettv != NULL - && !(rettv->v_type == VAR_LIST - && rettv->vval.v_list != NULL) - && !(rettv->v_type == VAR_BLOB - && rettv->vval.v_blob != NULL)) - { - if (!quiet) - emsg(_(e_slice_requires_list_or_blob_value)); - clear_tv(&var1); - return NULL; - } - p = skipwhite(p + 1); - if (*p == ']') - lp->ll_empty2 = TRUE; - else - { - lp->ll_empty2 = FALSE; - // recursive! - if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) - { - clear_tv(&var1); - return NULL; - } - if (tv_get_string_chk(&var2) == NULL) - { - // not a number or string - clear_tv(&var1); - clear_tv(&var2); - return NULL; - } - } - lp->ll_range = TRUE; - } - else - lp->ll_range = FALSE; - - if (*p != ']') - { - if (!quiet) - emsg(_(e_missing_closing_square_brace)); - clear_tv(&var1); - clear_tv(&var2); - return NULL; - } - - // Skip to past ']'. - ++p; - } -#ifdef LOG_LOCKVAR - if (len == -1) - ch_log(NULL, "LKVAR: ... loop: p: %s, '[' key: %s", p, - empty1 ? ":" : (char*)tv_get_string(&var1)); - else - ch_log(NULL, "LKVAR: ... loop: p: %s, '.' key: %s", p, key); -#endif - - if (v_type == VAR_DICT) - { - if (len == -1) - { - // "[key]": get key from "var1" - key = tv_get_string_chk(&var1); // is number or string - if (key == NULL) - { - clear_tv(&var1); - return NULL; - } - } - lp->ll_list = NULL; - lp->ll_object = NULL; - lp->ll_class = NULL; - - // a NULL dict is equivalent with an empty dict - if (lp->ll_tv->vval.v_dict == NULL) - { - lp->ll_tv->vval.v_dict = dict_alloc(); - if (lp->ll_tv->vval.v_dict == NULL) - { - clear_tv(&var1); - return NULL; - } - ++lp->ll_tv->vval.v_dict->dv_refcount; - } - lp->ll_dict = lp->ll_tv->vval.v_dict; - - lp->ll_di = dict_find(lp->ll_dict, key, len); - - // When assigning to a scope dictionary check that a function and - // variable name is valid (only variable name unless it is l: or - // g: dictionary). Disallow overwriting a builtin function. - if (rettv != NULL && lp->ll_dict->dv_scope != 0) - { - int prevval; - - if (len != -1) - { - prevval = key[len]; - key[len] = NUL; - } - else - prevval = 0; // avoid compiler warning - int wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE - && (rettv->v_type == VAR_FUNC - || rettv->v_type == VAR_PARTIAL) - && var_wrong_func_name(key, lp->ll_di == NULL)) - || !valid_varname(key, -1, TRUE); - if (len != -1) - key[len] = prevval; - if (wrong) - { - clear_tv(&var1); - return NULL; - } - } - - if (lp->ll_valtype != NULL) - // use the type of the member - lp->ll_valtype = lp->ll_valtype->tt_member; - - if (lp->ll_di == NULL) - { - // Can't add "v:" or "a:" variable. - if (lp->ll_dict == get_vimvar_dict() - || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) - { - semsg(_(e_illegal_variable_name_str), name); - clear_tv(&var1); - return NULL; - } - - // Key does not exist in dict: may need to add it. - if (*p == '[' || *p == '.' || unlet) - { - if (!quiet) - semsg(_(e_key_not_present_in_dictionary_str), key); - clear_tv(&var1); - return NULL; - } - if (len == -1) - lp->ll_newkey = vim_strsave(key); - else - lp->ll_newkey = vim_strnsave(key, len); - clear_tv(&var1); - if (lp->ll_newkey == NULL) - p = NULL; - break; - } - // existing variable, need to check if it can be changed - else if ((flags & GLV_READ_ONLY) == 0 - && (var_check_ro(lp->ll_di->di_flags, name, FALSE) - || var_check_lock(lp->ll_di->di_flags, name, FALSE))) - { - clear_tv(&var1); - return NULL; - } - - clear_tv(&var1); - lp->ll_tv = &lp->ll_di->di_tv; - } - else if (v_type == VAR_BLOB) - { - long bloblen = blob_len(lp->ll_tv->vval.v_blob); - - /* - * Get the number and item for the only or first index of the List. - */ - if (empty1) - lp->ll_n1 = 0; - else - // is number or string - lp->ll_n1 = (long)tv_get_number(&var1); - clear_tv(&var1); - - if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL) - { - clear_tv(&var2); - return NULL; - } - if (lp->ll_range && !lp->ll_empty2) - { - lp->ll_n2 = (long)tv_get_number(&var2); - clear_tv(&var2); - if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) - == FAIL) - return NULL; - } - lp->ll_blob = lp->ll_tv->vval.v_blob; - lp->ll_tv = NULL; - break; - } - else if (v_type == VAR_LIST) - { - /* - * Get the number and item for the only or first index of the List. - */ - if (empty1) - lp->ll_n1 = 0; - else - // is number or string - lp->ll_n1 = (long)tv_get_number(&var1); - clear_tv(&var1); - - lp->ll_dict = NULL; - lp->ll_object = NULL; - lp->ll_class = NULL; - lp->ll_list = lp->ll_tv->vval.v_list; - lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1, - (flags & GLV_ASSIGN_WITH_OP) == 0, quiet); - if (lp->ll_li == NULL) - { - clear_tv(&var2); - return NULL; - } - - if (lp->ll_valtype != NULL) - // use the type of the member - lp->ll_valtype = lp->ll_valtype->tt_member; - - /* - * May need to find the item or absolute index for the second - * index of a range. - * When no index given: "lp->ll_empty2" is TRUE. - * Otherwise "lp->ll_n2" is set to the second index. - */ - if (lp->ll_range && !lp->ll_empty2) - { - lp->ll_n2 = (long)tv_get_number(&var2); - // is number or string - clear_tv(&var2); - if (check_range_index_two(lp->ll_list, - &lp->ll_n1, lp->ll_li, - &lp->ll_n2, quiet) == FAIL) - return NULL; - } - - lp->ll_tv = &lp->ll_li->li_tv; - } - else // v_type == VAR_CLASS || v_type == VAR_OBJECT - { - lp->ll_dict = NULL; - lp->ll_list = NULL; - - class_T *cl; - if (v_type == VAR_OBJECT) - { - if (lp->ll_tv->vval.v_object == NULL) - { - if (!quiet) - emsg(_(e_using_null_object)); - return NULL; - } - cl = lp->ll_tv->vval.v_object->obj_class; - lp->ll_object = lp->ll_tv->vval.v_object; - } - else - { - cl = lp->ll_tv->vval.v_class; - lp->ll_object = NULL; - } - lp->ll_class = cl; - - // TODO: what if class is NULL? - if (cl != NULL) - { - lp->ll_valtype = NULL; - - if (flags & GLV_PREFER_FUNC) - { - // First look for a function with this name. - // round 1: class functions (skipped for an object) - // round 2: object methods - for (int round = v_type == VAR_OBJECT ? 2 : 1; - round <= 2; ++round) - { - int m_idx; - ufunc_T *fp; - - fp = method_lookup(cl, - round == 1 ? VAR_CLASS : VAR_OBJECT, - key, p - key, &m_idx); - lp->ll_oi = m_idx; - if (fp != NULL) - { - lp->ll_ufunc = fp; - lp->ll_valtype = fp->uf_func_type; - break; - } - } - } - - if (lp->ll_valtype == NULL) - { - int m_idx; - ocmember_T *om - = member_lookup(cl, v_type, key, p - key, &m_idx); - lp->ll_oi = m_idx; - if (om != NULL) - { - if (get_lval_check_access(cl_exec, cl, om, - p, flags) == FAIL) - return NULL; - - // When lhs is used to modify the variable, check it is - // not a read-only variable. - if ((flags & GLV_READ_ONLY) == 0 - && (*p != '.' && *p != '[') - && oc_var_check_ro(cl, om)) - return NULL; - - lp->ll_valtype = om->ocm_type; - - if (v_type == VAR_OBJECT) - lp->ll_tv = ((typval_T *)( - lp->ll_tv->vval.v_object + 1)) + m_idx; - else - lp->ll_tv = &cl->class_members_tv[m_idx]; - } - } - - if (lp->ll_valtype == NULL) - { - member_not_found_msg(cl, v_type, key, p - key); - return NULL; - } - } - } } - clear_tv(&var1); lp->ll_name_end = p; return p; } @@ -2085,159 +2354,238 @@ set_var_lval( } /* - * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" - * and "tv1 .= tv2" + * Handle "blob1 += blob2". * Returns OK or FAIL. */ - int -tv_op(typval_T *tv1, typval_T *tv2, char_u *op) + static int +tv_op_blob(typval_T *tv1, typval_T *tv2, char_u *op) +{ + if (*op != '+' || tv2->v_type != VAR_BLOB) + return FAIL; + + // Blob += Blob + if (tv2->vval.v_blob == NULL) + return OK; + + if (tv1->vval.v_blob == NULL) + { + tv1->vval.v_blob = tv2->vval.v_blob; + ++tv1->vval.v_blob->bv_refcount; + return OK; + } + + blob_T *b1 = tv1->vval.v_blob; + blob_T *b2 = tv2->vval.v_blob; + int len = blob_len(b2); + + for (int i = 0; i < len; i++) + ga_append(&b1->bv_ga, blob_get(b2, i)); + + return OK; +} + +/* + * Handle "list1 += list2". + * Returns OK or FAIL. + */ + static int +tv_op_list(typval_T *tv1, typval_T *tv2, char_u *op) +{ + if (*op != '+' || tv2->v_type != VAR_LIST) + return FAIL; + + // List += List + if (tv2->vval.v_list == NULL) + return OK; + + if (tv1->vval.v_list == NULL) + { + tv1->vval.v_list = tv2->vval.v_list; + ++tv1->vval.v_list->lv_refcount; + } + else + list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); + + return OK; +} + +/* + * Handle number operations: + * nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr + * + * Returns OK or FAIL. + */ + static int +tv_op_number(typval_T *tv1, typval_T *tv2, char_u *op) { varnumber_T n; + int failed = FALSE; + + n = tv_get_number(tv1); + if (tv2->v_type == VAR_FLOAT) + { + float_T f = n; + + if (*op == '%') + return FAIL; + switch (*op) + { + case '+': f += tv2->vval.v_float; break; + case '-': f -= tv2->vval.v_float; break; + case '*': f *= tv2->vval.v_float; break; + case '/': f /= tv2->vval.v_float; break; + } + clear_tv(tv1); + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f; + } + else + { + switch (*op) + { + case '+': n += tv_get_number(tv2); break; + case '-': n -= tv_get_number(tv2); break; + case '*': n *= tv_get_number(tv2); break; + case '/': n = num_divide(n, tv_get_number(tv2), &failed); break; + case '%': n = num_modulus(n, tv_get_number(tv2), &failed); break; + } + clear_tv(tv1); + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n; + } + + return failed ? FAIL : OK; +} + +/* + * Handle "str1 .= str2" + * Returns OK or FAIL. + */ + static int +tv_op_string(typval_T *tv1, typval_T *tv2, char_u *op UNUSED) +{ char_u numbuf[NUMBUFLEN]; char_u *s; - int failed = FALSE; - // Can't do anything with a Funcref or Dict or Type on the right. + if (tv2->v_type == VAR_FLOAT) + return FAIL; + + // str .= str + s = tv_get_string(tv1); + s = concat_str(s, tv_get_string_buf(tv2, numbuf)); + clear_tv(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = s; + + return OK; +} + +/* + * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" + * and "tv1 .= tv2" + * Returns OK or FAIL. + */ + static int +tv_op_nr_or_string(typval_T *tv1, typval_T *tv2, char_u *op) +{ + if (tv2->v_type == VAR_LIST) + return FAIL; + + if (vim_strchr((char_u *)"+-*/%", *op) != NULL) + return tv_op_number(tv1, tv2, op); + + return tv_op_string(tv1, tv2, op); +} + +/* + * Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2". + * Returns OK or FAIL. + */ + static int +tv_op_float(typval_T *tv1, typval_T *tv2, char_u *op) +{ + float_T f; + + if (*op == '%' || *op == '.' + || (tv2->v_type != VAR_FLOAT + && tv2->v_type != VAR_NUMBER + && tv2->v_type != VAR_STRING)) + return FAIL; + + if (tv2->v_type == VAR_FLOAT) + f = tv2->vval.v_float; + else + f = tv_get_number(tv2); + switch (*op) + { + case '+': tv1->vval.v_float += f; break; + case '-': tv1->vval.v_float -= f; break; + case '*': tv1->vval.v_float *= f; break; + case '/': tv1->vval.v_float /= f; break; + } + + return OK; +} + +/* + * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2" + * and "tv1 .= tv2" + * Returns OK or FAIL. + */ + int +tv_op(typval_T *tv1, typval_T *tv2, char_u *op) +{ + // Can't do anything with a Funcref or Dict on the right. // v:true and friends only work with "..=". - if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT - && tv2->v_type != VAR_CLASS && tv2->v_type != VAR_TYPEALIAS - && ((tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) - || *op == '.')) - { - switch (tv1->v_type) - { - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_VOID: - case VAR_DICT: - case VAR_FUNC: - case VAR_PARTIAL: - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_JOB: - case VAR_CHANNEL: - case VAR_INSTR: - case VAR_OBJECT: - break; - case VAR_CLASS: - case VAR_TYPEALIAS: - check_typval_is_value(tv1); - return FAIL; + if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT + || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL) + && *op != '.')) + { + semsg(_(e_wrong_variable_type_for_str_equal), op); + return FAIL; + } - case VAR_BLOB: - if (*op != '+' || tv2->v_type != VAR_BLOB) - break; - // BLOB += BLOB - if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) - { - blob_T *b1 = tv1->vval.v_blob; - blob_T *b2 = tv2->vval.v_blob; - int i, len = blob_len(b2); - for (i = 0; i < len; i++) - ga_append(&b1->bv_ga, blob_get(b2, i)); - } - return OK; + int retval = FAIL; + switch (tv1->v_type) + { + case VAR_UNKNOWN: + case VAR_ANY: + case VAR_VOID: + case VAR_DICT: + case VAR_FUNC: + case VAR_PARTIAL: + case VAR_BOOL: + case VAR_SPECIAL: + case VAR_JOB: + case VAR_CHANNEL: + case VAR_INSTR: + case VAR_OBJECT: + case VAR_CLASS: + case VAR_TYPEALIAS: + break; - case VAR_LIST: - if (*op != '+' || tv2->v_type != VAR_LIST) - break; - // List += List - if (tv2->vval.v_list != NULL) - { - if (tv1->vval.v_list == NULL) - { - tv1->vval.v_list = tv2->vval.v_list; - ++tv1->vval.v_list->lv_refcount; - } - else - list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); - } - return OK; + case VAR_BLOB: + retval = tv_op_blob(tv1, tv2, op); + break; - case VAR_NUMBER: - case VAR_STRING: - if (tv2->v_type == VAR_LIST) - break; - if (vim_strchr((char_u *)"+-*/%", *op) != NULL) - { - // nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr - n = tv_get_number(tv1); - if (tv2->v_type == VAR_FLOAT) - { - float_T f = n; - - if (*op == '%') - break; - switch (*op) - { - case '+': f += tv2->vval.v_float; break; - case '-': f -= tv2->vval.v_float; break; - case '*': f *= tv2->vval.v_float; break; - case '/': f /= tv2->vval.v_float; break; - } - clear_tv(tv1); - tv1->v_type = VAR_FLOAT; - tv1->vval.v_float = f; - } - else - { - switch (*op) - { - case '+': n += tv_get_number(tv2); break; - case '-': n -= tv_get_number(tv2); break; - case '*': n *= tv_get_number(tv2); break; - case '/': n = num_divide(n, tv_get_number(tv2), - &failed); break; - case '%': n = num_modulus(n, tv_get_number(tv2), - &failed); break; - } - clear_tv(tv1); - tv1->v_type = VAR_NUMBER; - tv1->vval.v_number = n; - } - } - else - { - if (tv2->v_type == VAR_FLOAT) - break; - - // str .= str - s = tv_get_string(tv1); - s = concat_str(s, tv_get_string_buf(tv2, numbuf)); - clear_tv(tv1); - tv1->v_type = VAR_STRING; - tv1->vval.v_string = s; - } - return failed ? FAIL : OK; + case VAR_LIST: + retval = tv_op_list(tv1, tv2, op); + break; - case VAR_FLOAT: - { - float_T f; - - if (*op == '%' || *op == '.' - || (tv2->v_type != VAR_FLOAT - && tv2->v_type != VAR_NUMBER - && tv2->v_type != VAR_STRING)) - break; - if (tv2->v_type == VAR_FLOAT) - f = tv2->vval.v_float; - else - f = tv_get_number(tv2); - switch (*op) - { - case '+': tv1->vval.v_float += f; break; - case '-': tv1->vval.v_float -= f; break; - case '*': tv1->vval.v_float *= f; break; - case '/': tv1->vval.v_float /= f; break; - } - } - return OK; - } + case VAR_NUMBER: + case VAR_STRING: + retval = tv_op_nr_or_string(tv1, tv2, op); + break; + + case VAR_FLOAT: + retval = tv_op_float(tv1, tv2, op); + break; } - if (check_typval_is_value(tv2) == OK) + if (retval != OK) semsg(_(e_wrong_variable_type_for_str_equal), op); - return FAIL; + + return retval; } /* @@ -2714,7 +3062,7 @@ newline_skip_comments(char_u *arg) char_u *nl = vim_strchr(p, NL); if (nl == NULL) - break; + break; p = nl; } if (*p != NL) @@ -3599,6 +3947,40 @@ eval_addlist(typval_T *tv1, typval_T *tv2) } /* + * Left or right shift the number "tv1" by the number "tv2" and store the + * result in "tv1". + * + * Return OK or FAIL. + */ + static int +eval_shift_number(typval_T *tv1, typval_T *tv2, int shift_type) +{ + if (tv2->v_type != VAR_NUMBER || tv2->vval.v_number < 0) + { + // right operand should be a positive number + if (tv2->v_type != VAR_NUMBER) + emsg(_(e_bitshift_ops_must_be_number)); + else + emsg(_(e_bitshift_ops_must_be_positive)); + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + + if (tv2->vval.v_number > MAX_LSHIFT_BITS) + // shifting more bits than we have always results in zero + tv1->vval.v_number = 0; + else if (shift_type == EXPR_LSHIFT) + tv1->vval.v_number = + (uvarnumber_T)tv1->vval.v_number << tv2->vval.v_number; + else + tv1->vval.v_number = + (uvarnumber_T)tv1->vval.v_number >> tv2->vval.v_number; + + return OK; +} + +/* * Handle the bitwise left/right shift operator expression: * var1 << var2 * var1 >> var2 @@ -3624,16 +4006,16 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) { char_u *p; int getnext; - exprtype_T type; + exprtype_T exprtype; int evaluate; typval_T var2; int vim9script; p = eval_next_non_blank(*arg, evalarg, &getnext); if (p[0] == '<' && p[1] == '<') - type = EXPR_LSHIFT; + exprtype = EXPR_LSHIFT; else if (p[0] == '>' && p[1] == '>') - type = EXPR_RSHIFT; + exprtype = EXPR_RSHIFT; else return OK; @@ -3678,27 +4060,8 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) if (evaluate) { - if (var2.v_type != VAR_NUMBER || var2.vval.v_number < 0) - { - // right operand should be a positive number - if (var2.v_type != VAR_NUMBER) - emsg(_(e_bitshift_ops_must_be_number)); - else - emsg(_(e_bitshift_ops_must_be_positive)); - clear_tv(rettv); - clear_tv(&var2); + if (eval_shift_number(rettv, &var2, exprtype) == FAIL) return FAIL; - } - - if (var2.vval.v_number > MAX_LSHIFT_BITS) - // shifting more bits than we have always results in zero - rettv->vval.v_number = 0; - else if (type == EXPR_LSHIFT) - rettv->vval.v_number = - (uvarnumber_T)rettv->vval.v_number << var2.vval.v_number; - else - rettv->vval.v_number = - (uvarnumber_T)rettv->vval.v_number >> var2.vval.v_number; } clear_tv(&var2); @@ -3708,6 +4071,120 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) } /* + * Concatenate strings "tv1" and "tv2" and store the result in "tv1". + */ + static int +eval_concat_str(typval_T *tv1, typval_T *tv2) +{ + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + char_u *s1 = tv_get_string_buf(tv1, buf1); + char_u *s2 = NULL; + char_u *p; + int vim9script = in_vim9script(); + + if (vim9script && (tv2->v_type == VAR_VOID + || tv2->v_type == VAR_CHANNEL + || tv2->v_type == VAR_JOB)) + semsg(_(e_using_invalid_value_as_string_str), + vartype_name(tv2->v_type)); + else if (vim9script && tv2->v_type == VAR_FLOAT) + { + vim_snprintf((char *)buf2, NUMBUFLEN, "%g", + tv2->vval.v_float); + s2 = buf2; + } + else + s2 = tv_get_string_buf_chk(tv2, buf2); + if (s2 == NULL) // type error ? + { + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + + p = concat_str(s1, s2); + clear_tv(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = p; + + return OK; +} + +/* + * Add or subtract numbers "tv1" and "tv2" and store the result in "tv1". + * The numbers can be whole numbers or floats. + */ + static int +eval_addsub_number(typval_T *tv1, typval_T *tv2, int op) +{ + int error = FALSE; + varnumber_T n1, n2; + float_T f1 = 0, f2 = 0; + + if (tv1->v_type == VAR_FLOAT) + { + f1 = tv1->vval.v_float; + n1 = 0; + } + else + { + n1 = tv_get_number_chk(tv1, &error); + if (error) + { + // This can only happen for "list + non-list" or + // "blob + non-blob". For "non-list + ..." or + // "something - ...", we returned before evaluating the + // 2nd operand. + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + if (tv2->v_type == VAR_FLOAT) + f1 = n1; + } + if (tv2->v_type == VAR_FLOAT) + { + f2 = tv2->vval.v_float; + n2 = 0; + } + else + { + n2 = tv_get_number_chk(tv2, &error); + if (error) + { + clear_tv(tv1); + clear_tv(tv2); + return FAIL; + } + if (tv1->v_type == VAR_FLOAT) + f2 = n2; + } + clear_tv(tv1); + + // If there is a float on either side the result is a float. + if (tv1->v_type == VAR_FLOAT || tv2->v_type == VAR_FLOAT) + { + if (op == '+') + f1 = f1 + f2; + else + f1 = f1 - f2; + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f1; + } + else + { + if (op == '+') + n1 = n1 + n2; + else + n1 = n1 - n2; + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n1; + } + + return OK; +} + +/* * Handle fifth level expression: * + number addition, concatenation of list or blob * - number subtraction @@ -3814,33 +4291,8 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg) */ if (op == '.') { - char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; - char_u *s1 = tv_get_string_buf(rettv, buf1); - char_u *s2 = NULL; - - if (vim9script && (var2.v_type == VAR_VOID - || var2.v_type == VAR_CHANNEL - || var2.v_type == VAR_JOB)) - semsg(_(e_using_invalid_value_as_string_str), - vartype_name(var2.v_type)); - else if (vim9script && var2.v_type == VAR_FLOAT) - { - vim_snprintf((char *)buf2, NUMBUFLEN, "%g", - var2.vval.v_float); - s2 = buf2; - } - else - s2 = tv_get_string_buf_chk(&var2, buf2); - if (s2 == NULL) // type error ? - { - clear_tv(rettv); - clear_tv(&var2); + if (eval_concat_str(rettv, &var2) == FAIL) return FAIL; - } - p = concat_str(s1, s2); - clear_tv(rettv); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = p; } else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) @@ -3853,73 +4305,119 @@ eval6(char_u **arg, typval_T *rettv, evalarg_T *evalarg) } else { - int error = FALSE; - varnumber_T n1, n2; - float_T f1 = 0, f2 = 0; + if (eval_addsub_number(rettv, &var2, op) == FAIL) + return FAIL; + } + clear_tv(&var2); + } + } + return OK; +} - if (rettv->v_type == VAR_FLOAT) - { - f1 = rettv->vval.v_float; - n1 = 0; - } - else - { - n1 = tv_get_number_chk(rettv, &error); - if (error) - { - // This can only happen for "list + non-list" or - // "blob + non-blob". For "non-list + ..." or - // "something - ...", we returned before evaluating the - // 2nd operand. - clear_tv(rettv); - clear_tv(&var2); - return FAIL; - } - if (var2.v_type == VAR_FLOAT) - f1 = n1; - } - if (var2.v_type == VAR_FLOAT) - { - f2 = var2.vval.v_float; - n2 = 0; - } - else - { - n2 = tv_get_number_chk(&var2, &error); - if (error) - { - clear_tv(rettv); - clear_tv(&var2); - return FAIL; - } - if (rettv->v_type == VAR_FLOAT) - f2 = n2; - } - clear_tv(rettv); +/* + * Multiply or divide or compute the modulo of numbers "tv1" and "tv2" and + * store the result in "tv1". The numbers can be whole numbers or floats. + */ + static int +eval_multdiv_number(typval_T *tv1, typval_T *tv2, int op) +{ + varnumber_T n1, n2; + float_T f1, f2; + int error; + int use_float = FALSE; - // If there is a float on either side the result is a float. - if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) - { - if (op == '+') - f1 = f1 + f2; - else - f1 = f1 - f2; - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = f1; - } + f1 = 0; + f2 = 0; + error = FALSE; + if (tv1->v_type == VAR_FLOAT) + { + f1 = tv1->vval.v_float; + use_float = TRUE; + n1 = 0; + } + else + n1 = tv_get_number_chk(tv1, &error); + clear_tv(tv1); + if (error) + { + clear_tv(tv2); + return FAIL; + } + + if (tv2->v_type == VAR_FLOAT) + { + if (!use_float) + { + f1 = n1; + use_float = TRUE; + } + f2 = tv2->vval.v_float; + n2 = 0; + } + else + { + n2 = tv_get_number_chk(tv2, &error); + clear_tv(tv2); + if (error) + return FAIL; + if (use_float) + f2 = n2; + } + + /* + * Compute the result. + * When either side is a float the result is a float. + */ + if (use_float) + { + if (op == '*') + f1 = f1 * f2; + else if (op == '/') + { +#ifdef VMS + // VMS crashes on divide by zero, work around it + if (f2 == 0.0) + { + if (f1 == 0) + f1 = -1 * __F_FLT_MAX - 1L; // similar to NaN + else if (f1 < 0) + f1 = -1 * __F_FLT_MAX; else - { - if (op == '+') - n1 = n1 + n2; - else - n1 = n1 - n2; - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; - } + f1 = __F_FLT_MAX; } - clear_tv(&var2); + else + f1 = f1 / f2; +#else + // We rely on the floating point library to handle divide + // by zero to result in "inf" and not a crash. + f1 = f1 / f2; +#endif } + else + { + emsg(_(e_cannot_use_percent_with_float)); + return FAIL; + } + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f1; + } + else + { + int failed = FALSE; + + if (op == '*') + n1 = n1 * n2; + else if (op == '/') + n1 = num_divide(n1, n2, &failed); + else + n1 = num_modulus(n1, n2, &failed); + if (failed) + return FAIL; + + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n1; } + return OK; } @@ -3941,8 +4439,6 @@ eval7( evalarg_T *evalarg, int want_string) // after "." operator { - int use_float = FALSE; - /* * Get the first expression. */ @@ -3959,9 +4455,6 @@ eval7( typval_T var2; char_u *p; int op; - varnumber_T n1, n2; - float_T f1, f2; - int error; // "*=", "/=" and "%=" are assignments p = eval_next_non_blank(*arg, evalarg, &getnext); @@ -3983,26 +4476,6 @@ eval7( *arg = p; } - f1 = 0; - f2 = 0; - error = FALSE; - if (evaluate) - { - if (rettv->v_type == VAR_FLOAT) - { - f1 = rettv->vval.v_float; - use_float = TRUE; - n1 = 0; - } - else - n1 = tv_get_number_chk(rettv, &error); - clear_tv(rettv); - if (error) - return FAIL; - } - else - n1 = 0; - /* * Get the second variable. */ @@ -4017,81 +4490,9 @@ eval7( return FAIL; if (evaluate) - { - if (var2.v_type == VAR_FLOAT) - { - if (!use_float) - { - f1 = n1; - use_float = TRUE; - } - f2 = var2.vval.v_float; - n2 = 0; - } - else - { - n2 = tv_get_number_chk(&var2, &error); - clear_tv(&var2); - if (error) - return FAIL; - if (use_float) - f2 = n2; - } - - /* - * Compute the result. - * When either side is a float the result is a float. - */ - if (use_float) - { - if (op == '*') - f1 = f1 * f2; - else if (op == '/') - { -#ifdef VMS - // VMS crashes on divide by zero, work around it - if (f2 == 0.0) - { - if (f1 == 0) - f1 = -1 * __F_FLT_MAX - 1L; // similar to NaN - else if (f1 < 0) - f1 = -1 * __F_FLT_MAX; - else - f1 = __F_FLT_MAX; - } - else - f1 = f1 / f2; -#else - // We rely on the floating point library to handle divide - // by zero to result in "inf" and not a crash. - f1 = f1 / f2; -#endif - } - else - { - emsg(_(e_cannot_use_percent_with_float)); - return FAIL; - } - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = f1; - } - else - { - int failed = FALSE; - - if (op == '*') - n1 = n1 * n2; - else if (op == '/') - n1 = num_divide(n1, n2, &failed); - else - n1 = num_modulus(n1, n2, &failed); - if (failed) - return FAIL; - - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; - } - } + // Compute the result. + if (eval_multdiv_number(rettv, &var2, op) == FAIL) + return FAIL; } return OK; @@ -4244,18 +4645,21 @@ handle_predefined(char_u *s, int len, typval_T *rettv) case 9: if (STRNCMP(s, "null_", 5) != 0) break; + // null_list if (STRNCMP(s + 5, "list", 4) == 0) { rettv->v_type = VAR_LIST; rettv->vval.v_list = NULL; return OK; } + // null_dict if (STRNCMP(s + 5, "dict", 4) == 0) { rettv->v_type = VAR_DICT; rettv->vval.v_dict = NULL; return OK; } + // null_blob if (STRNCMP(s + 5, "blob", 4) == 0) { rettv->v_type = VAR_BLOB; @@ -4314,6 +4718,158 @@ handle_predefined(char_u *s, int len, typval_T *rettv) } /* + * Handle register contents: @r. + */ + static void +eval9_reg_contents( + char_u **arg, + typval_T *rettv, + int evaluate) +{ + int vim9script = in_vim9script(); + + ++*arg; // skip '@' + if (evaluate) + { + if (vim9script && IS_WHITE_OR_NUL(**arg)) + semsg(_(e_syntax_error_at_str), *arg); + else if (vim9script && !valid_yank_reg(**arg, FALSE)) + emsg_invreg(**arg); + else + { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_reg_contents(**arg, + GREG_EXPR_SRC); + } + } + if (**arg != NUL) + ++*arg; +} + +/* + * Handle a nested expression: (expression) or lambda: (arg) => expr + */ + static int +eval9_nested_expr( + char_u **arg, + typval_T *rettv, + evalarg_T *evalarg, + int evaluate) +{ + int ret = NOTDONE; + int vim9script = in_vim9script(); + + if (vim9script) + { + ret = get_lambda_tv(arg, rettv, TRUE, evalarg); + if (ret == OK && evaluate) + { + ufunc_T *ufunc = rettv->vval.v_partial->pt_func; + + // Compile it here to get the return type. The return + // type is optional, when it's missing use t_unknown. + // This is recognized in compile_return(). + if (ufunc->uf_ret_type->tt_type == VAR_VOID) + ufunc->uf_ret_type = &t_unknown; + if (compile_def_function(ufunc, FALSE, + get_compile_type(ufunc), NULL) == FAIL) + { + clear_tv(rettv); + ret = FAIL; + } + } + } + if (ret == NOTDONE) + { + *arg = skipwhite_and_linebreak(*arg + 1, evalarg); + ret = eval1(arg, rettv, evalarg); // recursive! + + *arg = skipwhite_and_linebreak(*arg, evalarg); + if (**arg == ')') + ++*arg; + else if (ret == OK) + { + emsg(_(e_missing_closing_paren)); + clear_tv(rettv); + ret = FAIL; + } + } + + return ret; +} + +/* +* Handle be a variable or function name. +* Can also be a curly-braces kind of name: {expr}. +*/ + static int +eval9_var_func_name( + char_u **arg, + typval_T *rettv, + evalarg_T *evalarg, + int evaluate, + char_u **name_start) +{ + char_u *s; + int len; + char_u *alias; + int ret = OK; + int vim9script = in_vim9script(); + + s = *arg; + len = get_name_len(arg, &alias, evaluate, TRUE); + if (alias != NULL) + s = alias; + + if (len <= 0) + ret = FAIL; + else + { + int flags = evalarg == NULL ? 0 : evalarg->eval_flags; + + if (evaluate && vim9script && len == 1 && *s == '_') + { + emsg(_(e_cannot_use_underscore_here)); + ret = FAIL; + } + else if (evaluate && vim9script && len > 2 + && s[0] == 's' && s[1] == ':') + { + semsg(_(e_cannot_use_s_colon_in_vim9_script_str), s); + ret = FAIL; + } + else if ((vim9script ? **arg : *skipwhite(*arg)) == '(') + { + // "name(..." recursive! + *arg = skipwhite(*arg); + ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL); + } + else if (evaluate) + { + // get the value of "true", "false", etc. or a variable + ret = FAIL; + if (vim9script) + ret = handle_predefined(s, len, rettv); + if (ret == FAIL) + { + *name_start = s; + ret = eval_variable(s, len, 0, rettv, NULL, + EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT); + } + } + else + { + // skip the name + check_vars(s, len); + ret = OK; + } + } + vim_free(alias); + + return ret; +} + +/* * Handle sixth level expression: * number number constant * 0zFFFFFFFF Blob constant @@ -4352,12 +4908,9 @@ eval9( { int evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); - int len; - char_u *s; char_u *name_start = NULL; char_u *start_leader, *end_leader; int ret = OK; - char_u *alias; static int recurse = 0; int vim9script = in_vim9script(); @@ -4440,19 +4993,9 @@ eval9( break; /* - * Dictionary: #{key: val, key: val} + * Literal Dictionary: #{key: val, key: val} */ - case '#': if (vim9script) - { - ret = vim9_bad_comment(*arg) ? FAIL : NOTDONE; - } - else if ((*arg)[1] == '{') - { - ++*arg; - ret = eval_dict(arg, rettv, evalarg, TRUE); - } - else - ret = NOTDONE; + case '#': ret = eval_lit_dict(arg, rettv, evalarg); break; /* @@ -4486,64 +5029,14 @@ eval9( /* * Register contents: @r. */ - case '@': ++*arg; - if (evaluate) - { - if (vim9script && IS_WHITE_OR_NUL(**arg)) - semsg(_(e_syntax_error_at_str), *arg); - else if (vim9script && !valid_yank_reg(**arg, FALSE)) - emsg_invreg(**arg); - else - { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(**arg, - GREG_EXPR_SRC); - } - } - if (**arg != NUL) - ++*arg; + case '@': eval9_reg_contents(arg, rettv, evaluate); break; /* * nested expression: (expression). * or lambda: (arg) => expr */ - case '(': ret = NOTDONE; - if (vim9script) - { - ret = get_lambda_tv(arg, rettv, TRUE, evalarg); - if (ret == OK && evaluate) - { - ufunc_T *ufunc = rettv->vval.v_partial->pt_func; - - // Compile it here to get the return type. The return - // type is optional, when it's missing use t_unknown. - // This is recognized in compile_return(). - if (ufunc->uf_ret_type->tt_type == VAR_VOID) - ufunc->uf_ret_type = &t_unknown; - if (compile_def_function(ufunc, FALSE, - get_compile_type(ufunc), NULL) == FAIL) - { - clear_tv(rettv); - ret = FAIL; - } - } - } - if (ret == NOTDONE) - { - *arg = skipwhite_and_linebreak(*arg + 1, evalarg); - ret = eval1(arg, rettv, evalarg); // recursive! - - *arg = skipwhite_and_linebreak(*arg, evalarg); - if (**arg == ')') - ++*arg; - else if (ret == OK) - { - emsg(_(e_missing_closing_paren)); - clear_tv(rettv); - ret = FAIL; - } - } + case '(': ret = eval9_nested_expr(arg, rettv, evalarg, evaluate); break; default: ret = NOTDONE; @@ -4556,55 +5049,7 @@ eval9( * Must be a variable or function name. * Can also be a curly-braces kind of name: {expr}. */ - s = *arg; - len = get_name_len(arg, &alias, evaluate, TRUE); - if (alias != NULL) - s = alias; - - if (len <= 0) - ret = FAIL; - else - { - int flags = evalarg == NULL ? 0 : evalarg->eval_flags; - - if (evaluate && vim9script && len == 1 && *s == '_') - { - emsg(_(e_cannot_use_underscore_here)); - ret = FAIL; - } - else if (evaluate && vim9script && len > 2 - && s[0] == 's' && s[1] == ':') - { - semsg(_(e_cannot_use_s_colon_in_vim9_script_str), s); - ret = FAIL; - } - else if ((vim9script ? **arg : *skipwhite(*arg)) == '(') - { - // "name(..." recursive! - *arg = skipwhite(*arg); - ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL); - } - else if (evaluate) - { - // get the value of "true", "false", etc. or a variable - ret = FAIL; - if (vim9script) - ret = handle_predefined(s, len, rettv); - if (ret == FAIL) - { - name_start = s; - ret = eval_variable(s, len, 0, rettv, NULL, - EVAL_VAR_VERBOSE + EVAL_VAR_IMPORT); - } - } - else - { - // skip the name - check_vars(s, len); - ret = OK; - } - } - vim_free(alias); + ret = eval9_var_func_name(arg, rettv, evalarg, evaluate, &name_start); } // Handle following '[', '(' and '.' for expr[expr], expr.name, @@ -4895,7 +5340,7 @@ eval_method( { *arg = name; - // Truncate the name a the "(". Avoid trying to get another line + // Truncate the name at the "(". Avoid trying to get another line // by making "getline" NULL. *paren = NUL; char_u *(*getline)(int, void *, int, getline_opt_T) = NULL; @@ -4950,6 +5395,9 @@ eval_method( clear_tv(&base); vim_free(tofree); + if (alias != NULL) + vim_free(alias); + return ret; } @@ -5153,30 +5601,6 @@ check_can_index(typval_T *rettv, int evaluate, int verbose) } /* - * slice() function - */ - void -f_slice(typval_T *argvars, typval_T *rettv) -{ - if (in_vim9script() - && ((argvars[0].v_type != VAR_STRING - && argvars[0].v_type != VAR_LIST - && argvars[0].v_type != VAR_BLOB - && check_for_list_arg(argvars, 0) == FAIL) - || check_for_number_arg(argvars, 1) == FAIL - || check_for_opt_number_arg(argvars, 2) == FAIL)) - return; - - if (check_can_index(argvars, TRUE, FALSE) != OK) - return; - - copy_tv(argvars, rettv); - eval_index_inner(rettv, TRUE, argvars + 1, - argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, - TRUE, NULL, 0, FALSE); -} - -/* * Apply index or range to "rettv". * "var1" is the first index, NULL for [:expr]. * "var2" is the second index, NULL for [expr] and [expr: ] @@ -5426,763 +5850,299 @@ partial_unref(partial_T *pt) } /* - * Return the next (unique) copy ID. - * Used for serializing nested structures. - */ - int -get_copyID(void) -{ - current_copyID += COPYID_INC; - return current_copyID; -} - -/* - * Garbage collection for lists and dictionaries. - * - * We use reference counts to be able to free most items right away when they - * are no longer used. But for composite items it's possible that it becomes - * unused while the reference count is > 0: When there is a recursive - * reference. Example: - * :let l = [1, 2, 3] - * :let d = {9: l} - * :let l[1] = d - * - * Since this is quite unusual we handle this with garbage collection: every - * once in a while find out which lists and dicts are not referenced from any - * variable. - * - * Here is a good reference text about garbage collection (refers to Python - * but it applies to all reference-counting mechanisms): - * http://python.ca/nas/python/gc/ - */ - -/* - * Do garbage collection for lists and dicts. - * When "testing" is TRUE this is called from test_garbagecollect_now(). - * Return TRUE if some memory was freed. + * Return a textual representation of a string in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When both "echo_style" and "composite_val" are FALSE, put quotes around + * strings as "string()", otherwise does not put quotes around strings. + * May return NULL. */ - int -garbage_collect(int testing) + static char_u * +string_tv2string( + typval_T *tv, + char_u **tofree, + int echo_style, + int composite_val) { - int copyID; - int abort = FALSE; - buf_T *buf; - win_T *wp; - int did_free = FALSE; - tabpage_T *tp; - - if (!testing) - { - // Only do this once. - want_garbage_collect = FALSE; - may_garbage_collect = FALSE; - garbage_collect_at_exit = FALSE; - } - - // The execution stack can grow big, limit the size. - if (exestack.ga_maxlen - exestack.ga_len > 500) - { - size_t new_len; - char_u *pp; - int n; - - // Keep 150% of the current size, with a minimum of the growth size. - n = exestack.ga_len / 2; - if (n < exestack.ga_growsize) - n = exestack.ga_growsize; - - // Don't make it bigger though. - if (exestack.ga_len + n < exestack.ga_maxlen) - { - new_len = (size_t)exestack.ga_itemsize * (exestack.ga_len + n); - pp = vim_realloc(exestack.ga_data, new_len); - if (pp == NULL) - return FAIL; - exestack.ga_maxlen = exestack.ga_len + n; - exestack.ga_data = pp; - } - } - - // We advance by two because we add one for items referenced through - // previous_funccal. - copyID = get_copyID(); - - /* - * 1. Go through all accessible variables and mark all lists and dicts - * with copyID. - */ - - // Don't free variables in the previous_funccal list unless they are only - // referenced through previous_funccal. This must be first, because if - // the item is referenced elsewhere the funccal must not be freed. - abort = abort || set_ref_in_previous_funccal(copyID); - - // script-local variables - abort = abort || garbage_collect_scriptvars(copyID); - - // buffer-local variables - FOR_ALL_BUFFERS(buf) - abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, - NULL, NULL); - - // window-local variables - FOR_ALL_TAB_WINDOWS(tp, wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); - // window-local variables in autocmd windows - for (int i = 0; i < AUCMD_WIN_COUNT; ++i) - if (aucmd_win[i].auc_win != NULL) - abort = abort || set_ref_in_item( - &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); -#ifdef FEAT_PROP_POPUP - FOR_ALL_POPUPWINS(wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); - FOR_ALL_TABPAGES(tp) - FOR_ALL_POPUPWINS_IN_TAB(tp, wp) - abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, - NULL, NULL); -#endif - - // tabpage-local variables - FOR_ALL_TABPAGES(tp) - abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, - NULL, NULL); - // global variables - abort = abort || garbage_collect_globvars(copyID); - - // function-local variables - abort = abort || set_ref_in_call_stack(copyID); - - // named functions (matters for closures) - abort = abort || set_ref_in_functions(copyID); - - // function call arguments, if v:testing is set. - abort = abort || set_ref_in_func_args(copyID); - - // funcstacks keep variables for closures - abort = abort || set_ref_in_funcstacks(copyID); - - // loopvars keep variables for loop blocks - abort = abort || set_ref_in_loopvars(copyID); - - // v: vars - abort = abort || garbage_collect_vimvars(copyID); - - // callbacks in buffers - abort = abort || set_ref_in_buffers(copyID); - - // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks - abort = abort || set_ref_in_insexpand_funcs(copyID); - - // 'operatorfunc' callback - abort = abort || set_ref_in_opfunc(copyID); - - // 'tagfunc' callback - abort = abort || set_ref_in_tagfunc(copyID); - - // 'imactivatefunc' and 'imstatusfunc' callbacks - abort = abort || set_ref_in_im_funcs(copyID); - -#ifdef FEAT_LUA - abort = abort || set_ref_in_lua(copyID); -#endif - -#ifdef FEAT_PYTHON - abort = abort || set_ref_in_python(copyID); -#endif - -#ifdef FEAT_PYTHON3 - abort = abort || set_ref_in_python3(copyID); -#endif - -#ifdef FEAT_JOB_CHANNEL - abort = abort || set_ref_in_channel(copyID); - abort = abort || set_ref_in_job(copyID); -#endif -#ifdef FEAT_NETBEANS_INTG - abort = abort || set_ref_in_nb_channel(copyID); -#endif - -#ifdef FEAT_TIMERS - abort = abort || set_ref_in_timer(copyID); -#endif - -#ifdef FEAT_QUICKFIX - abort = abort || set_ref_in_quickfix(copyID); -#endif - -#ifdef FEAT_TERMINAL - abort = abort || set_ref_in_term(copyID); -#endif - -#ifdef FEAT_PROP_POPUP - abort = abort || set_ref_in_popups(copyID); -#endif - - abort = abort || set_ref_in_classes(copyID); + char_u *r = NULL; - if (!abort) + if (echo_style && !composite_val) { - /* - * 2. Free lists and dictionaries that are not referenced. - */ - did_free = free_unref_items(copyID); - - /* - * 3. Check if any funccal can be freed now. - * This may call us back recursively. - */ - free_unref_funccal(copyID, testing); + *tofree = NULL; + r = tv->vval.v_string; + if (r == NULL) + r = (char_u *)""; } - else if (p_verbose > 0) + else { - verb_msg(_("Not enough memory to set references, garbage collection aborted!")); + *tofree = string_quote(tv->vval.v_string, FALSE); + r = *tofree; } - return did_free; -} - -/* - * Free lists, dictionaries, channels and jobs that are no longer referenced. - */ - static int -free_unref_items(int copyID) -{ - int did_free = FALSE; - - // Let all "free" functions know that we are here. This means no - // dictionaries, lists, channels or jobs are to be freed, because we will - // do that here. - in_free_unref_items = TRUE; - - /* - * PASS 1: free the contents of the items. We don't free the items - * themselves yet, so that it is possible to decrement refcount counters - */ - - // Go through the list of dicts and free items without this copyID. - did_free |= dict_free_nonref(copyID); - - // Go through the list of lists and free items without this copyID. - did_free |= list_free_nonref(copyID); - - // Go through the list of objects and free items without this copyID. - did_free |= object_free_nonref(copyID); - - // Go through the list of classes and free items without this copyID. - did_free |= class_free_nonref(copyID); - -#ifdef FEAT_JOB_CHANNEL - // Go through the list of jobs and free items without the copyID. This - // must happen before doing channels, because jobs refer to channels, but - // the reference from the channel to the job isn't tracked. - did_free |= free_unused_jobs_contents(copyID, COPYID_MASK); - - // Go through the list of channels and free items without the copyID. - did_free |= free_unused_channels_contents(copyID, COPYID_MASK); -#endif - - /* - * PASS 2: free the items themselves. - */ - object_free_items(copyID); - dict_free_items(copyID); - list_free_items(copyID); - -#ifdef FEAT_JOB_CHANNEL - // Go through the list of jobs and free items without the copyID. This - // must happen before doing channels, because jobs refer to channels, but - // the reference from the channel to the job isn't tracked. - free_unused_jobs(copyID, COPYID_MASK); - - // Go through the list of channels and free items without the copyID. - free_unused_channels(copyID, COPYID_MASK); -#endif - - in_free_unref_items = FALSE; - - return did_free; + return r; } /* - * Mark all lists and dicts referenced through hashtab "ht" with "copyID". - * "list_stack" is used to add lists to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. + * Return a textual representation of a function in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When "echo_style" is FALSE, put quotes around the function name as + * "function()", otherwise does not put quotes around function name. + * May return NULL. */ - int -set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack) + static char_u * +func_tv2string(typval_T *tv, char_u **tofree, int echo_style) { - int todo; - int abort = FALSE; - hashitem_T *hi; - hashtab_T *cur_ht; - ht_stack_T *ht_stack = NULL; - ht_stack_T *tempitem; + char_u *r = NULL; + char_u buf[MAX_FUNC_NAME_LEN]; - cur_ht = ht; - for (;;) + if (echo_style) { - if (!abort) + *tofree = NULL; + + if (tv->vval.v_string == NULL) + r = (char_u *)"function()"; + else { - // Mark each item in the hashtab. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - todo = (int)cur_ht->ht_used; - FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo) - if (!HASHITEM_EMPTY(hi)) - { - --todo; - abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID, - &ht_stack, list_stack); - } + r = make_ufunc_name_readable(tv->vval.v_string, buf, + MAX_FUNC_NAME_LEN); + if (r == buf) + r = *tofree = vim_strsave(buf); } + } + else + { + char_u *s = NULL; - if (ht_stack == NULL) - break; + if (tv->vval.v_string != NULL) + s = make_ufunc_name_readable(tv->vval.v_string, buf, + MAX_FUNC_NAME_LEN); - // take an item from the stack - cur_ht = ht_stack->ht; - tempitem = ht_stack; - ht_stack = ht_stack->prev; - free(tempitem); + r = *tofree = string_quote(s, TRUE); } - return abort; + return r; } -#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ - || defined(PROTO) /* - * Mark a dict and its items with "copyID". - * Returns TRUE if setting references failed somehow. + * Return a textual representation of a partial in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * "numbuf" is used for a number. May return NULL. */ - int -set_ref_in_dict(dict_T *d, int copyID) + static char_u * +partial_tv2string( + typval_T *tv, + char_u **tofree, + char_u *numbuf, + int copyID) { - if (d != NULL && d->dv_copyID != copyID) - { - d->dv_copyID = copyID; - return set_ref_in_ht(&d->dv_hashtab, copyID, NULL); - } - return FALSE; -} -#endif + char_u *r = NULL; + partial_T *pt; + char_u *fname; + garray_T ga; + int i; + char_u *tf; -/* - * Mark a list and its items with "copyID". - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_list(list_T *ll, int copyID) -{ - if (ll != NULL && ll->lv_copyID != copyID) + pt = tv->vval.v_partial; + fname = string_quote(pt == NULL ? NULL : partial_name(pt), FALSE); + + ga_init2(&ga, 1, 100); + ga_concat(&ga, (char_u *)"function("); + if (fname != NULL) { - ll->lv_copyID = copyID; - return set_ref_in_list_items(ll, copyID, NULL); + // When using uf_name prepend "g:" for a global function. + if (pt != NULL && pt->pt_name == NULL && fname[0] == '\'' + && vim_isupper(fname[1])) + { + ga_concat(&ga, (char_u *)"'g:"); + ga_concat(&ga, fname + 1); + } + else + ga_concat(&ga, fname); + vim_free(fname); } - return FALSE; -} - -/* - * Mark all lists and dicts referenced through list "l" with "copyID". - * "ht_stack" is used to add hashtabs to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. - */ - int -set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack) -{ - listitem_T *li; - int abort = FALSE; - list_T *cur_l; - list_stack_T *list_stack = NULL; - list_stack_T *tempitem; - - cur_l = l; - for (;;) + if (pt != NULL && pt->pt_argc > 0) { - if (!abort && cur_l->lv_first != &range_list_item) - // Mark each item in the list. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next) - abort = abort || set_ref_in_item(&li->li_tv, copyID, - ht_stack, &list_stack); - if (list_stack == NULL) - break; - - // take an item from the stack - cur_l = list_stack->list; - tempitem = list_stack; - list_stack = list_stack->prev; - free(tempitem); + ga_concat(&ga, (char_u *)", ["); + for (i = 0; i < pt->pt_argc; ++i) + { + if (i > 0) + ga_concat(&ga, (char_u *)", "); + ga_concat(&ga, tv2string(&pt->pt_argv[i], &tf, numbuf, copyID)); + vim_free(tf); + } + ga_concat(&ga, (char_u *)"]"); } - - return abort; -} - -/* - * Mark the partial in callback 'cb' with "copyID". - */ - int -set_ref_in_callback(callback_T *cb, int copyID) -{ - typval_T tv; - - if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL) - return FALSE; - - tv.v_type = VAR_PARTIAL; - tv.vval.v_partial = cb->cb_partial; - return set_ref_in_item(&tv, copyID, NULL, NULL); -} - -/* - * Mark the dict "dd" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_dict( - dict_T *dd, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - if (dd == NULL || dd->dv_copyID == copyID) - return FALSE; - - // Didn't see this dict yet. - dd->dv_copyID = copyID; - if (ht_stack == NULL) - return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); - - ht_stack_T *newitem = ALLOC_ONE(ht_stack_T); - if (newitem == NULL) - return TRUE; - - newitem->ht = &dd->dv_hashtab; - newitem->prev = *ht_stack; - *ht_stack = newitem; - - return FALSE; -} - -/* - * Mark the list "ll" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_list( - list_T *ll, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - if (ll == NULL || ll->lv_copyID == copyID) - return FALSE; - - // Didn't see this list yet. - ll->lv_copyID = copyID; - if (list_stack == NULL) - return set_ref_in_list_items(ll, copyID, ht_stack); - - list_stack_T *newitem = ALLOC_ONE(list_stack_T); - if (newitem == NULL) - return TRUE; - - newitem->list = ll; - newitem->prev = *list_stack; - *list_stack = newitem; - - return FALSE; -} - -/* - * Mark the partial "pt" with "copyID". - * Also see set_ref_in_item(). - */ - static int -set_ref_in_item_partial( - partial_T *pt, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - if (pt == NULL || pt->pt_copyID == copyID) - return FALSE; - - // Didn't see this partial yet. - pt->pt_copyID = copyID; - - int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); - - if (pt->pt_dict != NULL) + if (pt != NULL && pt->pt_dict != NULL) { typval_T dtv; + ga_concat(&ga, (char_u *)", "); dtv.v_type = VAR_DICT; dtv.vval.v_dict = pt->pt_dict; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + ga_concat(&ga, tv2string(&dtv, &tf, numbuf, copyID)); + vim_free(tf); } + // terminate with ')' and a NUL + ga_concat_len(&ga, (char_u *)")", 2); - if (pt->pt_obj != NULL) - { - typval_T objtv; - - objtv.v_type = VAR_OBJECT; - objtv.vval.v_object = pt->pt_obj; - set_ref_in_item(&objtv, copyID, ht_stack, list_stack); - } + *tofree = ga.ga_data; + r = *tofree; - for (int i = 0; i < pt->pt_argc; ++i) - abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, - ht_stack, list_stack); - // pt_funcstack is handled in set_ref_in_funcstacks() - // pt_loopvars is handled in set_ref_in_loopvars() - - return abort; + return r; } -#ifdef FEAT_JOB_CHANNEL /* - * Mark the job "pt" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a List in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When "copyID" is not zero replace recursive lists with "...". When + * "restore_copyID" is FALSE, repeated items in lists are replaced with "...". + * May return NULL. */ - static int -set_ref_in_item_job( - job_T *job, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +list_tv2string( + typval_T *tv, + char_u **tofree, + int copyID, + int restore_copyID) { - typval_T dtv; - - if (job == NULL || job->jv_copyID == copyID) - return FALSE; + char_u *r = NULL; - job->jv_copyID = copyID; - if (job->jv_channel != NULL) + if (tv->vval.v_list == NULL) { - dtv.v_type = VAR_CHANNEL; - dtv.vval.v_channel = job->jv_channel; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + // NULL list is equivalent to empty list. + *tofree = NULL; + r = (char_u *)"[]"; } - if (job->jv_exit_cb.cb_partial != NULL) + else if (copyID != 0 && tv->vval.v_list->lv_copyID == copyID + && tv->vval.v_list->lv_len > 0) { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = job->jv_exit_cb.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + *tofree = NULL; + r = (char_u *)"[...]"; } + else + { + int old_copyID; + if (restore_copyID) + old_copyID = tv->vval.v_list->lv_copyID; - return FALSE; + tv->vval.v_list->lv_copyID = copyID; + *tofree = list2string(tv, copyID, restore_copyID); + if (restore_copyID) + tv->vval.v_list->lv_copyID = old_copyID; + r = *tofree; + } + + return r; } /* - * Mark the channel "ch" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a Dict in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * When "copyID" is not zero replace recursive dicts with "...". + * When "restore_copyID" is FALSE, repeated items in the dictionary are + * replaced with "...". May return NULL. */ - static int -set_ref_in_item_channel( - channel_T *ch, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +dict_tv2string( + typval_T *tv, + char_u **tofree, + int copyID, + int restore_copyID) { - typval_T dtv; - - if (ch == NULL || ch->ch_copyID == copyID) - return FALSE; + char_u *r = NULL; - ch->ch_copyID = copyID; - for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part) + if (tv->vval.v_dict == NULL) { - for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next; - jq != NULL; jq = jq->jq_next) - set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack); - for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL; - cq = cq->cq_next) - if (cq->cq_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = cq->cq_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - if (ch->ch_part[part].ch_callback.cb_partial != NULL) - { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } + // NULL dict is equivalent to empty dict. + *tofree = NULL; + r = (char_u *)"{}"; } - if (ch->ch_callback.cb_partial != NULL) + else if (copyID != 0 && tv->vval.v_dict->dv_copyID == copyID + && tv->vval.v_dict->dv_hashtab.ht_used != 0) { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_callback.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + *tofree = NULL; + r = (char_u *)"{...}"; } - if (ch->ch_close_cb.cb_partial != NULL) + else { - dtv.v_type = VAR_PARTIAL; - dtv.vval.v_partial = ch->ch_close_cb.cb_partial; - set_ref_in_item(&dtv, copyID, ht_stack, list_stack); - } - - return FALSE; -} -#endif + int old_copyID; + if (restore_copyID) + old_copyID = tv->vval.v_dict->dv_copyID; -/* - * Mark the class "cl" with "copyID". - * Also see set_ref_in_item(). - */ - int -set_ref_in_item_class( - class_T *cl, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) -{ - int abort = FALSE; - - if (cl == NULL || cl->class_copyID == copyID) - return FALSE; - - cl->class_copyID = copyID; - if (cl->class_members_tv != NULL) - { - // The "class_members_tv" table is allocated only for regular classes - // and not for interfaces. - for (int i = 0; !abort && i < cl->class_class_member_count; ++i) - abort = abort || set_ref_in_item( - &cl->class_members_tv[i], - copyID, ht_stack, list_stack); + tv->vval.v_dict->dv_copyID = copyID; + *tofree = dict2string(tv, copyID, restore_copyID); + if (restore_copyID) + tv->vval.v_dict->dv_copyID = old_copyID; + r = *tofree; } - for (int i = 0; !abort && i < cl->class_class_function_count; ++i) - abort = abort || set_ref_in_func(NULL, - cl->class_class_functions[i], copyID); - - for (int i = 0; !abort && i < cl->class_obj_method_count; ++i) - abort = abort || set_ref_in_func(NULL, - cl->class_obj_methods[i], copyID); - - return abort; + return r; } /* - * Mark the object "cl" with "copyID". - * Also see set_ref_in_item(). + * Return a textual representation of a job or a channel in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * "numbuf" is used for a number. + * When "composite_val" is FALSE, put quotes around strings as "string()", + * otherwise does not put quotes around strings. + * May return NULL. */ - static int -set_ref_in_item_object( - object_T *obj, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +jobchan_tv2string( + typval_T *tv, + char_u **tofree, + char_u *numbuf, + int composite_val) { - int abort = FALSE; + char_u *r = NULL; - if (obj == NULL || obj->obj_copyID == copyID) - return FALSE; +#ifdef FEAT_JOB_CHANNEL + *tofree = NULL; - obj->obj_copyID = copyID; + if (tv->v_type == VAR_JOB) + r = job_to_string_buf(tv, numbuf); + else + r = channel_to_string_buf(tv, numbuf); - // The typval_T array is right after the object_T. - typval_T *mtv = (typval_T *)(obj + 1); - for (int i = 0; !abort - && i < obj->obj_class->class_obj_member_count; ++i) - abort = abort || set_ref_in_item(mtv + i, copyID, - ht_stack, list_stack); + if (composite_val) + { + *tofree = string_quote(r, FALSE); + r = *tofree; + } +#endif - return abort; + return r; } /* - * Mark all lists, dicts and other container types referenced through typval - * "tv" with "copyID". - * "list_stack" is used to add lists to be marked. Can be NULL. - * "ht_stack" is used to add hashtabs to be marked. Can be NULL. - * - * Returns TRUE if setting references failed somehow. + * Return a textual representation of a class in "tv". + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * May return NULL. */ - int -set_ref_in_item( - typval_T *tv, - int copyID, - ht_stack_T **ht_stack, - list_stack_T **list_stack) + static char_u * +class_tv2string(typval_T *tv, char_u **tofree) { - int abort = FALSE; - - switch (tv->v_type) - { - case VAR_DICT: - return set_ref_in_item_dict(tv->vval.v_dict, copyID, - ht_stack, list_stack); - - case VAR_LIST: - return set_ref_in_item_list(tv->vval.v_list, copyID, - ht_stack, list_stack); - - case VAR_FUNC: - { - abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); - break; - } - - case VAR_PARTIAL: - return set_ref_in_item_partial(tv->vval.v_partial, copyID, - ht_stack, list_stack); - - case VAR_JOB: -#ifdef FEAT_JOB_CHANNEL - return set_ref_in_item_job(tv->vval.v_job, copyID, - ht_stack, list_stack); -#else - break; -#endif - - case VAR_CHANNEL: -#ifdef FEAT_JOB_CHANNEL - return set_ref_in_item_channel(tv->vval.v_channel, copyID, - ht_stack, list_stack); -#else - break; -#endif - - case VAR_CLASS: - return set_ref_in_item_class(tv->vval.v_class, copyID, - ht_stack, list_stack); - - case VAR_OBJECT: - return set_ref_in_item_object(tv->vval.v_object, copyID, - ht_stack, list_stack); - - case VAR_UNKNOWN: - case VAR_ANY: - case VAR_VOID: - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_NUMBER: - case VAR_FLOAT: - case VAR_STRING: - case VAR_BLOB: - case VAR_TYPEALIAS: - case VAR_INSTR: - // Types that do not contain any other item - break; - } + char_u *r = NULL; + class_T *cl = tv->vval.v_class; + char *s = "class"; + + if (cl != NULL && IS_INTERFACE(cl)) + s = "interface"; + else if (cl != NULL && IS_ENUM(cl)) + s = "enum"; + size_t len = STRLEN(s) + 1 + + (cl == NULL ? 9 : STRLEN(cl->class_name)) + 1; + r = *tofree = alloc(len); + vim_snprintf((char *)r, len, "%s %s", s, + cl == NULL ? "[unknown]" : (char *)cl->class_name); - return abort; + return r; } /* * Return a string with the string representation of a variable. * If the memory is allocated "tofree" is set to it, otherwise NULL. * "numbuf" is used for a number. - * When "copyID" is not NULL replace recursive lists and dicts with "...". + * When "copyID" is not zero replace recursive lists and dicts with "...". * When both "echo_style" and "composite_val" are FALSE, put quotes around * strings as "string()", otherwise does not put quotes around strings, as * ":echo" displays values. @@ -6221,155 +6181,27 @@ echo_string_core( switch (tv->v_type) { case VAR_STRING: - if (echo_style && !composite_val) - { - *tofree = NULL; - r = tv->vval.v_string; - if (r == NULL) - r = (char_u *)""; - } - else - { - *tofree = string_quote(tv->vval.v_string, FALSE); - r = *tofree; - } + r = string_tv2string(tv, tofree, echo_style, composite_val); break; case VAR_FUNC: - { - char_u buf[MAX_FUNC_NAME_LEN]; - - if (echo_style) - { - r = tv->vval.v_string == NULL ? (char_u *)"function()" - : make_ufunc_name_readable(tv->vval.v_string, - buf, MAX_FUNC_NAME_LEN); - if (r == buf) - { - r = vim_strsave(buf); - *tofree = r; - } - else - *tofree = NULL; - } - else - { - *tofree = string_quote(tv->vval.v_string == NULL ? NULL - : make_ufunc_name_readable( - tv->vval.v_string, buf, MAX_FUNC_NAME_LEN), - TRUE); - r = *tofree; - } - } + r = func_tv2string(tv, tofree, echo_style); break; case VAR_PARTIAL: - { - partial_T *pt = tv->vval.v_partial; - char_u *fname = string_quote(pt == NULL ? NULL - : partial_name(pt), FALSE); - garray_T ga; - int i; - char_u *tf; - - ga_init2(&ga, 1, 100); - ga_concat(&ga, (char_u *)"function("); - if (fname != NULL) - { - // When using uf_name prepend "g:" for a global function. - if (pt != NULL && pt->pt_name == NULL && fname[0] == '\'' - && vim_isupper(fname[1])) - { - ga_concat(&ga, (char_u *)"'g:"); - ga_concat(&ga, fname + 1); - } - else - ga_concat(&ga, fname); - vim_free(fname); - } - if (pt != NULL && pt->pt_argc > 0) - { - ga_concat(&ga, (char_u *)", ["); - for (i = 0; i < pt->pt_argc; ++i) - { - if (i > 0) - ga_concat(&ga, (char_u *)", "); - ga_concat(&ga, - tv2string(&pt->pt_argv[i], &tf, numbuf, copyID)); - vim_free(tf); - } - ga_concat(&ga, (char_u *)"]"); - } - if (pt != NULL && pt->pt_dict != NULL) - { - typval_T dtv; - - ga_concat(&ga, (char_u *)", "); - dtv.v_type = VAR_DICT; - dtv.vval.v_dict = pt->pt_dict; - ga_concat(&ga, tv2string(&dtv, &tf, numbuf, copyID)); - vim_free(tf); - } - // terminate with ')' and a NUL - ga_concat_len(&ga, (char_u *)")", 2); - - *tofree = ga.ga_data; - r = *tofree; - break; - } + r = partial_tv2string(tv, tofree, numbuf, copyID); + break; case VAR_BLOB: r = blob2string(tv->vval.v_blob, tofree, numbuf); break; case VAR_LIST: - if (tv->vval.v_list == NULL) - { - // NULL list is equivalent to empty list. - *tofree = NULL; - r = (char_u *)"[]"; - } - else if (copyID != 0 && tv->vval.v_list->lv_copyID == copyID - && tv->vval.v_list->lv_len > 0) - { - *tofree = NULL; - r = (char_u *)"[...]"; - } - else - { - int old_copyID = tv->vval.v_list->lv_copyID; - - tv->vval.v_list->lv_copyID = copyID; - *tofree = list2string(tv, copyID, restore_copyID); - if (restore_copyID) - tv->vval.v_list->lv_copyID = old_copyID; - r = *tofree; - } + r = list_tv2string(tv, tofree, copyID, restore_copyID); break; case VAR_DICT: - if (tv->vval.v_dict == NULL) - { - // NULL dict is equivalent to empty dict. - *tofree = NULL; - r = (char_u *)"{}"; - } - else if (copyID != 0 && tv->vval.v_dict->dv_copyID == copyID - && tv->vval.v_dict->dv_hashtab.ht_used != 0) - { - *tofree = NULL; - r = (char_u *)"{...}"; - } - else - { - int old_copyID = tv->vval.v_dict->dv_copyID; - - tv->vval.v_dict->dv_copyID = copyID; - *tofree = dict2string(tv, copyID, restore_copyID); - if (restore_copyID) - tv->vval.v_dict->dv_copyID = old_copyID; - r = *tofree; - } + r = dict_tv2string(tv, tofree, copyID, restore_copyID); break; case VAR_NUMBER: @@ -6382,16 +6214,7 @@ echo_string_core( case VAR_JOB: case VAR_CHANNEL: -#ifdef FEAT_JOB_CHANNEL - *tofree = NULL; - r = tv->v_type == VAR_JOB ? job_to_string_buf(tv, numbuf) - : channel_to_string_buf(tv, numbuf); - if (composite_val) - { - *tofree = string_quote(r, FALSE); - r = *tofree; - } -#endif + r = jobchan_tv2string(tv, tofree, numbuf, composite_val); break; case VAR_INSTR: @@ -6400,23 +6223,11 @@ echo_string_core( break; case VAR_CLASS: - { - class_T *cl = tv->vval.v_class; - char *s = "class"; - if (IS_INTERFACE(cl)) - s = "interface"; - else if (IS_ENUM(cl)) - s = "enum"; - size_t len = STRLEN(s) + 1 + - (cl == NULL ? 9 : STRLEN(cl->class_name)) + 1; - r = *tofree = alloc(len); - vim_snprintf((char *)r, len, "%s %s", s, - cl == NULL ? "[unknown]" : (char *)cl->class_name); - } + r = class_tv2string(tv, tofree); break; case VAR_OBJECT: - *tofree = r = object_string(tv->vval.v_object, numbuf, copyID, + *tofree = r = object2string(tv->vval.v_object, numbuf, copyID, echo_style, restore_copyID, composite_val); break; @@ -6451,7 +6262,7 @@ echo_string_core( * If the memory is allocated "tofree" is set to it, otherwise NULL. * "numbuf" is used for a number. * Does not put quotes around strings, as ":echo" displays values. - * When "copyID" is not NULL replace recursive lists and dicts with "...". + * When "copyID" is not zero replace recursive lists and dicts with "...". * May return NULL. */ char_u * diff --git a/src/evalfunc.c b/src/evalfunc.c index 2064982..9720691 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -73,6 +73,7 @@ static void f_getpos(typval_T *argvars, typval_T *rettv); static void f_getreg(typval_T *argvars, typval_T *rettv); static void f_getreginfo(typval_T *argvars, typval_T *rettv); static void f_getregion(typval_T *argvars, typval_T *rettv); +static void f_getregionpos(typval_T *argvars, typval_T *rettv); static void f_getregtype(typval_T *argvars, typval_T *rettv); static void f_gettagstack(typval_T *argvars, typval_T *rettv); static void f_gettext(typval_T *argvars, typval_T *rettv); @@ -2006,6 +2007,8 @@ static funcentry_T global_functions[] = ret_void, f_feedkeys}, {"file_readable", 1, 1, FEARG_1, arg1_string, // obsolete ret_number_bool, f_filereadable}, + {"filecopy", 2, 2, FEARG_1, arg2_string, + ret_number_bool, f_filecopy}, {"filereadable", 1, 1, FEARG_1, arg1_string, ret_number_bool, f_filereadable}, {"filewritable", 1, 1, FEARG_1, arg1_string, @@ -2136,6 +2139,8 @@ static funcentry_T global_functions[] = ret_dict_any, f_getreginfo}, {"getregion", 2, 3, FEARG_1, arg3_list_list_dict, ret_list_string, f_getregion}, + {"getregionpos", 2, 3, FEARG_1, arg3_list_list_dict, + ret_list_string, f_getregionpos}, {"getregtype", 0, 1, FEARG_1, arg1_string, ret_string, f_getregtype}, {"getscriptinfo", 0, 1, 0, arg1_dict_any, @@ -3840,8 +3845,9 @@ set_cursorpos(typval_T *argvars, typval_T *rettv, int charcol) return; // type error; errmsg already given if (lnum > 0) curwin->w_cursor.lnum = lnum; - if (col > 0) - curwin->w_cursor.col = col - 1; + if (col != MAXCOL && --col < 0) + col = 0; + curwin->w_cursor.col = col; curwin->w_cursor.coladd = coladd; // Make sure the cursor is in a valid position. @@ -3958,7 +3964,7 @@ f_empty(typval_T *argvars, typval_T *rettv) || *argvars[0].vval.v_string == NUL; break; case VAR_PARTIAL: - n = FALSE; + n = argvars[0].vval.v_partial == NULL; break; case VAR_NUMBER: n = argvars[0].vval.v_number == 0; @@ -4454,7 +4460,7 @@ f_exists_compiled(typval_T *argvars UNUSED, typval_T *rettv UNUSED) f_expand(typval_T *argvars, typval_T *rettv) { char_u *s; - int len; + size_t len; int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; expand_T xpc; int error = FALSE; @@ -5460,7 +5466,7 @@ f_getpos(typval_T *argvars, typval_T *rettv) /* * Convert from block_def to string */ - static char_u * + static char_u * block_def2str(struct block_def *bd) { char_u *p, *ret; @@ -5480,40 +5486,36 @@ block_def2str(struct block_def *bd) return ret; } -/* - * "getregion()" function - */ - static void -f_getregion(typval_T *argvars, typval_T *rettv) -{ - linenr_T lnum; - oparg_T oa; - struct block_def bd; - char_u *akt = NULL; - int inclusive = TRUE; - int fnum1 = -1, fnum2 = -1; - pos_T p1, p2; - char_u *type; - buf_T *save_curbuf; - buf_T *findbuf; - char_u default_type[] = "v"; - int save_virtual; - int l; - int region_type = -1; - int is_select_exclusive; + static int +getregionpos( + typval_T *argvars, + typval_T *rettv, + pos_T *p1, + pos_T *p2, + int *inclusive, + int *region_type, + oparg_T *oap) +{ + int fnum1 = -1, fnum2 = -1; + char_u *type; + buf_T *findbuf; + char_u default_type[] = "v"; + int block_width = 0; + int is_select_exclusive; + int l; if (rettv_list_alloc(rettv) == FAIL) - return; + return FAIL; if (check_for_list_arg(argvars, 0) == FAIL || check_for_list_arg(argvars, 1) == FAIL || check_for_opt_dict_arg(argvars, 2) == FAIL) - return; + return FAIL; - if (list2fpos(&argvars[0], &p1, &fnum1, NULL, FALSE) != OK - || list2fpos(&argvars[1], &p2, &fnum2, NULL, FALSE) != OK + if (list2fpos(&argvars[0], p1, &fnum1, NULL, FALSE) != OK + || list2fpos(&argvars[1], p2, &fnum2, NULL, FALSE) != OK || fnum1 != fnum2) - return; + return FAIL; if (argvars[2].v_type == VAR_DICT) { @@ -5531,120 +5533,142 @@ f_getregion(typval_T *argvars, typval_T *rettv) } if (type[0] == 'v' && type[1] == NUL) - region_type = MCHAR; + *region_type = MCHAR; else if (type[0] == 'V' && type[1] == NUL) - region_type = MLINE; - else if (type[0] == Ctrl_V && type[1] == NUL) - region_type = MBLOCK; + *region_type = MLINE; + else if (type[0] == Ctrl_V) + { + char_u *p = type + 1; + + if (*p != NUL && ((block_width = getdigits(&p)) <= 0 || *p != NUL)) + { + semsg(_(e_invalid_value_for_argument_str_str), "type", type); + return FAIL; + } + *region_type = MBLOCK; + } else { semsg(_(e_invalid_value_for_argument_str_str), "type", type); - return; + return FAIL; } findbuf = fnum1 != 0 ? buflist_findnr(fnum1) : curbuf; if (findbuf == NULL || findbuf->b_ml.ml_mfp == NULL) { emsg(_(e_buffer_is_not_loaded)); - return; + return FAIL; } - if (p1.lnum < 1 || p1.lnum > findbuf->b_ml.ml_line_count) + if (p1->lnum < 1 || p1->lnum > findbuf->b_ml.ml_line_count) { - semsg(_(e_invalid_line_number_nr), p1.lnum); - return; + semsg(_(e_invalid_line_number_nr), p1->lnum); + return FAIL; } - if (p1.col < 1 || p1.col > ml_get_buf_len(findbuf, p1.lnum) + 1) + if (p1->col == MAXCOL) + p1->col = ml_get_buf_len(findbuf, p1->lnum) + 1; + else if (p1->col < 1 || p1->col > ml_get_buf_len(findbuf, p1->lnum) + 1) { - semsg(_(e_invalid_column_number_nr), p1.col); - return; + semsg(_(e_invalid_column_number_nr), p1->col); + return FAIL; } - if (p2.lnum < 1 || p2.lnum > findbuf->b_ml.ml_line_count) + + if (p2->lnum < 1 || p2->lnum > findbuf->b_ml.ml_line_count) { - semsg(_(e_invalid_line_number_nr), p2.lnum); - return; + semsg(_(e_invalid_line_number_nr), p2->lnum); + return FAIL; } - if (p2.col < 1 || p2.col > ml_get_buf_len(findbuf, p2.lnum) + 1) + if (p2->col == MAXCOL) + p2->col = ml_get_buf_len(findbuf, p2->lnum) + 1; + else if (p2->col < 1 || p2->col > ml_get_buf_len(findbuf, p2->lnum) + 1) { - semsg(_(e_invalid_column_number_nr), p2.col); - return; + semsg(_(e_invalid_column_number_nr), p2->col); + return FAIL; } - save_curbuf = curbuf; curbuf = findbuf; curwin->w_buffer = curbuf; - save_virtual = virtual_op; virtual_op = virtual_active(); - // NOTE: Adjust is needed. - p1.col--; - p2.col--; + // NOTE: Adjustment is needed. + p1->col--; + p2->col--; - if (!LT_POS(p1, p2)) + if (!LT_POS(*p1, *p2)) { // swap position pos_T p; - p = p1; - p1 = p2; - p2 = p; + p = *p1; + *p1 = *p2; + *p2 = p; } - if (region_type == MCHAR) + if (*region_type == MCHAR) { - // handle 'selection' == "exclusive" - if (is_select_exclusive && !EQUAL_POS(p1, p2)) - { - if (p2.coladd > 0) - p2.coladd--; - else if (p2.col > 0) - { - p2.col--; - - mb_adjustpos(curbuf, &p2); - } - else if (p2.lnum > 1) - { - p2.lnum--; - p2.col = ml_get_len(p2.lnum); - if (p2.col > 0) - { - p2.col--; - - mb_adjustpos(curbuf, &p2); - } - } - } - // if fp2 is on NUL (empty line) inclusive becomes false - if (*ml_get_pos(&p2) == NUL && !virtual_op) - inclusive = FALSE; + // Handle 'selection' == "exclusive". + if (is_select_exclusive && !EQUAL_POS(*p1, *p2)) + // When backing up to previous line, inclusive becomes false. + *inclusive = !unadjust_for_sel_inner(p2); + // If p2 is on NUL (end of line), inclusive becomes false. + if (*inclusive && !virtual_op && *ml_get_pos(p2) == NUL) + *inclusive = FALSE; } - else if (region_type == MBLOCK) + else if (*region_type == MBLOCK) { colnr_T sc1, ec1, sc2, ec2; - getvvcol(curwin, &p1, &sc1, NULL, &ec1); - getvvcol(curwin, &p2, &sc2, NULL, &ec2); - oa.motion_type = MBLOCK; - oa.inclusive = TRUE; - oa.op_type = OP_NOP; - oa.start = p1; - oa.end = p2; - oa.start_vcol = MIN(sc1, sc2); - if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1) - oa.end_vcol = sc2 - 1; + getvvcol(curwin, p1, &sc1, NULL, &ec1); + getvvcol(curwin, p2, &sc2, NULL, &ec2); + oap->motion_type = MBLOCK; + oap->inclusive = TRUE; + oap->op_type = OP_NOP; + oap->start = *p1; + oap->end = *p2; + oap->start_vcol = MIN(sc1, sc2); + if (block_width > 0) + oap->end_vcol = oap->start_vcol + block_width - 1; + else if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1) + oap->end_vcol = sc2 - 1; else - oa.end_vcol = MAX(ec1, ec2); + oap->end_vcol = MAX(ec1, ec2); } // Include the trailing byte of a multi-byte char. - l = utfc_ptr2len((char_u *)ml_get_pos(&p2)); + l = mb_ptr2len((char_u *)ml_get_pos(p2)); if (l > 1) - p2.col += l - 1; + p2->col += l - 1; + + return OK; +} + +/* + * "getregion()" function + */ + static void +f_getregion(typval_T *argvars, typval_T *rettv) +{ + pos_T p1, p2; + int inclusive = TRUE; + int region_type = -1; + oparg_T oa; + + buf_T *save_curbuf; + int save_virtual; + char_u *akt = NULL; + linenr_T lnum; + + save_curbuf = curbuf; + save_virtual = virtual_op; + + if (getregionpos(argvars, rettv, + &p1, &p2, &inclusive, ®ion_type, &oa) == FAIL) + return; for (lnum = p1.lnum; lnum <= p2.lnum; lnum++) { int ret = 0; + struct block_def bd; if (region_type == MLINE) akt = vim_strsave(ml_get(lnum)); @@ -5675,6 +5699,187 @@ f_getregion(typval_T *argvars, typval_T *rettv) } } + // getregionpos() may change curbuf and virtual_op + curbuf = save_curbuf; + curwin->w_buffer = curbuf; + virtual_op = save_virtual; +} + + static void +add_regionpos_range(typval_T *rettv, pos_T p1, pos_T p2) +{ + list_T *l1, *l2, *l3; + + l1 = list_alloc(); + if (l1 == NULL) + return; + + if (list_append_list(rettv->vval.v_list, l1) == FAIL) + { + vim_free(l1); + return; + } + + l2 = list_alloc(); + if (l2 == NULL) + { + vim_free(l1); + return; + } + + if (list_append_list(l1, l2) == FAIL) + { + vim_free(l1); + vim_free(l2); + return; + } + + l3 = list_alloc(); + if (l3 == NULL) + { + vim_free(l1); + vim_free(l2); + return; + } + + if (list_append_list(l1, l3) == FAIL) + { + vim_free(l1); + vim_free(l2); + vim_free(l3); + return; + } + + list_append_number(l2, curbuf->b_fnum); + list_append_number(l2, p1.lnum); + list_append_number(l2, p1.col); + list_append_number(l2, p1.coladd); + + list_append_number(l3, curbuf->b_fnum); + list_append_number(l3, p2.lnum); + list_append_number(l3, p2.col); + list_append_number(l3, p2.coladd); +} + +/* + * "getregionpos()" function + */ + static void +f_getregionpos(typval_T *argvars, typval_T *rettv) +{ + pos_T p1, p2; + int inclusive = TRUE; + int region_type = -1; + int allow_eol = FALSE; + oparg_T oa; + int lnum; + + buf_T *save_curbuf; + int save_virtual; + + save_curbuf = curbuf; + save_virtual = virtual_op; + + if (getregionpos(argvars, rettv, + &p1, &p2, &inclusive, ®ion_type, &oa) == FAIL) + return; + + if (argvars[2].v_type == VAR_DICT) + allow_eol = dict_get_bool(argvars[2].vval.v_dict, "eol", FALSE); + + for (lnum = p1.lnum; lnum <= p2.lnum; lnum++) + { + pos_T ret_p1, ret_p2; + char_u *line = ml_get(lnum); + colnr_T line_len = ml_get_len(lnum); + + if (region_type == MLINE) + { + ret_p1.col = 1; + ret_p1.coladd = 0; + ret_p2.col = MAXCOL; + ret_p2.coladd = 0; + } + else + { + struct block_def bd; + + if (region_type == MBLOCK) + block_prep(&oa, &bd, lnum, FALSE); + else + charwise_block_prep(p1, p2, &bd, lnum, inclusive); + + if (bd.is_oneChar) // selection entirely inside one char + { + if (region_type == MBLOCK) + { + ret_p1.col = mb_prevptr(line, bd.textstart) - line + 1; + ret_p1.coladd = bd.start_char_vcols + - (bd.start_vcol - oa.start_vcol); + } + else + { + ret_p1.col = p1.col + 1; + ret_p1.coladd = p1.coladd; + } + } + else if (region_type == MBLOCK && oa.start_vcol > bd.start_vcol) + { + // blockwise selection entirely beyond end of line + ret_p1.col = MAXCOL; + ret_p1.coladd = oa.start_vcol - bd.start_vcol; + bd.is_oneChar = TRUE; + } + else if (bd.startspaces > 0) + { + ret_p1.col = mb_prevptr(line, bd.textstart) - line + 1; + ret_p1.coladd = bd.start_char_vcols - bd.startspaces; + } + else + { + ret_p1.col = bd.textcol + 1; + ret_p1.coladd = 0; + } + + if (bd.is_oneChar) // selection entirely inside one char + { + ret_p2.col = ret_p1.col; + ret_p2.coladd = ret_p1.coladd + bd.startspaces + bd.endspaces; + } + else if (bd.endspaces > 0) + { + ret_p2.col = bd.textcol + bd.textlen + 1; + ret_p2.coladd = bd.endspaces; + } + else + { + ret_p2.col = bd.textcol + bd.textlen; + ret_p2.coladd = 0; + } + } + + if (!allow_eol && ret_p1.col > line_len) + { + ret_p1.col = 0; + ret_p1.coladd = 0; + } + else if (ret_p1.col > line_len + 1) + ret_p1.col = line_len + 1; + + if (!allow_eol && ret_p2.col > line_len) + { + ret_p2.col = ret_p1.col == 0 ? 0 : line_len; + ret_p2.coladd = 0; + } + else if (ret_p2.col > line_len + 1) + ret_p2.col = line_len + 1; + + ret_p1.lnum = lnum; + ret_p2.lnum = lnum; + add_regionpos_range(rettv, ret_p1, ret_p2); + } + + // getregionpos() may change curbuf and virtual_op curbuf = save_curbuf; curwin->w_buffer = curbuf; virtual_op = save_virtual; @@ -7368,6 +7573,7 @@ indexof_blob(blob_T *b, long startidx, typval_T *expr) set_vim_var_type(VV_KEY, VAR_NUMBER); set_vim_var_type(VV_VAL, VAR_NUMBER); + int called_emsg_start = called_emsg; for (idx = startidx; idx < blob_len(b); ++idx) { set_vim_var_nr(VV_KEY, idx); @@ -7375,6 +7581,9 @@ indexof_blob(blob_T *b, long startidx, typval_T *expr) if (indexof_eval_expr(expr)) return idx; + + if (called_emsg != called_emsg_start) + return -1; } return -1; @@ -7410,6 +7619,7 @@ indexof_list(list_T *l, long startidx, typval_T *expr) set_vim_var_type(VV_KEY, VAR_NUMBER); + int called_emsg_start = called_emsg; for ( ; item != NULL; item = item->li_next, ++idx) { set_vim_var_nr(VV_KEY, idx); @@ -7420,6 +7630,9 @@ indexof_list(list_T *l, long startidx, typval_T *expr) if (found) return idx; + + if (called_emsg != called_emsg_start) + return -1; } return -1; @@ -7443,7 +7656,9 @@ f_indexof(typval_T *argvars, typval_T *rettv) || check_for_opt_dict_arg(argvars, 2) == FAIL) return; - if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL) + if ((argvars[1].v_type == VAR_STRING && + (argvars[1].vval.v_string == NULL + || *argvars[1].vval.v_string == NUL)) || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) return; @@ -9540,6 +9755,7 @@ search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { int flags; char_u *pat; + size_t patlen; pos_T pos; pos_T save_cursor; int save_p_ws = p_ws; @@ -9614,10 +9830,12 @@ search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) sia.sa_tm = time_limit; #endif + patlen = STRLEN(pat); + // Repeat until {skip} returns FALSE. for (;;) { - subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, pat, patlen, 1L, options, RE_SEARCH, &sia); // finding the first match again means there is no match where {skip} // evaluates to zero. @@ -10030,6 +10248,13 @@ do_searchpair( { char_u *save_cpo; char_u *pat, *pat2 = NULL, *pat3 = NULL; + size_t patlen; + size_t spatlen; + size_t epatlen; + size_t pat2size; + size_t pat2len; + size_t pat3size; + size_t pat3len; long retval = 0; pos_T pos; pos_T firstpos; @@ -10049,15 +10274,24 @@ do_searchpair( // Make two search patterns: start/end (pat2, for in nested pairs) and // start/middle/end (pat3, for the top pair). - pat2 = alloc(STRLEN(spat) + STRLEN(epat) + 17); - pat3 = alloc(STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25); - if (pat2 == NULL || pat3 == NULL) + spatlen = STRLEN(spat); + epatlen = STRLEN(epat); + pat2size = spatlen + epatlen + 17; + pat2 = alloc(pat2size); + if (pat2 == NULL) + goto theend; + pat3size = spatlen + STRLEN(mpat) + epatlen + 25; + pat3 = alloc(pat3size); + if (pat3 == NULL) goto theend; - sprintf((char *)pat2, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); + pat2len = vim_snprintf((char *)pat2, pat2size, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); if (*mpat == NUL) + { STRCPY(pat3, pat2); + pat3len = pat2len; + } else - sprintf((char *)pat3, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", + pat3len = vim_snprintf((char *)pat3, pat3size, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); if (flags & SP_START) options |= SEARCH_START; @@ -10074,13 +10308,14 @@ do_searchpair( CLEAR_POS(&firstpos); CLEAR_POS(&foundpos); pat = pat3; + patlen = pat3len; for (;;) { searchit_arg_T sia; CLEAR_FIELD(sia); sia.sa_stop_lnum = lnum_stop; - n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, + n = searchit(curwin, curbuf, &pos, NULL, dir, pat, patlen, 1L, options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && EQUAL_POS(pos, firstpos))) // didn't find it or found the first match again: FAIL @@ -10715,7 +10950,7 @@ f_shiftwidth(typval_T *argvars UNUSED, typval_T *rettv) if (col < 0) return; // type error; errmsg already given #ifdef FEAT_VARTABS - rettv->vval.v_number = get_sw_value_col(curbuf, col); + rettv->vval.v_number = get_sw_value_col(curbuf, col, FALSE); return; #endif } @@ -10784,7 +11019,7 @@ f_spellbadword(typval_T *argvars UNUSED, typval_T *rettv) if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. - len = spell_move_to(curwin, FORWARD, TRUE, TRUE, &attr); + len = spell_move_to(curwin, FORWARD, SMT_ALL, TRUE, &attr); if (len != 0) { word = ml_get_cursor(); @@ -11497,7 +11732,7 @@ f_type(typval_T *argvars, typval_T *rettv) case VAR_CLASS: { class_T *cl = argvars[0].vval.v_class; - if (IS_ENUM(cl)) + if (cl != NULL && IS_ENUM(cl)) n = VAR_TYPE_ENUM; else n = VAR_TYPE_CLASS; @@ -11505,11 +11740,18 @@ f_type(typval_T *argvars, typval_T *rettv) } case VAR_OBJECT: { - class_T *cl = argvars[0].vval.v_object->obj_class; - if (IS_ENUM(cl)) - n = VAR_TYPE_ENUMVALUE; - else + object_T *obj = argvars[0].vval.v_object; + + if (obj == NULL) n = VAR_TYPE_OBJECT; + else + { + class_T *cl = argvars[0].vval.v_object->obj_class; + if (IS_ENUM(cl)) + n = VAR_TYPE_ENUMVALUE; + else + n = VAR_TYPE_OBJECT; + } break; } case VAR_UNKNOWN: diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 2a5d842..8143c24 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -3750,6 +3750,7 @@ ex_substitute(exarg_T *eap) int save_do_all; // remember user specified 'g' flag int save_do_ask; // remember user specified 'c' flag char_u *pat = NULL, *sub = NULL; // init for GCC + size_t patlen = 0; int delimiter; int sublen; int got_quit = FALSE; @@ -3823,6 +3824,7 @@ ex_substitute(exarg_T *eap) if (*cmd != '&') which_pat = RE_SEARCH; // use last '/' pattern pat = (char_u *)""; // empty search pattern + patlen = 0; delimiter = *cmd++; // remember delimiter character } else // find the end of the regexp @@ -3830,6 +3832,7 @@ ex_substitute(exarg_T *eap) which_pat = RE_LAST; // use last used regexp delimiter = *cmd++; // remember delimiter character pat = cmd; // remember start of search pat + patlen = STRLEN(pat); cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), &eap->arg, NULL, NULL); if (cmd[0] == delimiter) // end delimiter found @@ -3883,6 +3886,7 @@ ex_substitute(exarg_T *eap) return; } pat = NULL; // search_regcomp() will use previous pattern + patlen = 0; sub = vim_strsave(old_sub); // Vi compatibility quirk: repeating with ":s" keeps the cursor in the @@ -3929,9 +3933,9 @@ ex_substitute(exarg_T *eap) } if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) - save_re_pat(RE_SUBST, pat, magic_isset()); + save_re_pat(RE_SUBST, pat, patlen, magic_isset()); // put pattern in history - add_to_history(HIST_SEARCH, pat, TRUE, NUL); + add_to_history(HIST_SEARCH, pat, patlen, TRUE, NUL); vim_free(sub); return; @@ -4066,7 +4070,7 @@ ex_substitute(exarg_T *eap) return; } - if (search_regcomp(pat, NULL, RE_SUBST, which_pat, SEARCH_HIS, ®match) == FAIL) + if (search_regcomp(pat, patlen, NULL, RE_SUBST, which_pat, SEARCH_HIS, ®match) == FAIL) { if (subflags.do_error) emsg(_(e_invalid_command)); @@ -5104,6 +5108,7 @@ ex_global(exarg_T *eap) char_u delim; // delimiter, normally '/' char_u *pat; + size_t patlen; char_u *used_pat; regmmatch_T regmatch; int match; @@ -5150,6 +5155,7 @@ ex_global(exarg_T *eap) which_pat = RE_SEARCH; // use previous search pattern ++cmd; pat = (char_u *)""; + patlen = 0; } else if (*cmd == NUL) { @@ -5165,12 +5171,13 @@ ex_global(exarg_T *eap) delim = *cmd; // get the delimiter ++cmd; // skip delimiter if there is one pat = cmd; // remember start of pattern + patlen = STRLEN(pat); cmd = skip_regexp_ex(cmd, delim, magic_isset(), &eap->arg, NULL, NULL); if (cmd[0] == delim) // end delimiter found *cmd++ = NUL; // replace it with a NUL } - if (search_regcomp(pat, &used_pat, RE_BOTH, which_pat, SEARCH_HIS, + if (search_regcomp(pat, patlen, &used_pat, RE_BOTH, which_pat, SEARCH_HIS, ®match) == FAIL) { emsg(_(e_invalid_command)); @@ -5622,6 +5629,9 @@ ex_oldfiles(exarg_T *eap UNUSED) listitem_T *li; int nr = 0; char_u *fname; + // for a single filtered match, remember the number + // so we can jump directly to it without prompting + int matches = -1; if (l == NULL) { @@ -5637,6 +5647,10 @@ ex_oldfiles(exarg_T *eap UNUSED) fname = tv_get_string(&li->li_tv); if (!message_filtered(fname)) { + if (matches < 0) + matches = nr; + else + matches = 0; msg_outnum((long)nr); msg_puts(": "); msg_outtrans(fname); @@ -5654,7 +5668,15 @@ ex_oldfiles(exarg_T *eap UNUSED) if (cmdmod.cmod_flags & CMOD_BROWSE) { quit_more = FALSE; - nr = prompt_for_number(FALSE); + // we only need to prompt if there is more than 1 match + if (matches > 0) + { + nr = matches; + // msg_putchar above sets needs_wait_return + need_wait_return = FALSE; + } + else + nr = prompt_for_number(FALSE); msg_starthere(); if (nr > 0) { diff --git a/src/ex_cmds.h b/src/ex_cmds.h index 40dec4c..25f6914 100644 --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -271,7 +271,7 @@ EXCMD(CMD_cabove, "cabove", ex_cbelow, ADDR_UNSIGNED), EXCMD(CMD_caddbuffer, "caddbuffer", ex_cbuffer, EX_RANGE|EX_WORD1|EX_TRLBAR, - ADDR_OTHER), + ADDR_LINES), EXCMD(CMD_caddexpr, "caddexpr", ex_cexpr, EX_NEEDARG|EX_WORD1|EX_NOTRLCOM|EX_EXPR_ARG, ADDR_NONE), @@ -289,7 +289,7 @@ EXCMD(CMD_catch, "catch", ex_catch, ADDR_NONE), EXCMD(CMD_cbuffer, "cbuffer", ex_cbuffer, EX_BANG|EX_RANGE|EX_WORD1|EX_TRLBAR, - ADDR_OTHER), + ADDR_LINES), EXCMD(CMD_cbefore, "cbefore", ex_cbelow, EX_RANGE|EX_COUNT|EX_TRLBAR, ADDR_UNSIGNED), @@ -331,7 +331,7 @@ EXCMD(CMD_cgetfile, "cgetfile", ex_cfile, ADDR_NONE), EXCMD(CMD_cgetbuffer, "cgetbuffer", ex_cbuffer, EX_RANGE|EX_WORD1|EX_TRLBAR, - ADDR_OTHER), + ADDR_LINES), EXCMD(CMD_cgetexpr, "cgetexpr", ex_cexpr, EX_NEEDARG|EX_WORD1|EX_NOTRLCOM|EX_EXPR_ARG, ADDR_NONE), @@ -820,7 +820,7 @@ EXCMD(CMD_laddexpr, "laddexpr", ex_cexpr, ADDR_NONE), EXCMD(CMD_laddbuffer, "laddbuffer", ex_cbuffer, EX_RANGE|EX_WORD1|EX_TRLBAR, - ADDR_OTHER), + ADDR_LINES), EXCMD(CMD_laddfile, "laddfile", ex_cfile, EX_TRLBAR|EX_FILE1, ADDR_NONE), @@ -832,7 +832,7 @@ EXCMD(CMD_later, "later", ex_later, ADDR_NONE), EXCMD(CMD_lbuffer, "lbuffer", ex_cbuffer, EX_BANG|EX_RANGE|EX_WORD1|EX_TRLBAR, - ADDR_OTHER), + ADDR_LINES), EXCMD(CMD_lbefore, "lbefore", ex_cbelow, EX_RANGE|EX_COUNT|EX_TRLBAR, ADDR_UNSIGNED), @@ -886,7 +886,7 @@ EXCMD(CMD_lgetfile, "lgetfile", ex_cfile, ADDR_NONE), EXCMD(CMD_lgetbuffer, "lgetbuffer", ex_cbuffer, EX_RANGE|EX_WORD1|EX_TRLBAR, - ADDR_OTHER), + ADDR_LINES), EXCMD(CMD_lgetexpr, "lgetexpr", ex_cexpr, EX_NEEDARG|EX_WORD1|EX_NOTRLCOM|EX_EXPR_ARG, ADDR_NONE), diff --git a/src/ex_docmd.c b/src/ex_docmd.c index a588f26..71bfa93 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -4567,7 +4567,7 @@ get_address( curwin->w_cursor.col = 0; searchcmdlen = 0; flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; - if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) + if (!do_search(NULL, c, c, cmd, STRLEN(cmd), 1L, flags, NULL)) { curwin->w_cursor = pos; cmd = NULL; @@ -4621,7 +4621,7 @@ get_address( pos.coladd = 0; if (searchit(curwin, curbuf, &pos, NULL, *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) + (char_u *)"", 0, 1L, SEARCH_MSG, i, NULL) != FAIL) lnum = pos.lnum; else { @@ -5077,7 +5077,7 @@ expand_filename( { int has_wildcards; // need to expand wildcards char_u *repl; - int srclen; + size_t srclen; char_u *p; int n; int escaped; @@ -5201,7 +5201,7 @@ expand_filename( } } - p = repl_cmdline(eap, p, (size_t)srclen, repl, cmdlinep); + p = repl_cmdline(eap, p, srclen, repl, cmdlinep); vim_free(repl); if (p == NULL) return FAIL; @@ -9363,7 +9363,7 @@ enum { * the variable. Otherwise return -1 and "*usedlen" is unchanged. */ int -find_cmdline_var(char_u *src, int *usedlen) +find_cmdline_var(char_u *src, size_t *usedlen) { // must be sorted by the 'value' field because it is used by bsearch()! static keyvalue_T spec_str_tab[] = { @@ -9444,7 +9444,7 @@ find_cmdline_var(char_u *src, int *usedlen) eval_vars( char_u *src, // pointer into commandline char_u *srcstart, // beginning of valid memory for src - int *usedlen, // characters after src that are used + size_t *usedlen, // characters after src that are used linenr_T *lnump, // line number for :e command, or NULL char **errormsg, // pointer to error message int *escaped, // return value has escaped white space (can @@ -9514,7 +9514,7 @@ eval_vars( */ else { - int off = 0; + size_t off = 0; switch (spec_idx) { @@ -9781,7 +9781,7 @@ expand_sfile(char_u *arg) size_t len; char_u *repl; size_t repllen; - int srclen; + size_t srclen; char_u *p; resultlen = STRLEN(arg); diff --git a/src/ex_getln.c b/src/ex_getln.c index 1731d29..3ae4958 100644 --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -493,7 +493,7 @@ may_do_incsearch_highlighting( sia.sa_tm = 500; #endif found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim, - ccline.cmdbuff + skiplen, count, search_flags, + ccline.cmdbuff + skiplen, patlen, count, search_flags, #ifdef FEAT_RELTIME &sia #else @@ -654,7 +654,7 @@ may_adjust_incsearch_highlighting( pat[patlen] = NUL; i = searchit(curwin, curbuf, &t, NULL, c == Ctrl_G ? FORWARD : BACKWARD, - pat, count, search_flags, RE_SEARCH, NULL); + pat, patlen, count, search_flags, RE_SEARCH, NULL); --emsg_off; pat[patlen] = save; if (i) @@ -2539,12 +2539,14 @@ returncmd: if (ccline.cmdlen && firstc != NUL && (some_key_typed || histype == HIST_SEARCH)) { - add_to_history(histype, ccline.cmdbuff, TRUE, + size_t cmdbufflen = STRLEN(ccline.cmdbuff); + + add_to_history(histype, ccline.cmdbuff, cmdbufflen, TRUE, histype == HIST_SEARCH ? firstc : NUL); if (firstc == ':') { vim_free(new_last_cmdline); - new_last_cmdline = vim_strsave(ccline.cmdbuff); + new_last_cmdline = vim_strnsave(ccline.cmdbuff, cmdbufflen); } } diff --git a/src/fileio.c b/src/fileio.c index 07e05fc..e7f3332 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -3770,19 +3770,12 @@ vim_fgets(char_u *buf, int size, FILE *fp) int vim_rename(char_u *from, char_u *to) { - int fd_in; - int fd_out; int n; - char *errmsg = NULL; - char *buffer; + int ret; #ifdef AMIGA BPTR flock; #endif stat_T st; - long perm; -#ifdef HAVE_ACL - vim_acl_T acl; // ACL from original file -#endif int use_tmp_file = FALSE; /* @@ -3903,6 +3896,61 @@ vim_rename(char_u *from, char_u *to) /* * Rename() failed, try copying the file. */ + ret = vim_copyfile(from, to); + if (ret != OK) + return -1; + + /* + * Remove copied original file + */ + if (mch_stat((char *)from, &st) >= 0) + mch_remove(from); + + return 0; +} + + +/* + * Create the new file with same permissions as the original. + * Return -1 for failure, 0 for success. + */ + int +vim_copyfile(char_u *from, char_u *to) +{ + int fd_in; + int fd_out; + int n; + char *errmsg = NULL; + char *buffer; + long perm; +#ifdef HAVE_ACL + vim_acl_T acl; // ACL from original file +#endif + +#ifdef HAVE_READLINK + int ret; + int len; + stat_T st; + char linkbuf[MAXPATHL + 1]; + + ret = mch_lstat((char *)from, &st); + if (ret >= 0 && S_ISLNK(st.st_mode)) + { + ret = FAIL; + + len = readlink((char *)from, linkbuf, MAXPATHL); + if (len > 0) + { + linkbuf[len] = NUL; + + // Create link + ret = symlink(linkbuf, (char *)to); + } + + return ret == 0 ? OK : FAIL; + } +#endif + perm = mch_getperm(from); #ifdef HAVE_ACL // For systems that support ACL: get the ACL from the original file. @@ -3914,7 +3962,7 @@ vim_rename(char_u *from, char_u *to) #ifdef HAVE_ACL mch_free_acl(acl); #endif - return -1; + return FAIL; } // Create the new file with same permissions as the original. @@ -3926,7 +3974,7 @@ vim_rename(char_u *from, char_u *to) #ifdef HAVE_ACL mch_free_acl(acl); #endif - return -1; + return FAIL; } buffer = alloc(WRITEBUFSIZE); @@ -3937,7 +3985,7 @@ vim_rename(char_u *from, char_u *to) #ifdef HAVE_ACL mch_free_acl(acl); #endif - return -1; + return FAIL; } while ((n = read_eintr(fd_in, buffer, WRITEBUFSIZE)) > 0) @@ -3969,10 +4017,9 @@ vim_rename(char_u *from, char_u *to) if (errmsg != NULL) { semsg(errmsg, to); - return -1; + return FAIL; } - mch_remove(from); - return 0; + return OK; } static int already_warned = FALSE; diff --git a/src/filepath.c b/src/filepath.c index 3bf8a2d..9f68d7c 100644 --- a/src/filepath.c +++ b/src/filepath.c @@ -292,7 +292,7 @@ shortpath_for_partial( modify_fname( char_u *src, // string with modifiers int tilde_file, // "~" is a file name, not $HOME - int *usedlen, // characters after src that are used + size_t *usedlen, // characters after src that are used char_u **fnamep, // file name so far char_u **bufp, // buffer for allocated file name or NULL int *fnamelen) // length of fnamep @@ -668,7 +668,7 @@ repeat: str = vim_strnsave(*fnamep, *fnamelen); if (sub != NULL && str != NULL) { - *usedlen = (int)(p + 1 - src); + *usedlen = p + 1 - src; s = do_string_sub(str, pat, sub, NULL, flags); if (s != NULL) { @@ -1038,7 +1038,7 @@ f_fnamemodify(typval_T *argvars, typval_T *rettv) { char_u *fname; char_u *mods; - int usedlen = 0; + size_t usedlen = 0; int len = 0; char_u *fbuf = NULL; char_u buf[NUMBUFLEN]; @@ -2649,6 +2649,31 @@ f_browsedir(typval_T *argvars UNUSED, typval_T *rettv) rettv->v_type = VAR_STRING; } +/* + * "filecopy()" function + */ + void +f_filecopy(typval_T *argvars, typval_T *rettv) +{ + char_u *from; + stat_T st; + + rettv->vval.v_number = FALSE; + + if (check_restricted() || check_secure() + || check_for_string_arg(argvars, 0) == FAIL + || check_for_string_arg(argvars, 1) == FAIL) + return; + + from = tv_get_string(&argvars[0]); + + if (mch_lstat((char *)from, &st) >= 0 + && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) + rettv->vval.v_number = vim_copyfile( + tv_get_string(&argvars[0]), + tv_get_string(&argvars[1])) == OK ? TRUE : FALSE; +} + #endif // FEAT_EVAL /* @@ -2707,7 +2732,7 @@ home_replace( if (homedir_env != NULL && *homedir_env == '~') { - int usedlen = 0; + size_t usedlen = 0; int flen; char_u *fbuf = NULL; @@ -3170,7 +3195,7 @@ expand_wildcards_eval( char_u *eval_pat = NULL; char_u *exp_pat = *pat; char *ignored_msg; - int usedlen; + size_t usedlen; int is_cur_alt_file = *exp_pat == '%' || *exp_pat == '#'; int star_follows = FALSE; diff --git a/src/gc.c b/src/gc.c new file mode 100644 index 0000000..987ca27 --- /dev/null +++ b/src/gc.c @@ -0,0 +1,780 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * gc.c: Garbage Collection + */ + +#include "vim.h" + +#if defined(FEAT_EVAL) || defined(PROTO) + +/* + * When recursively copying lists and dicts we need to remember which ones we + * have done to avoid endless recursiveness. This unique ID is used for that. + * The last bit is used for previous_funccal, ignored when comparing. + */ +static int current_copyID = 0; + +static int free_unref_items(int copyID); + +/* + * Return the next (unique) copy ID. + * Used for serializing nested structures. + */ + int +get_copyID(void) +{ + current_copyID += COPYID_INC; + return current_copyID; +} + +/* + * Garbage collection for lists and dictionaries. + * + * We use reference counts to be able to free most items right away when they + * are no longer used. But for composite items it's possible that it becomes + * unused while the reference count is > 0: When there is a recursive + * reference. Example: + * :let l = [1, 2, 3] + * :let d = {9: l} + * :let l[1] = d + * + * Since this is quite unusual we handle this with garbage collection: every + * once in a while find out which lists and dicts are not referenced from any + * variable. + * + * Here is a good reference text about garbage collection (refers to Python + * but it applies to all reference-counting mechanisms): + * http://python.ca/nas/python/gc/ + */ + +/* + * Do garbage collection for lists and dicts. + * When "testing" is TRUE this is called from test_garbagecollect_now(). + * Return TRUE if some memory was freed. + */ + int +garbage_collect(int testing) +{ + int copyID; + int abort = FALSE; + buf_T *buf; + win_T *wp; + int did_free = FALSE; + tabpage_T *tp; + + if (!testing) + { + // Only do this once. + want_garbage_collect = FALSE; + may_garbage_collect = FALSE; + garbage_collect_at_exit = FALSE; + } + + // The execution stack can grow big, limit the size. + if (exestack.ga_maxlen - exestack.ga_len > 500) + { + size_t new_len; + char_u *pp; + int n; + + // Keep 150% of the current size, with a minimum of the growth size. + n = exestack.ga_len / 2; + if (n < exestack.ga_growsize) + n = exestack.ga_growsize; + + // Don't make it bigger though. + if (exestack.ga_len + n < exestack.ga_maxlen) + { + new_len = (size_t)exestack.ga_itemsize * (exestack.ga_len + n); + pp = vim_realloc(exestack.ga_data, new_len); + if (pp == NULL) + return FAIL; + exestack.ga_maxlen = exestack.ga_len + n; + exestack.ga_data = pp; + } + } + + // We advance by two because we add one for items referenced through + // previous_funccal. + copyID = get_copyID(); + + /* + * 1. Go through all accessible variables and mark all lists and dicts + * with copyID. + */ + + // Don't free variables in the previous_funccal list unless they are only + // referenced through previous_funccal. This must be first, because if + // the item is referenced elsewhere the funccal must not be freed. + abort = abort || set_ref_in_previous_funccal(copyID); + + // script-local variables + abort = abort || garbage_collect_scriptvars(copyID); + + // buffer-local variables + FOR_ALL_BUFFERS(buf) + abort = abort || set_ref_in_item(&buf->b_bufvar.di_tv, copyID, + NULL, NULL); + + // window-local variables + FOR_ALL_TAB_WINDOWS(tp, wp) + abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, + NULL, NULL); + // window-local variables in autocmd windows + for (int i = 0; i < AUCMD_WIN_COUNT; ++i) + if (aucmd_win[i].auc_win != NULL) + abort = abort || set_ref_in_item( + &aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); +#ifdef FEAT_PROP_POPUP + FOR_ALL_POPUPWINS(wp) + abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, + NULL, NULL); + FOR_ALL_TABPAGES(tp) + FOR_ALL_POPUPWINS_IN_TAB(tp, wp) + abort = abort || set_ref_in_item(&wp->w_winvar.di_tv, copyID, + NULL, NULL); +#endif + + // tabpage-local variables + FOR_ALL_TABPAGES(tp) + abort = abort || set_ref_in_item(&tp->tp_winvar.di_tv, copyID, + NULL, NULL); + // global variables + abort = abort || garbage_collect_globvars(copyID); + + // function-local variables + abort = abort || set_ref_in_call_stack(copyID); + + // named functions (matters for closures) + abort = abort || set_ref_in_functions(copyID); + + // function call arguments, if v:testing is set. + abort = abort || set_ref_in_func_args(copyID); + + // funcstacks keep variables for closures + abort = abort || set_ref_in_funcstacks(copyID); + + // loopvars keep variables for loop blocks + abort = abort || set_ref_in_loopvars(copyID); + + // v: vars + abort = abort || garbage_collect_vimvars(copyID); + + // callbacks in buffers + abort = abort || set_ref_in_buffers(copyID); + + // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks + abort = abort || set_ref_in_insexpand_funcs(copyID); + + // 'operatorfunc' callback + abort = abort || set_ref_in_opfunc(copyID); + + // 'tagfunc' callback + abort = abort || set_ref_in_tagfunc(copyID); + + // 'imactivatefunc' and 'imstatusfunc' callbacks + abort = abort || set_ref_in_im_funcs(copyID); + +#ifdef FEAT_LUA + abort = abort || set_ref_in_lua(copyID); +#endif + +#ifdef FEAT_PYTHON + abort = abort || set_ref_in_python(copyID); +#endif + +#ifdef FEAT_PYTHON3 + abort = abort || set_ref_in_python3(copyID); +#endif + +#ifdef FEAT_JOB_CHANNEL + abort = abort || set_ref_in_channel(copyID); + abort = abort || set_ref_in_job(copyID); +#endif +#ifdef FEAT_NETBEANS_INTG + abort = abort || set_ref_in_nb_channel(copyID); +#endif + +#ifdef FEAT_TIMERS + abort = abort || set_ref_in_timer(copyID); +#endif + +#ifdef FEAT_QUICKFIX + abort = abort || set_ref_in_quickfix(copyID); +#endif + +#ifdef FEAT_TERMINAL + abort = abort || set_ref_in_term(copyID); +#endif + +#ifdef FEAT_PROP_POPUP + abort = abort || set_ref_in_popups(copyID); +#endif + + abort = abort || set_ref_in_classes(copyID); + + if (!abort) + { + /* + * 2. Free lists and dictionaries that are not referenced. + */ + did_free = free_unref_items(copyID); + + /* + * 3. Check if any funccal can be freed now. + * This may call us back recursively. + */ + free_unref_funccal(copyID, testing); + } + else if (p_verbose > 0) + { + verb_msg(_("Not enough memory to set references, garbage collection aborted!")); + } + + return did_free; +} + +/* + * Free lists, dictionaries, channels and jobs that are no longer referenced. + */ + static int +free_unref_items(int copyID) +{ + int did_free = FALSE; + + // Let all "free" functions know that we are here. This means no + // dictionaries, lists, channels or jobs are to be freed, because we will + // do that here. + in_free_unref_items = TRUE; + + /* + * PASS 1: free the contents of the items. We don't free the items + * themselves yet, so that it is possible to decrement refcount counters + */ + + // Go through the list of dicts and free items without this copyID. + did_free |= dict_free_nonref(copyID); + + // Go through the list of lists and free items without this copyID. + did_free |= list_free_nonref(copyID); + + // Go through the list of objects and free items without this copyID. + did_free |= object_free_nonref(copyID); + + // Go through the list of classes and free items without this copyID. + did_free |= class_free_nonref(copyID); + +#ifdef FEAT_JOB_CHANNEL + // Go through the list of jobs and free items without the copyID. This + // must happen before doing channels, because jobs refer to channels, but + // the reference from the channel to the job isn't tracked. + did_free |= free_unused_jobs_contents(copyID, COPYID_MASK); + + // Go through the list of channels and free items without the copyID. + did_free |= free_unused_channels_contents(copyID, COPYID_MASK); +#endif + + /* + * PASS 2: free the items themselves. + */ + object_free_items(copyID); + dict_free_items(copyID); + list_free_items(copyID); + +#ifdef FEAT_JOB_CHANNEL + // Go through the list of jobs and free items without the copyID. This + // must happen before doing channels, because jobs refer to channels, but + // the reference from the channel to the job isn't tracked. + free_unused_jobs(copyID, COPYID_MASK); + + // Go through the list of channels and free items without the copyID. + free_unused_channels(copyID, COPYID_MASK); +#endif + + in_free_unref_items = FALSE; + + return did_free; +} + +/* + * Mark all lists and dicts referenced through hashtab "ht" with "copyID". + * "list_stack" is used to add lists to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack) +{ + int todo; + int abort = FALSE; + hashitem_T *hi; + hashtab_T *cur_ht; + ht_stack_T *ht_stack = NULL; + ht_stack_T *tempitem; + + cur_ht = ht; + for (;;) + { + if (!abort) + { + // Mark each item in the hashtab. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + todo = (int)cur_ht->ht_used; + FOR_ALL_HASHTAB_ITEMS(cur_ht, hi, todo) + if (!HASHITEM_EMPTY(hi)) + { + --todo; + abort = abort || set_ref_in_item(&HI2DI(hi)->di_tv, copyID, + &ht_stack, list_stack); + } + } + + if (ht_stack == NULL) + break; + + // take an item from the stack + cur_ht = ht_stack->ht; + tempitem = ht_stack; + ht_stack = ht_stack->prev; + free(tempitem); + } + + return abort; +} + +#if defined(FEAT_LUA) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ + || defined(PROTO) +/* + * Mark a dict and its items with "copyID". + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_dict(dict_T *d, int copyID) +{ + if (d != NULL && d->dv_copyID != copyID) + { + d->dv_copyID = copyID; + return set_ref_in_ht(&d->dv_hashtab, copyID, NULL); + } + return FALSE; +} +#endif + +/* + * Mark a list and its items with "copyID". + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_list(list_T *ll, int copyID) +{ + if (ll != NULL && ll->lv_copyID != copyID) + { + ll->lv_copyID = copyID; + return set_ref_in_list_items(ll, copyID, NULL); + } + return FALSE; +} + +/* + * Mark all lists and dicts referenced through list "l" with "copyID". + * "ht_stack" is used to add hashtabs to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack) +{ + listitem_T *li; + int abort = FALSE; + list_T *cur_l; + list_stack_T *list_stack = NULL; + list_stack_T *tempitem; + + cur_l = l; + for (;;) + { + if (!abort && cur_l->lv_first != &range_list_item) + // Mark each item in the list. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + for (li = cur_l->lv_first; !abort && li != NULL; li = li->li_next) + abort = abort || set_ref_in_item(&li->li_tv, copyID, + ht_stack, &list_stack); + if (list_stack == NULL) + break; + + // take an item from the stack + cur_l = list_stack->list; + tempitem = list_stack; + list_stack = list_stack->prev; + free(tempitem); + } + + return abort; +} + +/* + * Mark the partial in callback 'cb' with "copyID". + */ + int +set_ref_in_callback(callback_T *cb, int copyID) +{ + typval_T tv; + + if (cb->cb_name == NULL || *cb->cb_name == NUL || cb->cb_partial == NULL) + return FALSE; + + tv.v_type = VAR_PARTIAL; + tv.vval.v_partial = cb->cb_partial; + return set_ref_in_item(&tv, copyID, NULL, NULL); +} + +/* + * Mark the dict "dd" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_dict( + dict_T *dd, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (dd == NULL || dd->dv_copyID == copyID) + return FALSE; + + // Didn't see this dict yet. + dd->dv_copyID = copyID; + if (ht_stack == NULL) + return set_ref_in_ht(&dd->dv_hashtab, copyID, list_stack); + + ht_stack_T *newitem = ALLOC_ONE(ht_stack_T); + if (newitem == NULL) + return TRUE; + + newitem->ht = &dd->dv_hashtab; + newitem->prev = *ht_stack; + *ht_stack = newitem; + + return FALSE; +} + +/* + * Mark the list "ll" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_list( + list_T *ll, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (ll == NULL || ll->lv_copyID == copyID) + return FALSE; + + // Didn't see this list yet. + ll->lv_copyID = copyID; + if (list_stack == NULL) + return set_ref_in_list_items(ll, copyID, ht_stack); + + list_stack_T *newitem = ALLOC_ONE(list_stack_T); + if (newitem == NULL) + return TRUE; + + newitem->list = ll; + newitem->prev = *list_stack; + *list_stack = newitem; + + return FALSE; +} + +/* + * Mark the partial "pt" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_partial( + partial_T *pt, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + if (pt == NULL || pt->pt_copyID == copyID) + return FALSE; + + // Didn't see this partial yet. + pt->pt_copyID = copyID; + + int abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID); + + if (pt->pt_dict != NULL) + { + typval_T dtv; + + dtv.v_type = VAR_DICT; + dtv.vval.v_dict = pt->pt_dict; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + if (pt->pt_obj != NULL) + { + typval_T objtv; + + objtv.v_type = VAR_OBJECT; + objtv.vval.v_object = pt->pt_obj; + set_ref_in_item(&objtv, copyID, ht_stack, list_stack); + } + + for (int i = 0; i < pt->pt_argc; ++i) + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); + // pt_funcstack is handled in set_ref_in_funcstacks() + // pt_loopvars is handled in set_ref_in_loopvars() + + return abort; +} + +#ifdef FEAT_JOB_CHANNEL +/* + * Mark the job "pt" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_job( + job_T *job, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + typval_T dtv; + + if (job == NULL || job->jv_copyID == copyID) + return FALSE; + + job->jv_copyID = copyID; + if (job->jv_channel != NULL) + { + dtv.v_type = VAR_CHANNEL; + dtv.vval.v_channel = job->jv_channel; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (job->jv_exit_cb.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = job->jv_exit_cb.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + return FALSE; +} + +/* + * Mark the channel "ch" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_channel( + channel_T *ch, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + typval_T dtv; + + if (ch == NULL || ch->ch_copyID == copyID) + return FALSE; + + ch->ch_copyID = copyID; + for (ch_part_T part = PART_SOCK; part < PART_COUNT; ++part) + { + for (jsonq_T *jq = ch->ch_part[part].ch_json_head.jq_next; + jq != NULL; jq = jq->jq_next) + set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack); + for (cbq_T *cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL; + cq = cq->cq_next) + if (cq->cq_callback.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = cq->cq_callback.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (ch->ch_part[part].ch_callback.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_part[part].ch_callback.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + } + if (ch->ch_callback.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_callback.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (ch->ch_close_cb.cb_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_close_cb.cb_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + return FALSE; +} +#endif + +/* + * Mark the class "cl" with "copyID". + * Also see set_ref_in_item(). + */ + int +set_ref_in_item_class( + class_T *cl, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + int abort = FALSE; + + if (cl == NULL || cl->class_copyID == copyID) + return FALSE; + + cl->class_copyID = copyID; + if (cl->class_members_tv != NULL) + { + // The "class_members_tv" table is allocated only for regular classes + // and not for interfaces. + for (int i = 0; !abort && i < cl->class_class_member_count; ++i) + abort = abort || set_ref_in_item( + &cl->class_members_tv[i], + copyID, ht_stack, list_stack); + } + + for (int i = 0; !abort && i < cl->class_class_function_count; ++i) + abort = abort || set_ref_in_func(NULL, + cl->class_class_functions[i], copyID); + + for (int i = 0; !abort && i < cl->class_obj_method_count; ++i) + abort = abort || set_ref_in_func(NULL, + cl->class_obj_methods[i], copyID); + + return abort; +} + +/* + * Mark the object "cl" with "copyID". + * Also see set_ref_in_item(). + */ + static int +set_ref_in_item_object( + object_T *obj, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + int abort = FALSE; + + if (obj == NULL || obj->obj_copyID == copyID) + return FALSE; + + obj->obj_copyID = copyID; + + // The typval_T array is right after the object_T. + typval_T *mtv = (typval_T *)(obj + 1); + for (int i = 0; !abort + && i < obj->obj_class->class_obj_member_count; ++i) + abort = abort || set_ref_in_item(mtv + i, copyID, + ht_stack, list_stack); + + return abort; +} + +/* + * Mark all lists, dicts and other container types referenced through typval + * "tv" with "copyID". + * "list_stack" is used to add lists to be marked. Can be NULL. + * "ht_stack" is used to add hashtabs to be marked. Can be NULL. + * + * Returns TRUE if setting references failed somehow. + */ + int +set_ref_in_item( + typval_T *tv, + int copyID, + ht_stack_T **ht_stack, + list_stack_T **list_stack) +{ + int abort = FALSE; + + switch (tv->v_type) + { + case VAR_DICT: + return set_ref_in_item_dict(tv->vval.v_dict, copyID, + ht_stack, list_stack); + + case VAR_LIST: + return set_ref_in_item_list(tv->vval.v_list, copyID, + ht_stack, list_stack); + + case VAR_FUNC: + { + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); + break; + } + + case VAR_PARTIAL: + return set_ref_in_item_partial(tv->vval.v_partial, copyID, + ht_stack, list_stack); + + case VAR_JOB: +#ifdef FEAT_JOB_CHANNEL + return set_ref_in_item_job(tv->vval.v_job, copyID, + ht_stack, list_stack); +#else + break; +#endif + + case VAR_CHANNEL: +#ifdef FEAT_JOB_CHANNEL + return set_ref_in_item_channel(tv->vval.v_channel, copyID, + ht_stack, list_stack); +#else + break; +#endif + + case VAR_CLASS: + return set_ref_in_item_class(tv->vval.v_class, copyID, + ht_stack, list_stack); + + case VAR_OBJECT: + return set_ref_in_item_object(tv->vval.v_object, copyID, + ht_stack, list_stack); + + case VAR_UNKNOWN: + case VAR_ANY: + case VAR_VOID: + case VAR_BOOL: + case VAR_SPECIAL: + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_BLOB: + case VAR_TYPEALIAS: + case VAR_INSTR: + // Types that do not contain any other item + break; + } + + return abort; +} + +#endif diff --git a/src/globals.h b/src/globals.h index 2c00e5f..fb5c7b3 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1936,6 +1936,7 @@ EXTERN int reset_term_props_on_termresponse INIT(= FALSE); EXTERN int disable_vterm_title_for_testing INIT(= FALSE); EXTERN long override_sysinfo_uptime INIT(= -1); EXTERN int override_autoload INIT(= FALSE); +EXTERN int override_defcompile INIT(= FALSE); EXTERN int ml_get_alloc_lines INIT(= FALSE); EXTERN int ignore_unreachable_code_for_testing INIT(= FALSE); @@ -5312,7 +5312,7 @@ gui_do_findrepl( i = msg_scroll; if (down) { - (void)do_search(NULL, '/', '/', ga.ga_data, 1L, searchflags, NULL); + (void)do_search(NULL, '/', '/', ga.ga_data, STRLEN(ga.ga_data), 1L, searchflags, NULL); } else { @@ -5320,7 +5320,7 @@ gui_do_findrepl( // direction p = vim_strsave_escaped(ga.ga_data, (char_u *)"?"); if (p != NULL) - (void)do_search(NULL, '?', '?', p, 1L, searchflags, NULL); + (void)do_search(NULL, '?', '?', p, STRLEN(p), 1L, searchflags, NULL); vim_free(p); } diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c index 4d201fc..67ee531 100644 --- a/src/gui_gtk_x11.c +++ b/src/gui_gtk_x11.c @@ -2704,23 +2704,9 @@ global_event_filter(GdkXEvent *xev, static void mainwin_realize(GtkWidget *widget UNUSED, gpointer data UNUSED) { -// If you get an error message here, you still need to unpack the runtime -// archive! -#ifdef magick -# undef magick -#endif - // A bit hackish, but avoids casting later and allows optimization -# define static static const -#define magick vim32x32 #include "../runtime/vim32x32.xpm" -#undef magick -#define magick vim16x16 #include "../runtime/vim16x16.xpm" -#undef magick -#define magick vim48x48 #include "../runtime/vim48x48.xpm" -#undef magick -# undef static GdkWindow * const mainwin_win = gtk_widget_get_window(gui.mainwin); @@ -2741,9 +2727,9 @@ mainwin_realize(GtkWidget *widget UNUSED, gpointer data UNUSED) */ GList *icons = NULL; - icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data(vim16x16)); - icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data(vim32x32)); - icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data(vim48x48)); + icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data((const char **)vim16x16)); + icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data((const char **)vim32x32)); + icons = g_list_prepend(icons, gdk_pixbuf_new_from_xpm_data((const char **)vim48x48)); gtk_window_set_icon_list(GTK_WINDOW(gui.mainwin), icons); diff --git a/src/gui_x11.c b/src/gui_x11.c index fc63658..edde6b5 100644 --- a/src/gui_x11.c +++ b/src/gui_x11.c @@ -1363,20 +1363,9 @@ gui_mch_init(void) #else // Use Pixmaps, looking much nicer. -// If you get an error message here, you still need to unpack the runtime -// archive! -# ifdef magick -# undef magick -# endif -# define magick vim32x32 # include "../runtime/vim32x32.xpm" -# undef magick -# define magick vim16x16 # include "../runtime/vim16x16.xpm" -# undef magick -# define magick vim48x48 # include "../runtime/vim48x48.xpm" -# undef magick static Pixmap icon = 0; static Pixmap icon_mask = 0; diff --git a/src/highlight.c b/src/highlight.c index 9aa149f..a71a100 100644 --- a/src/highlight.c +++ b/src/highlight.c @@ -258,6 +258,8 @@ static char *(highlight_init_both[]) = { "default link CurSearch Search", "default link PmenuKind Pmenu", "default link PmenuKindSel PmenuSel", + "default link PmenuMatch Pmenu", + "default link PmenuMatchSel PmenuSel", "default link PmenuExtra Pmenu", "default link PmenuExtraSel PmenuSel", CENT("Normal cterm=NONE", "Normal gui=NONE"), diff --git a/src/if_cscope.c b/src/if_cscope.c index d9982ef..7b6fd92 100644 --- a/src/if_cscope.c +++ b/src/if_cscope.c @@ -541,7 +541,7 @@ cs_add_common( char *ppath = NULL; int i; int len; - int usedlen = 0; + size_t usedlen = 0; char_u *fbuf = NULL; // get the filename (arg1), expand it, and try to stat it diff --git a/src/if_py_both.h b/src/if_py_both.h index 3e5993b..e0fd3ea 100644 --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -7325,12 +7325,11 @@ populate_module(PyObject *m) return -1; } +# if PY_VERSION_HEX < 0x30c00a7 + // find_module has been removed as of Python 3.12.0a7 if ((py_find_module = PyObject_GetAttrString(cls, "find_module"))) - { - // find_module() is deprecated, this may stop working in some later - // version. ADD_OBJECT(m, "_find_module", py_find_module); - } +# endif Py_DECREF(imp); diff --git a/src/indent.c b/src/indent.c index 1dfde7d..777db24 100644 --- a/src/indent.c +++ b/src/indent.c @@ -123,14 +123,22 @@ tabstop_padding(colnr_T col, int ts_arg, int *vts) /* * Find the size of the tab that covers a particular column. + * + * If this is being called as part of a shift operation, col is not the cursor + * column but is the column number to the left of the first non-whitespace + * character in the line. If the shift is to the left (left = TRUE), then + * return the size of the tab interval to the left of the column. */ int -tabstop_at(colnr_T col, int ts, int *vts) +tabstop_at(colnr_T col, int ts, int *vts, int left) { - int tabcount; - colnr_T tabcol = 0; - int t; - int tab_size = 0; + int tabcount; // Number of tab stops in the list of variable + // tab stops. + colnr_T tabcol = 0; // Column of the tab stop under consideration. + int t; // Tabstop index in the list of variable tab + // stops. + int tab_size = 0; // Size of the tab stop interval to the right + // or left of the col. if (vts == 0 || vts[0] == 0) return ts; @@ -141,11 +149,22 @@ tabstop_at(colnr_T col, int ts, int *vts) tabcol += vts[t]; if (tabcol > col) { - tab_size = vts[t]; + // If shifting left (left != 0), and if the column to the left of + // the first first non-blank character (col) in the line is + // already to the left of the first tabstop, set the shift amount + // (tab_size) to just enough to shift the line to the left margin. + // The value doesn't seem to matter as long as it is at least that + // distance. + if (left && (t == 1)) + tab_size = col; + else + tab_size = vts[t - (left ? 1 : 0)]; break; } } - if (t > tabcount) + if (t > tabcount) // If the value of the index t is beyond the + // end of the list, use the tab stop value at + // the end of the list. tab_size = vts[tabcount]; return tab_size; @@ -327,20 +346,20 @@ tabstop_first(int *ts) long get_sw_value(buf_T *buf) { - return get_sw_value_col(buf, 0); + return get_sw_value_col(buf, 0, FALSE); } /* * Idem, using "pos". */ static long -get_sw_value_pos(buf_T *buf, pos_T *pos) +get_sw_value_pos(buf_T *buf, pos_T *pos, int left) { pos_T save_cursor = curwin->w_cursor; long sw_value; curwin->w_cursor = *pos; - sw_value = get_sw_value_col(buf, get_nolist_virtcol()); + sw_value = get_sw_value_col(buf, get_nolist_virtcol(), left); curwin->w_cursor = save_cursor; return sw_value; } @@ -349,23 +368,23 @@ get_sw_value_pos(buf_T *buf, pos_T *pos) * Idem, using the first non-black in the current line. */ long -get_sw_value_indent(buf_T *buf) +get_sw_value_indent(buf_T *buf, int left) { pos_T pos = curwin->w_cursor; pos.col = getwhitecols_curline(); - return get_sw_value_pos(buf, &pos); + return get_sw_value_pos(buf, &pos, left); } /* * Idem, using virtual column "col". */ long -get_sw_value_col(buf_T *buf, colnr_T col UNUSED) +get_sw_value_col(buf_T *buf, colnr_T col UNUSED, int left UNUSED) { return buf->b_p_sw ? buf->b_p_sw : #ifdef FEAT_VARTABS - tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); + tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array, left); #else buf->b_p_ts; #endif diff --git a/src/insexpand.c b/src/insexpand.c index 93a56a8..c673df9 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -113,6 +113,7 @@ struct compl_S // cp_flags has CP_FREE_FNAME int cp_flags; // CP_ values int cp_number; // sequence number + int cp_score; // fuzzy match score }; // values for cp_flags @@ -147,13 +148,6 @@ static char_u *compl_leader = NULL; static int compl_get_longest = FALSE; // put longest common string // in compl_leader -static int compl_no_insert = FALSE; // FALSE: select & insert - // TRUE: noinsert -static int compl_no_select = FALSE; // FALSE: select & insert - // TRUE: noselect -static int compl_longest = FALSE; // FALSE: insert full match - // TRUE: insert longest prefix - // Selected one of the matches. When FALSE the match was edited or using the // longest common string. static int compl_used_match; @@ -176,6 +170,7 @@ static int ctrl_x_mode = CTRL_X_NORMAL; static int compl_matches = 0; // number of completion matches static char_u *compl_pattern = NULL; +static size_t compl_patternlen = 0; static int compl_direction = FORWARD; static int compl_shows_dir = FORWARD; static int compl_pending = 0; // > 1 for postponed CTRL-N @@ -206,6 +201,8 @@ static int compl_cont_status = 0; static int compl_opt_refresh_always = FALSE; static int compl_opt_suppress_empty = FALSE; +static int compl_selected_item = -1; + static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup); static void ins_compl_longest_match(compl_T *match); static void ins_compl_del_pum(void); @@ -1049,21 +1046,12 @@ ins_compl_long_shown_match(void) } /* - * Set variables that store noselect and noinsert behavior from the - * 'completeopt' value. + * Get the local or global value of 'completeopt' flags. */ - void -completeopt_was_set(void) + unsigned int +get_cot_flags(void) { - compl_no_insert = FALSE; - compl_no_select = FALSE; - compl_longest = FALSE; - if (strstr((char *)p_cot, "noselect") != NULL) - compl_no_select = TRUE; - if (strstr((char *)p_cot, "noinsert") != NULL) - compl_no_insert = TRUE; - if (strstr((char *)p_cot, "longest") != NULL) - compl_longest = TRUE; + return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags; } @@ -1110,7 +1098,7 @@ ins_compl_del_pum(void) pum_wanted(void) { // 'completeopt' must contain "menu" or "menuone" - if (vim_strchr(p_cot, 'm') == NULL) + if ((get_cot_flags() & COT_ANY_MENU) == 0) return FALSE; // The display looks bad on a B&W display. @@ -1144,7 +1132,7 @@ pum_enough_matches(void) compl = compl->cp_next; } while (!is_first_match(compl)); - if (strstr((char *)p_cot, "menuone") != NULL) + if (get_cot_flags() & COT_MENUONE) return (i >= 1); return (i >= 2); } @@ -1212,6 +1200,19 @@ trigger_complete_changed_event(int cur) #endif /* + * pumitem qsort compare func + */ + static int +ins_compl_fuzzy_cmp(const void *a, const void *b) +{ + const int sa = (*(pumitem_T *)a).pum_score; + const int sb = (*(pumitem_T *)b).pum_score; + const int ia = (*(pumitem_T *)a).pum_idx; + const int ib = (*(pumitem_T *)b).pum_idx; + return sa == sb ? (ia == ib ? 0 : (ia < ib ? -1 : 1)) : (sa < sb ? 1 : -1); +} + +/* * Build a popup menu to show the completion matches. * Returns the popup menu entry that should be selected. Returns -1 if nothing * should be selected. @@ -1226,6 +1227,10 @@ ins_compl_build_pum(void) int i; int cur = -1; int lead_len = 0; + int max_fuzzy_score = 0; + unsigned int cur_cot_flags = get_cot_flags(); + int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; + int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; // Need to build the popup menu list. compl_match_arraysize = 0; @@ -1235,9 +1240,15 @@ ins_compl_build_pum(void) do { + // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, + // set the cp_score for later comparisons. + if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) + compl->cp_score = fuzzy_match_str(compl->cp_str, compl_leader); + if (!match_at_original_text(compl) && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, lead_len))) + || ins_compl_equal(compl, compl_leader, lead_len) + || (compl_fuzzy_match && compl->cp_score > 0))) ++compl_match_arraysize; compl = compl->cp_next; } while (compl != NULL && !is_first_match(compl)); @@ -1254,15 +1265,22 @@ ins_compl_build_pum(void) if (match_at_original_text(compl_shown_match)) shown_match_ok = TRUE; + if (compl_leader != NULL + && STRCMP(compl_leader, compl_orig_text) == 0 + && shown_match_ok == FALSE) + compl_shown_match = compl_no_select ? compl_first_match + : compl_first_match->cp_next; + i = 0; compl = compl_first_match; do { if (!match_at_original_text(compl) && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, lead_len))) + || ins_compl_equal(compl, compl_leader, lead_len) + || (compl_fuzzy_match && compl->cp_score > 0))) { - if (!shown_match_ok) + if (!shown_match_ok && !compl_fuzzy_match) { if (compl == compl_shown_match || did_find_shown_match) { @@ -1278,6 +1296,34 @@ ins_compl_build_pum(void) shown_compl = compl; cur = i; } + else if (compl_fuzzy_match) + { + if (i == 0) + shown_compl = compl; + // Update the maximum fuzzy score and the shown match + // if the current item's score is higher + if (compl->cp_score > max_fuzzy_score) + { + did_find_shown_match = TRUE; + max_fuzzy_score = compl->cp_score; + compl_shown_match = compl; + shown_match_ok = TRUE; + } + + // If there is no "no select" condition and the max fuzzy + // score is positive, or there is no completion leader or the + // leader length is zero, mark the shown match as valid and + // reset the current index. + if (!compl_no_select + && (max_fuzzy_score > 0 + || (compl_leader == NULL || lead_len == 0))) + { + shown_match_ok = TRUE; + cur = 0; + if (match_at_original_text(compl_shown_match)) + compl_shown_match = shown_compl; + } + } if (compl->cp_text[CPT_ABBR] != NULL) compl_match_array[i].pum_text = @@ -1286,6 +1332,7 @@ ins_compl_build_pum(void) compl_match_array[i].pum_text = compl->cp_str; compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; + compl_match_array[i].pum_score = compl->cp_score; if (compl->cp_text[CPT_MENU] != NULL) compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU]; @@ -1293,7 +1340,7 @@ ins_compl_build_pum(void) compl_match_array[i++].pum_extra = compl->cp_fname; } - if (compl == compl_shown_match) + if (compl == compl_shown_match && !compl_fuzzy_match) { did_find_shown_match = TRUE; @@ -1313,6 +1360,15 @@ ins_compl_build_pum(void) compl = compl->cp_next; } while (compl != NULL && !is_first_match(compl)); + if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) + { + for (i = 0; i < compl_match_arraysize; i++) + compl_match_array[i].pum_idx = i; + // sort by the largest score of fuzzy match + qsort(compl_match_array, (size_t)compl_match_arraysize, + sizeof(pumitem_T), ins_compl_fuzzy_cmp); + } + if (!shown_match_ok) // no displayed match at all cur = -1; @@ -1369,6 +1425,7 @@ ins_compl_show_pum(void) // Use the cursor to get all wrapping and other settings right. col = curwin->w_cursor.col; curwin->w_cursor.col = compl_col; + compl_selected_item = cur; pum_display(compl_match_array, compl_match_arraysize, cur); curwin->w_cursor.col = col; @@ -1386,6 +1443,15 @@ ins_compl_show_pum(void) #define DICT_EXACT (2) // "dict" is the exact name of a file /* + * Get current completion leader + */ + char_u * +ins_compl_leader(void) +{ + return compl_leader != NULL ? compl_leader : compl_orig_text; +} + +/* * Add any identifiers that match the given pattern "pat" in the list of * dictionary files "dict_start" to the list of completions. */ @@ -1708,6 +1774,7 @@ ins_compl_free(void) int i; VIM_CLEAR(compl_pattern); + compl_patternlen = 0; VIM_CLEAR(compl_leader); if (compl_first_match == NULL) @@ -1747,6 +1814,7 @@ ins_compl_clear(void) compl_started = FALSE; compl_matches = 0; VIM_CLEAR(compl_pattern); + compl_patternlen = 0; VIM_CLEAR(compl_leader); edit_submode_extra = NULL; VIM_CLEAR(compl_orig_text); @@ -2404,9 +2472,8 @@ ins_compl_prep(int c) if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = compl_longest; + compl_get_longest = (get_cot_flags() & COT_LONGEST) != 0; compl_used_match = TRUE; - } if (ctrl_x_mode_not_defined_yet()) @@ -2878,6 +2945,10 @@ set_completion(colnr_T startcol, list_T *list) int save_w_wrow = curwin->w_wrow; int save_w_leftcol = curwin->w_leftcol; int flags = CP_ORIGINAL_TEXT; + unsigned int cur_cot_flags = get_cot_flags(); + int compl_longest = (cur_cot_flags & COT_LONGEST) != 0; + int compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; + int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; // If already doing completions stop it. if (ctrl_x_mode_not_default()) @@ -3374,7 +3445,7 @@ done: get_next_include_file_completion(int compl_type) { find_pattern_in_path(compl_pattern, compl_direction, - (int)STRLEN(compl_pattern), FALSE, FALSE, + (int)compl_patternlen, FALSE, FALSE, (compl_type == CTRL_X_PATH_DEFINES && !(compl_cont_status & CONT_SOL)) ? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND, @@ -3478,8 +3549,7 @@ get_next_cmdline_completion(void) int num_matches; if (expand_cmdline(&compl_xp, compl_pattern, - (int)STRLEN(compl_pattern), - &num_matches, &matches) == EXPAND_OK) + (int)compl_patternlen, &num_matches, &matches) == EXPAND_OK) ins_compl_add_matches(num_matches, matches, FALSE); } @@ -3644,8 +3714,8 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) st->cur_match_pos, compl_direction, compl_pattern); else found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, - NULL, compl_direction, compl_pattern, 1L, - SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); + NULL, compl_direction, compl_pattern, compl_patternlen, + 1L, SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); --msg_silent; if (!compl_started || st->set_match_pos) { @@ -4018,6 +4088,43 @@ ins_compl_show_filename(void) } /* + * Find a completion item when 'completeopt' contains "fuzzy". + */ + static compl_T * +find_comp_when_fuzzy(void) +{ + int score; + char_u* str; + int target_idx = -1; + int is_forward = compl_shows_dir_forward(); + int is_backward = compl_shows_dir_backward(); + compl_T *comp = NULL; + + if (compl_match_array == NULL || + (is_forward && compl_selected_item == compl_match_arraysize - 1) + || (is_backward && compl_selected_item == 0)) + return compl_first_match; + + if (is_forward) + target_idx = compl_selected_item + 1; + else if (is_backward) + target_idx = compl_selected_item == -1 ? compl_match_arraysize - 1 + : compl_selected_item - 1; + + score = compl_match_array[target_idx].pum_score; + str = compl_match_array[target_idx].pum_text; + + comp = compl_first_match; + do { + if (comp->cp_score == score && (str == comp->cp_str || str == comp->cp_text[CPT_ABBR])) + return comp; + comp = comp->cp_next; + } while (comp != NULL && !is_first_match(comp)); + + return NULL; +} + +/* * Find the next set of matches for completion. Repeat the completion "todo" * times. The number of matches found is returned in 'num_matches'. * @@ -4039,12 +4146,16 @@ find_next_completion_match( { int found_end = FALSE; compl_T *found_compl = NULL; + unsigned int cur_cot_flags = get_cot_flags(); + int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; + int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; while (--todo >= 0) { if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { - compl_shown_match = compl_shown_match->cp_next; + compl_shown_match = compl_fuzzy_match && compl_match_array != NULL + ? find_comp_when_fuzzy() : compl_shown_match->cp_next; found_end = (compl_first_match != NULL && (is_first_match(compl_shown_match->cp_next) || is_first_match(compl_shown_match))); @@ -4053,7 +4164,8 @@ find_next_completion_match( && compl_shown_match->cp_prev != NULL) { found_end = is_first_match(compl_shown_match); - compl_shown_match = compl_shown_match->cp_prev; + compl_shown_match = compl_fuzzy_match && compl_match_array != NULL + ? find_comp_when_fuzzy() : compl_shown_match->cp_prev; found_end |= is_first_match(compl_shown_match); } else @@ -4103,7 +4215,8 @@ find_next_completion_match( if (!match_at_original_text(compl_shown_match) && compl_leader != NULL && !ins_compl_equal(compl_shown_match, - compl_leader, (int)STRLEN(compl_leader))) + compl_leader, (int)STRLEN(compl_leader)) + && !(compl_fuzzy_match && compl_shown_match->cp_score > 0)) ++todo; else // Remember a matching item. @@ -4153,13 +4266,18 @@ ins_compl_next( int advance; int started = compl_started; buf_T *orig_curbuf = curbuf; + unsigned int cur_cot_flags = get_cot_flags(); + int compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; + int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; // When user complete function return -1 for findstart which is next // time of 'always', compl_shown_match become NULL. if (compl_shown_match == NULL) return -1; - if (compl_leader != NULL && !match_at_original_text(compl_shown_match)) + if (compl_leader != NULL + && !match_at_original_text(compl_shown_match) + && !compl_fuzzy_match) // Update "compl_shown_match" to the actually shown match ins_compl_update_shown_match(); @@ -4305,7 +4423,7 @@ ins_compl_check_keys(int frequency, int in_compl_func) } } } - if (compl_pending != 0 && !got_int && !compl_no_insert) + if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT)) { int todo = compl_pending > 0 ? compl_pending : -compl_pending; @@ -4383,7 +4501,8 @@ ins_compl_use_match(int c) /* * Get the pattern, column and length for normal completion (CTRL-N CTRL-P * completion) - * Sets the global variables: compl_col, compl_length and compl_pattern. + * Sets the global variables: compl_col, compl_length, compl_pattern and + * compl_patternlen. * Uses the global variables: compl_cont_status and ctrl_x_mode */ static int @@ -4404,32 +4523,45 @@ get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) else compl_pattern = vim_strnsave(line + compl_col, compl_length); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } } else if (compl_status_adding()) { char_u *prefix = (char_u *)"\\<"; + size_t prefixlen = STRLEN_LITERAL("\\<"); // we need up to 2 extra chars for the prefix compl_pattern = alloc(quote_meta(NULL, line + compl_col, - compl_length) + 2); + compl_length) + prefixlen); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } if (!vim_iswordp(line + compl_col) || (compl_col > 0 && (vim_iswordp(mb_prevptr(line, line + compl_col))))) + { prefix = (char_u *)""; + prefixlen = 0; + } STRCPY((char *)compl_pattern, prefix); - (void)quote_meta(compl_pattern + STRLEN(prefix), + (void)quote_meta(compl_pattern + prefixlen, line + compl_col, compl_length); } else if (--startcol < 0 || !vim_iswordp(mb_prevptr(line, line + startcol + 1))) { // Match any word of at least two chars - compl_pattern = vim_strsave((char_u *)"\\<\\k\\k"); + compl_pattern = vim_strnsave((char_u *)"\\<\\k\\k", STRLEN_LITERAL("\\<\\k\\k")); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } compl_col += curs_col; compl_length = 0; } @@ -4465,7 +4597,10 @@ get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) // alloc(7) is enough -- Acevedo compl_pattern = alloc(7); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } STRCPY((char *)compl_pattern, "\\<"); (void)quote_meta(compl_pattern + 2, line + compl_col, 1); STRCAT((char *)compl_pattern, "\\k"); @@ -4475,13 +4610,18 @@ get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) compl_pattern = alloc(quote_meta(NULL, line + compl_col, compl_length) + 2); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } STRCPY((char *)compl_pattern, "\\<"); (void)quote_meta(compl_pattern + 2, line + compl_col, compl_length); } } + compl_patternlen = STRLEN(compl_pattern); + return OK; } @@ -4503,7 +4643,12 @@ get_wholeline_compl_info(char_u *line, colnr_T curs_col) else compl_pattern = vim_strnsave(line + compl_col, compl_length); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } + + compl_patternlen = STRLEN(compl_pattern); return OK; } @@ -4533,7 +4678,12 @@ get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col) compl_length = (int)curs_col - startcol; compl_pattern = addstar(line + compl_col, compl_length, EXPAND_FILES); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } + + compl_patternlen = STRLEN(compl_pattern); return OK; } @@ -4547,9 +4697,13 @@ get_cmdline_compl_info(char_u *line, colnr_T curs_col) { compl_pattern = vim_strnsave(line, curs_col); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } + compl_patternlen = curs_col; set_cmd_context(&compl_xp, compl_pattern, - (int)STRLEN(compl_pattern), curs_col, FALSE); + (int)compl_patternlen, curs_col, FALSE); if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL || compl_xp.xp_context == EXPAND_NOTHING) // No completion possible, use an empty pattern to get a @@ -4647,8 +4801,12 @@ get_userdefined_compl_info(colnr_T curs_col UNUSED) compl_length = curs_col - compl_col; compl_pattern = vim_strnsave(line + compl_col, compl_length); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } + compl_patternlen = compl_length; ret = OK; #endif @@ -4685,8 +4843,12 @@ get_spell_compl_info(int startcol UNUSED, colnr_T curs_col UNUSED) line = ml_get(curwin->w_cursor.lnum); compl_pattern = vim_strnsave(line + compl_col, compl_length); if (compl_pattern == NULL) + { + compl_patternlen = 0; return FAIL; + } + compl_patternlen = compl_length; ret = OK; #endif @@ -4907,6 +5069,7 @@ ins_compl_start(void) -1, NULL, NULL, NULL, 0, flags, FALSE) != OK) { VIM_CLEAR(compl_pattern); + compl_patternlen = 0; VIM_CLEAR(compl_orig_text); return FAIL; } @@ -5195,7 +5358,7 @@ spell_back_to_badword(void) { pos_T tpos = curwin->w_cursor; - spell_bad_len = spell_move_to(curwin, BACKWARD, TRUE, TRUE, NULL); + spell_bad_len = spell_move_to(curwin, BACKWARD, SMT_ALL, TRUE, NULL); if (curwin->w_cursor.col != tpos.col) start_arrow(&tpos); } diff --git a/src/link.sh b/src/link.sh index e4030de..6e5c50f 100755 --- a/src/link.sh +++ b/src/link.sh @@ -53,7 +53,7 @@ else if sh link_$PROG.cmd; then touch auto/link.sed cp link_$PROG.cmd linkit_$PROG.sh - for libname in SM ICE nsl dnet dnet_stub inet socket dir elf iconv Xt Xmu Xp Xpm X11 Xdmcp x w perl dl pthread thread readline m crypt attr; do + for libname in SM ICE nsl dnet dnet_stub inet socket dir iconv Xt Xmu Xp Xpm X11 Xdmcp x w perl dl pthread thread readline m crypt attr; do cont=yes while test -n "$cont"; do if grep "l$libname " linkit_$PROG.sh >/dev/null; then @@ -3194,4 +3194,28 @@ f_reduce(typval_T *argvars, typval_T *rettv) blob_reduce(argvars, &argvars[1], rettv); } +/* + * slice() function + */ + void +f_slice(typval_T *argvars, typval_T *rettv) +{ + if (in_vim9script() + && ((argvars[0].v_type != VAR_STRING + && argvars[0].v_type != VAR_LIST + && argvars[0].v_type != VAR_BLOB + && check_for_list_arg(argvars, 0) == FAIL) + || check_for_number_arg(argvars, 1) == FAIL + || check_for_opt_number_arg(argvars, 2) == FAIL)) + return; + + if (check_can_index(&argvars[0], TRUE, FALSE) != OK) + return; + + copy_tv(argvars, rettv); + eval_index_inner(rettv, TRUE, argvars + 1, + argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, + TRUE, NULL, 0, FALSE); +} + #endif // defined(FEAT_EVAL) diff --git a/src/misc1.c b/src/misc1.c index c5a0c38..8348488 100644 --- a/src/misc1.c +++ b/src/misc1.c @@ -497,12 +497,17 @@ plines_win_col(win_T *wp, linenr_T lnum, long column) return lines; } +/* + * Return number of window lines the physical line range from "first" until + * "last" will occupy in window "wp". Takes into account folding, 'wrap', + * topfill and filler lines beyond the end of the buffer. Limit to "max" lines. + */ int -plines_m_win(win_T *wp, linenr_T first, linenr_T last, int limit_winheight) +plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max) { int count = 0; - while (first <= last && (!limit_winheight || count < wp->w_height)) + while (first <= last && count < max) { #ifdef FEAT_FOLDING int x; @@ -531,9 +536,7 @@ plines_m_win(win_T *wp, linenr_T first, linenr_T last, int limit_winheight) if (first == wp->w_buffer->b_ml.ml_line_count + 1) count += diff_check_fill(wp, first); #endif - if (limit_winheight && count > wp->w_height) - return wp->w_height; - return (count); + return MIN(max, count); } int diff --git a/src/mouse.c b/src/mouse.c index 247c6df..4e10e72 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -3029,16 +3029,22 @@ mouse_comp_pos( if (win->w_skipcol > 0 && lnum == win->w_topline) { - // Adjust for 'smoothscroll' clipping the top screen lines. - // A similar formula is used in curs_columns(). int width1 = win->w_width - win_col_off(win); - int skip_lines = 0; - if (win->w_skipcol > width1) - skip_lines = (win->w_skipcol - width1) + + if (width1 > 0) + { + int skip_lines = 0; + + // Adjust for 'smoothscroll' clipping the top screen lines. + // A similar formula is used in curs_columns(). + if (win->w_skipcol > width1) + skip_lines = (win->w_skipcol - width1) / (width1 + win_col_off2(win)) + 1; - else if (win->w_skipcol > 0) - skip_lines = 1; - count -= skip_lines; + else if (win->w_skipcol > 0) + skip_lines = 1; + + count -= skip_lines; + } } if (count > row) @@ -319,6 +319,7 @@ update_topline(void) redraw_later(UPD_NOT_VALID); curwin->w_topline = 1; curwin->w_botline = 2; + curwin->w_skipcol = 0; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; curwin->w_scbind_pos = 1; } @@ -1455,7 +1456,7 @@ textpos2screenpos( is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, TRUE, NULL); #endif - row = plines_m_win(wp, wp->w_topline, lnum - 1, FALSE); + row = plines_m_win(wp, wp->w_topline, lnum - 1, INT_MAX); // "row" should be the screen line where line "lnum" begins, which can // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero. row -= adjust_plines_for_skipcol(wp); @@ -1629,6 +1630,7 @@ static void cursor_correct_sms(void) int width2 = width1 + curwin_col_off2(); int so_cols = so == 0 ? 0 : width1 + (so - 1) * width2; int space_cols = (curwin->w_height - 1) * width2; + int overlap, top, bot; int size = so == 0 ? 0 : win_linetabsize(curwin, curwin->w_topline, ml_get(curwin->w_topline), (colnr_T)MAXCOL); @@ -1638,16 +1640,16 @@ static void cursor_correct_sms(void) so_cols = space_cols / 2; // Not enough room: put cursor in the middle. // Not enough screen lines in topline: ignore 'scrolloff'. - while (so_cols > size && so_cols - width2 >= width1) + while (so_cols > size && so_cols - width2 >= width1 && width1 > 0) so_cols -= width2; if (so_cols >= width1 && so_cols > size) so_cols -= width1; // If there is no marker or we have non-zero scrolloff, just ignore it. - int overlap = (curwin->w_skipcol == 0 || so_cols != 0) ? 0 + overlap = (curwin->w_skipcol == 0 || so_cols != 0) ? 0 : sms_marker_overlap(curwin, -1); - int top = curwin->w_skipcol + overlap + so_cols; - int bot = curwin->w_skipcol + width1 + (curwin->w_height - 1) * width2 + top = curwin->w_skipcol + overlap + so_cols; + bot = curwin->w_skipcol + width1 + (curwin->w_height - 1) * width2 - so_cols; validate_virtcol(); colnr_T col = curwin->w_virtcol; @@ -2025,6 +2027,8 @@ adjust_skipcol(void) long so = get_scrolloff_value(); int scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; int scrolled = FALSE; + int row = 0; + int overlap, col; validate_cheight(); if (curwin->w_cline_height == curwin->w_height @@ -2039,7 +2043,7 @@ adjust_skipcol(void) } validate_virtcol(); - int overlap = sms_marker_overlap(curwin, -1); + overlap = sms_marker_overlap(curwin, -1); while (curwin->w_skipcol > 0 && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) { @@ -2057,8 +2061,19 @@ adjust_skipcol(void) return; // don't scroll in the other direction now } - int col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; - int row = 0; + col = curwin->w_virtcol + scrolloff_cols; + + // Avoid adjusting for 'scrolloff' beyond the text line height. + if (scrolloff_cols > 0) + { + int size = win_linetabsize(curwin, curwin->w_topline, + ml_get(curwin->w_topline), (colnr_T)MAXCOL); + size = width1 + width2 * ((size - width1 + width2 - 1) / width2); + while (col > size) + col -= width2; + } + col -= curwin->w_skipcol; + if (col >= width1) { col -= width1; @@ -2616,12 +2631,14 @@ scroll_cursor_bot(int min_scroll, int set_topbot) plines_win #endif (curwin, curwin->w_topline, FALSE); - int skip_lines = 0; int width1 = curwin->w_width - curwin_col_off(); + if (width1 > 0) { int width2 = width1 + curwin_col_off2(); - // similar formula is used in curs_columns() + int skip_lines = 0; + + // A similar formula is used in curs_columns(). if (curwin->w_skipcol > width1) skip_lines += (curwin->w_skipcol - width1) / width2 + 1; else if (curwin->w_skipcol > 0) @@ -2781,7 +2798,9 @@ scroll_cursor_bot(int min_scroll, int set_topbot) } curwin->w_valid |= VALID_TOPLINE; - cursor_correct_sms(); + // Make sure cursor is still visible after adjusting skipcol for "zb". + if (set_topbot) + cursor_correct_sms(); } /* @@ -3155,9 +3174,10 @@ static int get_scroll_overlap(int dir) /* * Scroll "count" lines with 'smoothscroll' in direction "dir". Return TRUE - * when scrolling happened. + * when scrolling happened. Adjust "curscount" for scrolling different amount of + * lines when 'smoothscroll' is disabled. */ -static int scroll_with_sms(int dir, long count) +static int scroll_with_sms(int dir, long count, long *curscount) { int prev_sms = curwin->w_p_sms; colnr_T prev_skipcol = curwin->w_skipcol; @@ -3180,7 +3200,10 @@ static int scroll_with_sms(int dir, long count) fixdir = dir * -1; while (curwin->w_skipcol > 0 && curwin->w_topline < curbuf->b_ml.ml_line_count) + { scroll_redraw(fixdir == FORWARD, 1); + *curscount += (fixdir == dir ? 1 : -1); + } } curwin->w_p_sms = prev_sms; @@ -3217,21 +3240,27 @@ pagescroll(int dir, long count, int half) curwin->w_p_scr = MIN(curwin->w_height, count); count = MIN(curwin->w_height, curwin->w_p_scr); - int curscount = count; + long curscount = count; // Adjust count so as to not reveal end of buffer lines. - if (dir == FORWARD) + if (dir == FORWARD + && (curwin->w_topline + curwin->w_height + count > buflen +#ifdef FEAT_FOLDING + || hasAnyFolding(curwin) +#endif + )) { int n = plines_correct_topline(curwin, curwin->w_topline, FALSE); if (n - count < curwin->w_height && curwin->w_topline < buflen) - n += plines_m_win(curwin, curwin->w_topline + 1, buflen, FALSE); - if (n - count < curwin->w_height) + n += plines_m_win(curwin, curwin->w_topline + 1, buflen, + curwin->w_height + count); + if (n < curwin->w_height + count) count = n - curwin->w_height; } // (Try to) scroll the window unless already at the end of the buffer. if (count > 0) { - nochange = scroll_with_sms(dir, count); + nochange = scroll_with_sms(dir, count, &curscount); curwin->w_cursor.lnum = prev_lnum; curwin->w_cursor.col = prev_col; curwin->w_curswant = prev_curswant; @@ -3250,7 +3279,7 @@ pagescroll(int dir, long count, int half) // Scroll [count] times 'window' or current window height lines. count *= ((ONE_WINDOW && p_window > 0 && p_window < Rows - 1) ? MAX(1, p_window - 2) : get_scroll_overlap(dir)); - nochange = scroll_with_sms(dir, count); + nochange = scroll_with_sms(dir, count, &count); // Place cursor at top or bottom of window. validate_botline(); diff --git a/src/normal.c b/src/normal.c index fef2826..b55d941 100644 --- a/src/normal.c +++ b/src/normal.c @@ -61,7 +61,7 @@ static void nv_end(cmdarg_T *cap); static void nv_dollar(cmdarg_T *cap); static void nv_search(cmdarg_T *cap); static void nv_next(cmdarg_T *cap); -static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrapped); +static int normal_search(cmdarg_T *cap, int dir, char_u *pat, size_t patlen, int opt, int *wrapped); static void nv_csearch(cmdarg_T *cap); static void nv_brackets(cmdarg_T *cap); static void nv_percent(cmdarg_T *cap); @@ -2169,6 +2169,7 @@ find_decl( int flags_arg) // flags passed to searchit() { char_u *pat; + size_t patlen; pos_T old_pos; pos_T par_pos; pos_T found_pos; @@ -2185,8 +2186,9 @@ find_decl( // Put "\V" before the pattern to avoid that the special meaning of "." // and "~" causes trouble. - sprintf((char *)pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", - len, ptr); + patlen = vim_snprintf((char *)pat, len + 7, + vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", len, ptr); + old_pos = curwin->w_cursor; save_p_ws = p_ws; save_p_scs = p_scs; @@ -2215,7 +2217,7 @@ find_decl( for (;;) { t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD, - pat, 1L, searchflags, RE_LAST, NULL); + pat, patlen, 1L, searchflags, RE_LAST, NULL); if (curwin->w_cursor.lnum >= old_pos.lnum) t = FAIL; // match after start is failure too @@ -2593,7 +2595,7 @@ nv_zg_zw(cmdarg_T *cap, int nchar) // off this fails and find_ident_under_cursor() is // used below. emsg_off++; - len = spell_move_to(curwin, FORWARD, TRUE, TRUE, NULL); + len = spell_move_to(curwin, FORWARD, SMT_ALL, TRUE, NULL); emsg_off--; if (len != 0 && curwin->w_cursor.col <= pos.col) ptr = ml_get_pos(&curwin->w_cursor); @@ -3332,7 +3334,8 @@ nv_K_getcmd( char_u **ptr_arg, int n, char_u *buf, - unsigned buflen) + size_t bufsize, + size_t *buflen) { char_u *ptr = *ptr_arg; int isman; @@ -3342,6 +3345,7 @@ nv_K_getcmd( { // in the help buffer STRCPY(buf, "he! "); + *buflen = STRLEN_LITERAL("he! "); return n; } @@ -3349,10 +3353,9 @@ nv_K_getcmd( { // 'keywordprog' is an ex command if (cap->count0 != 0) - vim_snprintf((char *)buf, buflen, "%s %ld", kp, cap->count0); + *buflen = vim_snprintf((char *)buf, bufsize, "%s %ld ", kp, cap->count0); else - STRCPY(buf, kp); - STRCAT(buf, " "); + *buflen = vim_snprintf((char *)buf, bufsize, "%s ", kp); return n; } @@ -3377,19 +3380,16 @@ nv_K_getcmd( isman = (STRCMP(kp, "man") == 0); isman_s = (STRCMP(kp, "man -s") == 0); if (cap->count0 != 0 && !(isman || isman_s)) - sprintf((char *)buf, ".,.+%ld", cap->count0 - 1); + *buflen = vim_snprintf((char *)buf, bufsize, ".,.+%ld! ", cap->count0 - 1); + else + *buflen = vim_snprintf((char *)buf, bufsize, "! "); - STRCAT(buf, "! "); if (cap->count0 == 0 && isman_s) - STRCAT(buf, "man"); + *buflen += vim_snprintf((char *)buf + *buflen, bufsize - *buflen, "man "); else - STRCAT(buf, kp); - STRCAT(buf, " "); + *buflen += vim_snprintf((char *)buf + *buflen, bufsize - *buflen, "%s ", kp); if (cap->count0 != 0 && (isman || isman_s)) - { - sprintf((char *)buf + STRLEN(buf), "%ld", cap->count0); - STRCAT(buf, " "); - } + *buflen += vim_snprintf((char *)buf + *buflen, bufsize - *buflen, "%ld ", cap->count0); *ptr_arg = ptr; return n; @@ -3408,7 +3408,8 @@ nv_ident(cmdarg_T *cap) { char_u *ptr = NULL; char_u *buf; - unsigned buflen; + size_t bufsize; + size_t buflen; char_u *newbuf; char_u *p; char_u *kp; // value of 'keywordprg' @@ -3463,11 +3464,12 @@ nv_ident(cmdarg_T *cap) return; } kp_ex = (*kp == ':'); - buflen = (unsigned)(n * 2 + 30 + STRLEN(kp)); - buf = alloc(buflen); + bufsize = (size_t)(n * 2 + 30 + STRLEN(kp)); + buf = alloc(bufsize); if (buf == NULL) return; buf[0] = NUL; + buflen = 0; switch (cmdchar) { @@ -3481,12 +3483,15 @@ nv_ident(cmdarg_T *cap) curwin->w_cursor.col = (colnr_T) (ptr - ml_get_curline()); if (!g_cmd && vim_iswordp(ptr)) + { STRCPY(buf, "\\<"); + buflen = STRLEN_LITERAL("\\<"); + } no_smartcase = TRUE; // don't use 'smartcase' now break; case 'K': - n = nv_K_getcmd(cap, kp, kp_help, kp_ex, &ptr, n, buf, buflen); + n = nv_K_getcmd(cap, kp, kp_help, kp_ex, &ptr, n, buf, bufsize, &buflen); if (n == 0) return; break; @@ -3495,30 +3500,47 @@ nv_ident(cmdarg_T *cap) tag_cmd = TRUE; #ifdef FEAT_CSCOPE if (p_cst) + { STRCPY(buf, "cstag "); + buflen = STRLEN_LITERAL("cstag "); + } else #endif + { STRCPY(buf, "ts "); + buflen = STRLEN_LITERAL("ts "); + } break; default: tag_cmd = TRUE; if (curbuf->b_help) + { STRCPY(buf, "he! "); + buflen = STRLEN_LITERAL("he! "); + } else { if (g_cmd) + { STRCPY(buf, "tj "); + buflen = STRLEN_LITERAL("tj "); + } else if (cap->count0 == 0) + { STRCPY(buf, "ta "); + buflen = STRLEN_LITERAL("ta "); + } else - sprintf((char *)buf, ":%ldta ", cap->count0); + buflen = vim_snprintf((char *)buf, bufsize, ":%ldta ", cap->count0); } } // Now grab the chars in the identifier if (cmdchar == 'K' && !kp_help) { + size_t plen; + ptr = vim_strnsave(ptr, n); if (kp_ex) // Escape the argument properly for an Ex command @@ -3532,7 +3554,8 @@ nv_ident(cmdarg_T *cap) vim_free(buf); return; } - newbuf = vim_realloc(buf, STRLEN(buf) + STRLEN(p) + 1); + plen = STRLEN(p); + newbuf = vim_realloc(buf, buflen + plen + 1); if (newbuf == NULL) { vim_free(buf); @@ -3540,7 +3563,8 @@ nv_ident(cmdarg_T *cap) return; } buf = newbuf; - STRCAT(buf, p); + STRCPY(buf + buflen, p); + buflen += plen; vim_free(p); } else @@ -3560,12 +3584,13 @@ nv_ident(cmdarg_T *cap) else aux_ptr = (char_u *)"\\|\"\n*?["; - p = buf + STRLEN(buf); + p = buf + buflen; while (n-- > 0) { // put a backslash before \ and some others if (vim_strchr(aux_ptr, *ptr) != NULL) *p++ = '\\'; + // When current byte is a part of multibyte character, copy all // bytes of that character. if (has_mbyte) @@ -3579,6 +3604,7 @@ nv_ident(cmdarg_T *cap) *p++ = *ptr++; } *p = NUL; + buflen = p - buf; } // Execute the command. @@ -3587,13 +3613,16 @@ nv_ident(cmdarg_T *cap) if (!g_cmd && (has_mbyte ? vim_iswordp(mb_prevptr(ml_get_curline(), ptr)) : vim_iswordc(ptr[-1]))) - STRCAT(buf, "\\>"); + { + STRCPY(buf + buflen, "\\>"); + buflen += STRLEN_LITERAL("\\>"); + } // put pattern in search history init_history(); - add_to_history(HIST_SEARCH, buf, TRUE, NUL); + add_to_history(HIST_SEARCH, buf, buflen, TRUE, NUL); - (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0, NULL); + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, buflen, 0, NULL); } else { @@ -4117,7 +4146,7 @@ nv_search(cmdarg_T *cap) return; } - (void)normal_search(cap, cap->cmdchar, cap->searchbuf, + (void)normal_search(cap, cap->cmdchar, cap->searchbuf, STRLEN(cap->searchbuf), (cap->arg || !EQUAL_POS(save_cursor, curwin->w_cursor)) ? 0 : SEARCH_MARK, NULL); } @@ -4132,7 +4161,7 @@ nv_next(cmdarg_T *cap) { pos_T old = curwin->w_cursor; int wrapped = FALSE; - int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, &wrapped); + int i = normal_search(cap, 0, NULL, 0, SEARCH_MARK | cap->arg, &wrapped); if (i == 1 && !wrapped && EQUAL_POS(old, curwin->w_cursor)) { @@ -4140,7 +4169,7 @@ nv_next(cmdarg_T *cap) // happen when an offset is given and the cursor is on the last char // in the buffer: Repeat with count + 1. cap->count1 += 1; - (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, NULL); + (void)normal_search(cap, 0, NULL, 0, SEARCH_MARK | cap->arg, NULL); cap->count1 -= 1; } @@ -4161,6 +4190,7 @@ normal_search( cmdarg_T *cap, int dir, char_u *pat, + size_t patlen, int opt, // extra flags for do_search() int *wrapped) { @@ -4176,7 +4206,7 @@ normal_search( curwin->w_set_curswant = TRUE; CLEAR_FIELD(sia); - i = do_search(cap->oap, dir, dir, pat, cap->count1, + i = do_search(cap->oap, dir, dir, pat, patlen, cap->count1, opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) *wrapped = sia.sa_wrapped; @@ -4201,6 +4231,7 @@ normal_search( // "/$" will put the cursor after the end of the line, may need to // correct that here check_cursor(); + return i; } @@ -4529,13 +4560,15 @@ nv_brackets(cmdarg_T *cap) #endif #ifdef FEAT_SPELL - // "[s", "[S", "]s" and "]S": move to next spell error. - else if (cap->nchar == 's' || cap->nchar == 'S') + // "[r", "[s", "[S", "]r", "]s" and "]S": move to next spell error. + else if (cap->nchar == 'r' || cap->nchar == 's' || cap->nchar == 'S') { setpcmark(); for (n = 0; n < cap->count1; ++n) if (spell_move_to(curwin, cap->cmdchar == ']' ? FORWARD : BACKWARD, - cap->nchar == 's' ? TRUE : FALSE, FALSE, NULL) == 0) + cap->nchar == 's' ? SMT_ALL : + cap->nchar == 'r' ? SMT_RARE : + SMT_BAD, FALSE, NULL) == 0) { clearopbeep(cap->oap); break; @@ -6663,29 +6696,40 @@ adjust_for_sel(cmdarg_T *cap) int unadjust_for_sel(void) { - pos_T *pp; - if (*p_sel == 'e' && !EQUAL_POS(VIsual, curwin->w_cursor)) + return unadjust_for_sel_inner(LT_POS(VIsual, curwin->w_cursor) + ? &curwin->w_cursor : &VIsual); + return FALSE; +} + +/* + * Move position "*pp" back one character for 'selection' == "exclusive". + * Returns TRUE when backed up to the previous line. + */ + int +unadjust_for_sel_inner(pos_T *pp) +{ + colnr_T cs, ce; + + if (pp->coladd > 0) + --pp->coladd; + else if (pp->col > 0) { - if (LT_POS(VIsual, curwin->w_cursor)) - pp = &curwin->w_cursor; - else - pp = &VIsual; - if (pp->coladd > 0) - --pp->coladd; - else - if (pp->col > 0) - { - --pp->col; - mb_adjustpos(curbuf, pp); - } - else if (pp->lnum > 1) + --pp->col; + mb_adjustpos(curbuf, pp); + if (virtual_active()) { - --pp->lnum; - pp->col = ml_get_len(pp->lnum); - return TRUE; + getvcol(curwin, pp, &cs, NULL, &ce); + pp->coladd = ce - cs; } } + else if (pp->lnum > 1) + { + --pp->lnum; + pp->col = ml_get_len(pp->lnum); + return TRUE; + } + return FALSE; } @@ -231,7 +231,10 @@ shift_line( { vimlong_T count; int i, j; - int sw_val = trim_to_int(get_sw_value_indent(curbuf)); + int sw_val = trim_to_int(get_sw_value_indent(curbuf, left)); + + if (sw_val == 0) + sw_val = 1; // shouldn't happen, just in case count = get_indent(); // get current indent @@ -283,7 +286,7 @@ shift_block(oparg_T *oap, int amount) char_u *newp, *oldp; size_t newlen, oldlen; int oldcol = curwin->w_cursor.col; - int sw_val = (int)get_sw_value_indent(curbuf); + int sw_val = (int)get_sw_value_indent(curbuf, left); int ts_val = (int)curbuf->b_p_ts; struct block_def bd; int incr; @@ -580,7 +583,7 @@ block_insert( // copy the new text mch_memmove(newp + startcol, s, slen); - offset += slen; + offset += (int)slen; if (spaces > 0 && !bdp->is_short) { @@ -607,7 +610,7 @@ block_insert( if (b_insert) // correct any text properties - inserted_bytes(lnum, startcol, slen); + inserted_bytes(lnum, startcol, (int)slen); if (lnum == oap->end.lnum) { @@ -1258,8 +1261,8 @@ op_replace(oparg_T *oap, int c) replace_character(c); else PBYTE(curwin->w_cursor, c); - if (inc(&curwin->w_cursor) == -1) - break; + if (inc(&curwin->w_cursor) == -1) + break; } } @@ -1608,7 +1611,7 @@ op_insert(oparg_T *oap, long count1) if (oap->block_mode) { - size_t ins_len; + int ins_len; char_u *firstline, *ins_text; struct block_def bd2; int did_indent = FALSE; @@ -1722,7 +1725,7 @@ op_insert(oparg_T *oap, long count1) add = len; // short line, point to the NUL firstline += add; len -= add; - if (pre_textlen >= 0 && (ins_len = len - pre_textlen - offset) > 0) + if (pre_textlen >= 0 && (ins_len = (int)len - pre_textlen - offset) > 0) { ins_text = vim_strnsave(firstline, ins_len); if (ins_text != NULL) @@ -1811,7 +1814,7 @@ op_change(oparg_T *oap) */ if (oap->block_mode && oap->start.lnum != oap->end.lnum && !got_int) { - size_t ins_len; + int ins_len; // Auto-indenting may have changed the indent. If the cursor was past // the indent, exclude that indent change from the inserted text. @@ -1824,7 +1827,7 @@ op_change(oparg_T *oap) bd.textcol += new_indent - pre_indent; } - ins_len = ml_get_len(oap->start.lnum) - pre_textlen; + ins_len = (int)ml_get_len(oap->start.lnum) - pre_textlen; if (ins_len > 0) { // Subsequent calls to ml_get() flush the firstline data - take a @@ -1866,7 +1869,7 @@ op_change(oparg_T *oap) // Shift the properties for linenr as edit() would do. if (curbuf->b_has_textprop) adjust_prop_columns(linenr, bd.textcol, - vpos.coladd + ins_len, 0); + vpos.coladd + (int)ins_len, 0); #endif } } @@ -2444,13 +2447,14 @@ charwise_block_prep( int inclusive) { colnr_T startcol = 0, endcol = MAXCOL; - int is_oneChar = FALSE; colnr_T cs, ce; char_u *p; p = ml_get(lnum); bdp->startspaces = 0; bdp->endspaces = 0; + bdp->is_oneChar = FALSE; + bdp->start_char_vcols = 0; if (lnum == start.lnum) { @@ -2462,8 +2466,8 @@ charwise_block_prep( { // Part of a tab selected -- but don't // double-count it. - bdp->startspaces = (ce - cs + 1) - - start.coladd; + bdp->start_char_vcols = ce - cs + 1; + bdp->startspaces = bdp->start_char_vcols - start.coladd; if (bdp->startspaces < 0) bdp->startspaces = 0; startcol++; @@ -2483,19 +2487,16 @@ charwise_block_prep( // of multi-byte char. && (*mb_head_off)(p, p + endcol) == 0)) { - if (start.lnum == end.lnum - && start.col == end.col) + if (start.lnum == end.lnum && start.col == end.col) { // Special case: inside a single char - is_oneChar = TRUE; - bdp->startspaces = end.coladd - - start.coladd + inclusive; + bdp->is_oneChar = TRUE; + bdp->startspaces = end.coladd - start.coladd + inclusive; endcol = startcol; } else { - bdp->endspaces = end.coladd - + inclusive; + bdp->endspaces = end.coladd + inclusive; endcol -= inclusive; } } @@ -2503,10 +2504,11 @@ charwise_block_prep( } if (endcol == MAXCOL) endcol = ml_get_len(lnum); - if (startcol > endcol || is_oneChar) + if (startcol > endcol || bdp->is_oneChar) bdp->textlen = 0; else bdp->textlen = endcol - startcol + inclusive; + bdp->textcol = startcol; bdp->textstart = p + startcol; } @@ -2524,11 +2526,11 @@ op_addsub( int change_cnt = 0; linenr_T amount = Prenum1; - // do_addsub() might trigger re-evaluation of 'foldexpr' halfway, when the - // buffer is not completely updated yet. Postpone updating folds until before - // the call to changed_lines(). + // do_addsub() might trigger re-evaluation of 'foldexpr' halfway, when the + // buffer is not completely updated yet. Postpone updating folds until before + // the call to changed_lines(). #ifdef FEAT_FOLDING - disable_fold_update++; + disable_fold_update++; #endif if (!VIsual_active) diff --git a/src/option.c b/src/option.c index 0cd2823..b26c606 100644 --- a/src/option.c +++ b/src/option.c @@ -417,6 +417,14 @@ set_init_xdg_rtp(void) options[opt_idx].def_val[VI_DEFAULT] = xdg_rtp; p_pp = xdg_rtp; +#if defined(XDG_VDIR) && defined(FEAT_SESSION) + if ((opt_idx = findoption((char_u *)"viewdir")) < 0) + goto theend; + + options[opt_idx].def_val[VI_DEFAULT] = (char_u *)XDG_VDIR; + p_vdir = (char_u *)XDG_VDIR; +#endif + theend: vim_free(vimrc1); vim_free(vimrc2); @@ -6216,6 +6224,10 @@ unset_global_local_option(char_u *name, void *from) clear_string_option(&buf->b_p_inc); break; # endif + case PV_COT: + clear_string_option(&buf->b_p_cot); + buf->b_cot_flags = 0; + break; case PV_DICT: clear_string_option(&buf->b_p_dict); break; @@ -6325,6 +6337,7 @@ get_varp_scope(struct vimoption *p, int scope) case PV_DEF: return (char_u *)&(curbuf->b_p_def); case PV_INC: return (char_u *)&(curbuf->b_p_inc); #endif + case PV_COT: return (char_u *)&(curbuf->b_p_cot); case PV_DICT: return (char_u *)&(curbuf->b_p_dict); case PV_TSR: return (char_u *)&(curbuf->b_p_tsr); #ifdef FEAT_COMPL_FUNC @@ -6405,6 +6418,8 @@ get_varp(struct vimoption *p) case PV_INC: return *curbuf->b_p_inc != NUL ? (char_u *)&(curbuf->b_p_inc) : p->var; #endif + case PV_COT: return *curbuf->b_p_cot != NUL + ? (char_u *)&(curbuf->b_p_cot) : p->var; case PV_DICT: return *curbuf->b_p_dict != NUL ? (char_u *)&(curbuf->b_p_dict) : p->var; case PV_TSR: return *curbuf->b_p_tsr != NUL @@ -7197,6 +7212,8 @@ buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_INEX); # endif #endif + buf->b_p_cot = empty_option; + buf->b_cot_flags = 0; buf->b_p_dict = empty_option; buf->b_p_tsr = empty_option; #ifdef FEAT_COMPL_FUNC @@ -8347,10 +8364,10 @@ get_sidescrolloff_value(void) } /* - * Get the local or global value of 'backupcopy'. + * Get the local or global value of 'backupcopy' flags. */ unsigned int -get_bkc_value(buf_T *buf) +get_bkc_flags(buf_T *buf) { return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } @@ -8369,7 +8386,7 @@ get_flp_value(buf_T *buf) #endif /* - * Get the local or global value of the 'virtualedit' flags. + * Get the local or global value of 'virtualedit' flags. */ unsigned int get_ve_flags(void) diff --git a/src/option.h b/src/option.h index bf889e4..91d8d95 100644 --- a/src/option.h +++ b/src/option.h @@ -513,6 +513,20 @@ EXTERN int p_confirm; // 'confirm' #endif EXTERN int p_cp; // 'compatible' EXTERN char_u *p_cot; // 'completeopt' +EXTERN unsigned cot_flags; // flags from 'completeopt' +// Keep in sync with p_cot_values in optionstr.c +#define COT_MENU 0x001 +#define COT_MENUONE 0x002 +#define COT_ANY_MENU 0x003 // combination of menu flags +#define COT_LONGEST 0x004 // FALSE: insert full match, + // TRUE: insert longest prefix +#define COT_PREVIEW 0x008 +#define COT_POPUP 0x010 +#define COT_POPUPHIDDEN 0x020 +#define COT_ANY_PREVIEW 0x038 // combination of preview flags +#define COT_NOINSERT 0x040 // FALSE: select & insert, TRUE: noinsert +#define COT_NOSELECT 0x080 // FALSE: select & insert, TRUE: noselect +#define COT_FUZZY 0x100 // TRUE: fuzzy match enabled #ifdef BACKSLASH_IN_FILENAME EXTERN char_u *p_csl; // 'completeslash' #endif @@ -1127,6 +1141,7 @@ enum , BV_CMS #endif , BV_COM + , BV_COT , BV_CPT , BV_DICT , BV_TSR diff --git a/src/optiondefs.h b/src/optiondefs.h index 33e3165..617f3ce 100644 --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -50,6 +50,7 @@ # define PV_CMS OPT_BUF(BV_CMS) #endif #define PV_COM OPT_BUF(BV_COM) +#define PV_COT OPT_BOTH(OPT_BUF(BV_COT)) #define PV_CPT OPT_BUF(BV_CPT) #define PV_DICT OPT_BOTH(OPT_BUF(BV_DICT)) #define PV_TSR OPT_BOTH(OPT_BUF(BV_TSR)) @@ -300,7 +301,7 @@ struct vimoption # define ISP_LATIN1 (char_u *)"@,161-255" #endif -# define HIGHLIGHT_INIT "8:SpecialKey,~:EndOfBuffer,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm,Z:StatusLineTermNC,g:MsgArea" +# define HIGHLIGHT_INIT "8:SpecialKey,~:EndOfBuffer,@:NonText,d:Directory,e:ErrorMsg,i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm,Z:StatusLineTermNC,g:MsgArea" // Default python version for pyx* commands #if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3) @@ -629,7 +630,7 @@ static struct vimoption options[] = {"commentstring", "cms", P_STRING|P_ALLOCED|P_VI_DEF|P_CURSWANT, #ifdef FEAT_FOLDING (char_u *)&p_cms, PV_CMS, did_set_commentstring, NULL, - {(char_u *)"/*%s*/", (char_u *)0L} + {(char_u *)"/* %s */", (char_u *)0L} #else (char_u *)NULL, PV_NONE, NULL, NULL, {(char_u *)0L, (char_u *)0L} @@ -654,7 +655,7 @@ static struct vimoption options[] = #endif SCTX_INIT}, {"completeopt", "cot", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP, - (char_u *)&p_cot, PV_NONE, did_set_completeopt, expand_set_completeopt, + (char_u *)&p_cot, PV_COT, did_set_completeopt, expand_set_completeopt, {(char_u *)"menu,preview", (char_u *)0L} SCTX_INIT}, {"completepopup", "cpp", P_STRING|P_VI_DEF|P_COMMA|P_NODUP|P_COLON, diff --git a/src/optionstr.c b/src/optionstr.c index 45f126f..d722981 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -118,7 +118,7 @@ static char *(p_fdm_values[]) = {"manual", "expr", "marker", "indent", "syntax", NULL}; static char *(p_fcl_values[]) = {"all", NULL}; #endif -static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", "popup", "popuphidden", "noinsert", "noselect", NULL}; +static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", "popup", "popuphidden", "noinsert", "noselect", "fuzzy", NULL}; #ifdef BACKSLASH_IN_FILENAME static char *(p_csl_values[]) = {"slash", "backslash", NULL}; #endif @@ -144,6 +144,7 @@ didset_string_options(void) (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, TRUE); (void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, TRUE); (void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, TRUE); + (void)opt_strings_flags(p_cot, p_cot_values, &cot_flags, TRUE); #ifdef FEAT_SESSION (void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, TRUE); (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, TRUE); @@ -301,6 +302,7 @@ check_buf_options(buf_T *buf) check_string_option(&buf->b_p_lop); check_string_option(&buf->b_p_ft); check_string_option(&buf->b_p_cinw); + check_string_option(&buf->b_p_cot); check_string_option(&buf->b_p_cpt); #ifdef FEAT_COMPL_FUNC check_string_option(&buf->b_p_cfu); @@ -1601,10 +1603,21 @@ expand_set_complete(optexpand_T *args, int *numMatches, char_u ***matches) char * did_set_completeopt(optset_T *args UNUSED) { - if (check_opt_strings(p_cot, p_cot_values, TRUE) != OK) + char_u *cot = p_cot; + unsigned *flags = &cot_flags; + + if (args->os_flags & OPT_LOCAL) + { + cot = curbuf->b_p_cot; + flags = &curbuf->b_cot_flags; + } + + if (check_opt_strings(cot, p_cot_values, TRUE) != OK) + return e_invalid_argument; + + if (opt_strings_flags(cot, p_cot_values, flags, TRUE) != OK) return e_invalid_argument; - completeopt_was_set(); return NULL; } diff --git a/src/os_unix.h b/src/os_unix.h index 6efd8ce..99184ab 100644 --- a/src/os_unix.h +++ b/src/os_unix.h @@ -347,6 +347,8 @@ typedef struct dsc$descriptor DESC; # define DFLT_VDIR "sys$login:vimfiles/view" # else # define DFLT_VDIR "$HOME/.vim/view" // default for 'viewdir' +# define XDG_VDIR (mch_getenv("XDG_CONFIG_HOME") ? \ + "$XDG_CONFIG_HOME/vim/view" : "~/.config/vim/view") # endif #endif diff --git a/src/po/check.vim b/src/po/check.vim index b0460f1..2ea4a38 100644 --- a/src/po/check.vim +++ b/src/po/check.vim @@ -226,8 +226,8 @@ elseif ctu " endif endif -" Check that all lines are no longer than 80 chars -let overlong = search('\%>80v', 'n') +" Check that no lines are longer than 80 chars (except comments) +let overlong = search('^[^#]\%>80v', 'n') if overlong > 0 echomsg "Lines should be wrapped at 80 columns" " TODO: make this an error diff --git a/src/po/it.po b/src/po/it.po index 8bdc5e3..fde1c23 100644 --- a/src/po/it.po +++ b/src/po/it.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-06 09:13+0100\n" +"POT-Creation-Date: 2024-05-28 09:50+0200\n" "PO-Revision-Date: 2024-03-06 15:00+0100\n" "Last-Translator: Antonio Colombo <azc100@gmail.com>\n" "Language-Team: Italian\n" @@ -421,9 +421,6 @@ msgstr "Katakana" msgid "Bopomofo" msgstr "Bopomofo" -msgid "Not enough memory to set references, garbage collection aborted!" -msgstr "Memoria insufficiente per impostarlo, recupero memoria fallito!" - msgid "" "\n" "\tLast set from " @@ -810,6 +807,9 @@ msgid_plural "+-%s%3ld lines: " msgstr[0] "+-%s%3ld riga: " msgstr[1] "+-%s%3ld righe: " +msgid "Not enough memory to set references, garbage collection aborted!" +msgstr "Memoria insufficiente per impostarlo, recupero memoria fallito!" + msgid "No match at cursor, finding next" msgstr "Nessuna corrispondenza al cursore, cerco la prossima" @@ -3419,6 +3419,9 @@ msgstr " II file vimrc utente: \"" msgid " 3rd user vimrc file: \"" msgstr " III file vimrc utente: \"" +msgid " 4th user vimrc file: \"" +msgstr " IV file vimrc utente: \"" + msgid " user exrc file: \"" msgstr " file exrc utente: \"" @@ -6351,6 +6354,9 @@ msgstr "" "E876: (Espressione regolare NFA) Non c'è spazio sufficiente a immagazzinare " "l'intero NFA" +msgid "E877: (NFA regexp) Invalid character class: %d" +msgstr "E877: (Espressione regolare NFA) Classe di caratteri non valida: %d" + msgid "E878: (NFA regexp) Could not allocate memory for branch traversal!" msgstr "" "E878: (Espressione regolare NFA) Non posso allocare memoria per lo zigzag di " @@ -7780,13 +7786,15 @@ msgid "E1330: Invalid type for object variable: %s" msgstr "E1330: Tipo non valido per variabile Object: %s" msgid "" -"E1331: Public must be followed by \"var\" or \"static\" or \"final\" or " +"E1331: public must be followed by \"var\" or \"static\" or \"final\" or " +"\"const\"" +msgstr "" +"E1331: public dev'essere seguito da \"var\" o \"static\" o \"final\" o " "\"const\"" -msgstr "E1331: Public dev'essere seguito da \"var\" o \"static\" o \"const\"" -msgid "E1332: Public variable name cannot start with underscore: %s" +msgid "E1332: public variable name cannot start with underscore: %s" msgstr "" -"E1332: Il nome di un elemento Pubblico non può iniziare con un trattino " +"E1332: Il nome di un elemento public non può iniziare con il trattino " "basso: %s" msgid "E1333: Cannot access protected variable \"%s\" in class \"%s\"" @@ -7967,10 +7975,10 @@ msgid "E1386: Object method \"%s\" accessible only using class \"%s\" object" msgstr "" "E1386: Metodo Object \"%s\" accessibile solo usando la Classe \"%s\" object" -msgid "E1387: Public variable not supported in an interface" -msgstr "E1387: Variabile pubblica non supportata in un'Interfaccia" +msgid "E1387: public variable not supported in an interface" +msgstr "E1387: Variabile public non supportata in un'Interfaccia" -msgid "E1388: Public keyword not supported for a method" +msgid "E1388: public keyword not supported for a method" msgstr "E1388: Attributo public non supportato per un Metodo" msgid "E1389: Missing name after implements" @@ -8054,6 +8062,54 @@ msgstr "E1412: Metodo Object predefinito \"%s\" non supportato" msgid "E1413: Builtin class method not supported" msgstr "E1413: Metodo Classe predefinito non supportato" +msgid "E1414: Enum can only be defined in Vim9 script" +msgstr "E1414: Enum può essere usato solo negli script Vim9" + +msgid "E1415: Enum name must start with an uppercase letter: %s" +msgstr "E1415: Il nome di Enum deve iniziare con una lettera maiuscola: %s" + +msgid "E1416: Enum cannot extend a class or enum" +msgstr "E1416: Enum non può estendere una Classe o un Enum" + +msgid "E1417: Abstract cannot be used in an Enum" +msgstr "E1417: Impossibile usare Abstract in un Enum" + +msgid "E1418: Invalid enum value declaration: %s" +msgstr "E1418: Dichiarazione di variabile Enum non valida: %s" + +msgid "E1419: Not a valid command in an Enum: %s" +msgstr "E1419: Comando non valido in un Enum: %s" + +msgid "E1420: Missing :endenum" +msgstr "E1420: Manca :endenum" + +msgid "E1421: Enum \"%s\" cannot be used as a value" +msgstr "E1421: Impossibile usare come valore Enum \"%s\"" + +msgid "E1422: Enum value \"%s\" not found in enum \"%s\"" +msgstr "E1422: Valore Enum \"%s\" non trovato in Enum \"%s\"" + +msgid "E1423: Enum value \"%s.%s\" cannot be modified" +msgstr "E1423: Valore Enum \"%s.%s\" non può essere modificato" + +msgid "E1424: Using an Enum \"%s\" as a Number" +msgstr "E1424: Uso dell'Enum \"%s\" come un Numero" + +msgid "E1425: Using an Enum \"%s\" as a String" +msgstr "E1425: Uso di un Enum \"%s\" come una Stringa" + +msgid "E1426: Enum \"%s\" ordinal value cannot be modified" +msgstr "E1426: Il valore ordinale di Enum \"%s\" non può essere modificato" + +msgid "E1427: Enum \"%s\" name cannot be modified" +msgstr "E1427: Il nome di Enum \"%s\" non può essere modificato" + +msgid "E1428: Duplicate enum value: %s" +msgstr "E1428: Valore di Enum duplicato: %s" + +msgid "E1429: Class can only be used in a script" +msgstr "E1429: Class si può usare solo in uno script" + msgid "E1500: Cannot mix positional and non-positional arguments: %s" msgstr "" "E1500: Non si possono mischiare argomenti posizionali e non posizionali: %s" @@ -8101,8 +8157,9 @@ msgstr "E1511: Numero caratteri errato per campo \"%s\"" msgid "E1512: Wrong character width for field \"%s\"" msgstr "E1512: Larghezza carattere errata per campo \"%s\"" -msgid "E1513: Cannot edit buffer. 'winfixbuf' is enabled" -msgstr "E1513: Non riesco a modificare il buffer. Opzione 'winfixbuf' attiva" +msgid "E1513: Cannot switch buffer. 'winfixbuf' is enabled" +msgstr "E1513: Non riesco a passare a un altro buffer. Opzione " +"'winfixbuf' attiva" #. type of cmdline window or 0 #. result of cmdline window or 0 diff --git a/src/popupmenu.c b/src/popupmenu.c index 7419806..6e9d826 100644 --- a/src/popupmenu.c +++ b/src/popupmenu.c @@ -26,6 +26,9 @@ static int pum_base_width; // width of pum items base static int pum_kind_width; // width of pum items kind column static int pum_extra_width; // width of extra stuff static int pum_scrollbar; // TRUE when scrollbar present +#ifdef FEAT_RIGHTLEFT +static int pum_rl; // TRUE when pum is drawn 'rightleft' +#endif static int pum_row; // top row of pum static int pum_col; // left column of pum @@ -101,8 +104,9 @@ pum_display( #if defined(FEAT_QUICKFIX) win_T *pvwin; #endif + #ifdef FEAT_RIGHTLEFT - int right_left = State == MODE_CMDLINE ? FALSE : curwin->w_p_rl; + pum_rl = State != MODE_CMDLINE && curwin->w_p_rl; #endif do @@ -243,7 +247,7 @@ pum_display( // w_wcol includes virtual text "above" int wcol = curwin->w_wcol % curwin->w_width; #ifdef FEAT_RIGHTLEFT - if (right_left) + if (pum_rl) cursor_col = curwin->w_wincol + curwin->w_width - wcol - 1; else #endif @@ -264,8 +268,8 @@ pum_display( if (((cursor_col < Columns - p_pw || cursor_col < Columns - max_width) #ifdef FEAT_RIGHTLEFT - && !right_left) - || (right_left && (cursor_col > p_pw || cursor_col > max_width) + && !pum_rl) + || (pum_rl && (cursor_col > p_pw || cursor_col > max_width) #endif )) { @@ -274,7 +278,7 @@ pum_display( // start with the maximum space available #ifdef FEAT_RIGHTLEFT - if (right_left) + if (pum_rl) pum_width = pum_col - pum_scrollbar + 1; else #endif @@ -291,22 +295,22 @@ pum_display( } else if (((cursor_col > p_pw || cursor_col > max_width) #ifdef FEAT_RIGHTLEFT - && !right_left) - || (right_left && (cursor_col < Columns - p_pw + && !pum_rl) + || (pum_rl && (cursor_col < Columns - p_pw || cursor_col < Columns - max_width) #endif )) { // align pum edge with "cursor_col" #ifdef FEAT_RIGHTLEFT - if (right_left + if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { pum_col = cursor_col + max_width + pum_scrollbar + 1; if (pum_col >= Columns) pum_col = Columns - 1; } - else if (!right_left) + else if (!pum_rl) #endif { if (curwin->w_wincol > Columns - max_width - pum_scrollbar @@ -320,7 +324,7 @@ pum_display( } #ifdef FEAT_RIGHTLEFT - if (right_left) + if (pum_rl) pum_width = pum_col - pum_scrollbar + 1; else #endif @@ -330,7 +334,7 @@ pum_display( { pum_width = p_pw; #ifdef FEAT_RIGHTLEFT - if (right_left) + if (pum_rl) { if (pum_width > pum_col) pum_width = pum_col; @@ -358,7 +362,7 @@ pum_display( { // not enough room, will use what we have #ifdef FEAT_RIGHTLEFT - if (right_left) + if (pum_rl) pum_col = Columns - 1; else #endif @@ -370,7 +374,7 @@ pum_display( if (max_width > p_pw) max_width = p_pw; // truncate #ifdef FEAT_RIGHTLEFT - if (right_left) + if (pum_rl) pum_col = max_width - 1; else #endif @@ -417,6 +421,119 @@ pum_under_menu(int row, int col, int only_redrawing) } /* + * Computes attributes of text on the popup menu. + * Returns attributes for every cell, or NULL if all attributes are the same. + */ + static int * +pum_compute_text_attrs(char_u *text, hlf_T hlf) +{ + int i; + size_t leader_len; + int char_cells; + int new_attr; + char_u *ptr = text; + int cell_idx = 0; + garray_T *ga = NULL; + int *attrs = NULL; + char_u *leader = NULL; + int in_fuzzy; + int matched_start = FALSE; + int_u char_pos = 0; + + if ((hlf != HLF_PSI && hlf != HLF_PNI) + || (highlight_attr[HLF_PMSI] == highlight_attr[HLF_PSI] + && highlight_attr[HLF_PMNI] == highlight_attr[HLF_PNI])) + return NULL; + + leader = State == MODE_CMDLINE ? cmdline_compl_pattern() + : ins_compl_leader(); + if (leader == NULL || *leader == NUL) + return NULL; + + attrs = ALLOC_MULT(int, vim_strsize(text)); + if (attrs == NULL) + return NULL; + + in_fuzzy = State == MODE_CMDLINE ? cmdline_compl_is_fuzzy() + : (get_cot_flags() & COT_FUZZY) != 0; + leader_len = STRLEN(leader); + + if (in_fuzzy) + ga = fuzzy_match_str_with_pos(text, leader); + else + matched_start = MB_STRNICMP(text, leader, leader_len) == 0; + + while (*ptr != NUL) + { + new_attr = highlight_attr[hlf]; + + if (ga != NULL) + { + // Handle fuzzy matching + for (i = 0; i < ga->ga_len; i++) + { + if (char_pos == ((int_u *)ga->ga_data)[i]) + { + new_attr = highlight_attr[hlf == HLF_PSI + ? HLF_PMSI : HLF_PMNI]; + break; + } + } + } + else if (matched_start && ptr < text + leader_len) + new_attr = highlight_attr[hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI]; + + char_cells = mb_ptr2cells(ptr); + for (i = 0; i < char_cells; i++) + attrs[cell_idx + i] = new_attr; + cell_idx += char_cells; + + MB_PTR_ADV(ptr); + char_pos++; + } + + if (ga != NULL) + { + ga_clear(ga); + vim_free(ga); + } + return attrs; +} + +/* + * Displays text on the popup menu with specific attributes. + */ + static void +pum_screen_puts_with_attrs( + int row, + int col, + int cells UNUSED, + char_u *text, + int textlen, + int *attrs) +{ + int col_start = col; + char_u *ptr = text; + int char_len; + int attr; + + // Render text with proper attributes + while (*ptr != NUL && ptr < text + textlen) + { + char_len = mb_ptr2len(ptr); +#ifdef FEAT_RIGHTLEFT + if (pum_rl) + attr = attrs[col_start + cells - col - 1]; + else +#endif + attr = attrs[col - col_start]; + screen_puts_len(ptr, char_len, row, col, attr); + col += mb_ptr2cells(ptr); + ptr += char_len; + } +} + +/* * Redraw the popup menu, using "pum_first" and "pum_selected". */ void @@ -426,8 +543,9 @@ pum_redraw(void) int col; int attr_scroll = highlight_attr[HLF_PSB]; int attr_thumb = highlight_attr[HLF_PST]; + hlf_T *hlfs; // array used for highlights + hlf_T hlf; int attr; - int *attrs; // array used for highlights int i; int idx; char_u *s; @@ -438,17 +556,17 @@ pum_redraw(void) int round; int n; - int attrsNorm[3]; - int attrsSel[3]; + hlf_T hlfsNorm[3]; + hlf_T hlfsSel[3]; // "word" - attrsNorm[0] = highlight_attr[HLF_PNI]; - attrsSel[0] = highlight_attr[HLF_PSI]; + hlfsNorm[0] = HLF_PNI; + hlfsSel[0] = HLF_PSI; // "kind" - attrsNorm[1] = highlight_attr[HLF_PNK]; - attrsSel[1] = highlight_attr[HLF_PSK]; + hlfsNorm[1] = HLF_PNK; + hlfsSel[1] = HLF_PSK; // "extra text" - attrsNorm[2] = highlight_attr[HLF_PNX]; - attrsSel[2] = highlight_attr[HLF_PSX]; + hlfsNorm[2] = HLF_PNX; + hlfsSel[2] = HLF_PSX; if (call_update_screen) { @@ -483,12 +601,13 @@ pum_redraw(void) for (i = 0; i < pum_height; ++i) { idx = i + pum_first; - attrs = (idx == pum_selected) ? attrsSel : attrsNorm; - attr = attrs[0]; // start with "word" highlight + hlfs = (idx == pum_selected) ? hlfsSel : hlfsNorm; + hlf = hlfs[0]; // start with "word" highlight + attr = highlight_attr[hlf]; // prepend a space if there is room #ifdef FEAT_RIGHTLEFT - if (curwin->w_p_rl) + if (pum_rl) { if (pum_col < curwin->w_wincol + curwin->w_width - 1) screen_putchar(' ', row, pum_col + 1, attr); @@ -507,7 +626,8 @@ pum_redraw(void) totwidth = 0; for (round = 0; round < 3; ++round) { - attr = attrs[round]; + hlf = hlfs[round]; + attr = highlight_attr[hlf]; width = 0; s = NULL; switch (round) @@ -527,6 +647,7 @@ pum_redraw(void) // Display the text that fits or comes before a Tab. // First convert it to printable characters. char_u *st; + int *attrs; int saved = *p; if (saved != NUL) @@ -534,8 +655,11 @@ pum_redraw(void) st = transstr(s); if (saved != NUL) *p = saved; + + attrs = pum_compute_text_attrs(st, hlf); + #ifdef FEAT_RIGHTLEFT - if (curwin->w_p_rl) + if (pum_rl) { if (st != NULL) { @@ -544,19 +668,19 @@ pum_redraw(void) if (rt != NULL) { char_u *rt_start = rt; - int size; + int cells; - size = vim_strsize(rt); - if (size > pum_width) + cells = vim_strsize(rt); + if (cells > pum_width) { do { - size -= has_mbyte - ? (*mb_ptr2cells)(rt) : 1; + cells -= has_mbyte + ? (*mb_ptr2cells)(rt) : 1; MB_PTR_ADV(rt); - } while (size > pum_width); + } while (cells > pum_width); - if (size < pum_width) + if (cells < pum_width) { // Most left character requires // 2-cells but only 1 cell is @@ -564,11 +688,18 @@ pum_redraw(void) // '<' on the left of the pum // item *(--rt) = '<'; - size++; + cells++; } } - screen_puts_len(rt, (int)STRLEN(rt), - row, col - size + 1, attr); + + if (attrs == NULL) + screen_puts_len(rt, (int)STRLEN(rt), + row, col - cells + 1, attr); + else + pum_screen_puts_with_attrs(row, + col - cells + 1, cells, rt, + (int)STRLEN(rt), attrs); + vim_free(rt_start); } vim_free(st); @@ -596,18 +727,26 @@ pum_redraw(void) else --cells; } - screen_puts_len(st, size, row, col, attr); + + if (attrs == NULL) + screen_puts_len(st, size, row, col, attr); + else + pum_screen_puts_with_attrs(row, col, cells, + st, size, attrs); + vim_free(st); } col += width; } + vim_free(attrs); + if (*p != TAB) break; // Display two spaces for a Tab. #ifdef FEAT_RIGHTLEFT - if (curwin->w_p_rl) + if (pum_rl) { screen_puts_len((char_u *)" ", 2, row, col - 1, attr); @@ -640,11 +779,11 @@ pum_redraw(void) || pum_base_width + n >= pum_width) break; #ifdef FEAT_RIGHTLEFT - if (curwin->w_p_rl) + if (pum_rl) { screen_fill(row, row + 1, pum_col - pum_base_width - n + 1, col + 1, ' ', ' ', attr); - col = pum_col - pum_base_width - n + 1; + col = pum_col - pum_base_width - n; } else #endif @@ -657,7 +796,7 @@ pum_redraw(void) } #ifdef FEAT_RIGHTLEFT - if (curwin->w_p_rl) + if (pum_rl) screen_fill(row, row + 1, pum_col - pum_width + 1, col + 1, ' ', ' ', attr); else @@ -667,7 +806,7 @@ pum_redraw(void) if (pum_scrollbar > 0) { #ifdef FEAT_RIGHTLEFT - if (curwin->w_p_rl) + if (pum_rl) screen_putchar(' ', row, pum_col - pum_width, i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); @@ -760,6 +899,7 @@ pum_set_selected(int n, int repeat UNUSED) int context = pum_height / 2; #ifdef FEAT_QUICKFIX int prev_selected = pum_selected; + unsigned cur_cot_flags = get_cot_flags(); #endif #if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX) int has_info = FALSE; @@ -831,7 +971,7 @@ pum_set_selected(int n, int repeat UNUSED) if (pum_array[pum_selected].pum_info != NULL && Rows > 10 && repeat <= 1 - && vim_strchr(p_cot, 'p') != NULL) + && (cur_cot_flags & COT_ANY_PREVIEW)) { win_T *curwin_save = curwin; tabpage_T *curtab_save = curtab; @@ -842,9 +982,9 @@ pum_set_selected(int n, int repeat UNUSED) # endif # ifdef FEAT_PROP_POPUP has_info = TRUE; - if (strstr((char *)p_cot, "popuphidden") != NULL) + if (cur_cot_flags & COT_POPUPHIDDEN) use_popup = USEPOPUP_HIDDEN; - else if (strstr((char *)p_cot, "popup") != NULL) + else if (cur_cot_flags & COT_POPUP) use_popup = USEPOPUP_NORMAL; else use_popup = USEPOPUP_NONE; @@ -1211,16 +1351,34 @@ pum_position_at_mouse(int min_width) pum_row = 0; } } - if (Columns - mouse_col >= pum_base_width - || Columns - mouse_col > min_width) - // Enough space to show at mouse column. - pum_col = mouse_col; + +# ifdef FEAT_RIGHTLEFT + if (pum_rl) + { + if (mouse_col + 1 >= pum_base_width + || mouse_col + 1 > min_width) + // Enough space to show at mouse column. + pum_col = mouse_col; + else + // Not enough space, left align with window. + pum_col = (pum_base_width > min_width + ? min_width : pum_base_width) - 1; + pum_width = pum_col + 1; + } else - // Not enough space, right align with window. - pum_col = Columns - (pum_base_width > min_width +# endif + { + if (Columns - mouse_col >= pum_base_width + || Columns - mouse_col > min_width) + // Enough space to show at mouse column. + pum_col = mouse_col; + else + // Not enough space, right align with window. + pum_col = Columns - (pum_base_width > min_width ? min_width : pum_base_width); + pum_width = Columns - pum_col; + } - pum_width = Columns - pum_col; if (pum_width > pum_base_width + 1) pum_width = pum_base_width + 1; @@ -1444,6 +1602,9 @@ ui_post_balloon(char_u *mesg, list_T *list) pum_compute_size(); pum_scrollbar = 0; pum_height = balloon_arraysize; +# ifdef FEAT_RIGHTLEFT + pum_rl = curwin->w_p_rl; +# endif pum_position_at_mouse(BALLOON_MIN_WIDTH); pum_selected = -1; @@ -1554,6 +1715,9 @@ pum_show_popupmenu(vimmenu_T *menu) pum_compute_size(); pum_scrollbar = 0; pum_height = pum_size; +# ifdef FEAT_RIGHTLEFT + pum_rl = curwin->w_p_rl; +# endif pum_position_at_mouse(20); pum_selected = -1; @@ -1649,7 +1813,11 @@ pum_make_popup(char_u *path_name, int use_mouse_pos) // Hack: set mouse position at the cursor so that the menu pops up // around there. mouse_row = W_WINROW(curwin) + curwin->w_wrow; - mouse_col = curwin->w_wincol + curwin->w_wcol; + mouse_col = +# ifdef FEAT_RIGHTLEFT + curwin->w_p_rl ? W_ENDCOL(curwin) - curwin->w_wcol - 1 : +# endif + curwin->w_wincol + curwin->w_wcol; } menu = gui_find_menu(path_name); diff --git a/src/popupwin.c b/src/popupwin.c index 25bb153..38c1c9e 100644 --- a/src/popupwin.c +++ b/src/popupwin.c @@ -654,8 +654,8 @@ popup_show_curline(win_T *wp) wp->w_topline = wp->w_buffer->b_ml.ml_line_count; while (wp->w_topline < wp->w_cursor.lnum && wp->w_topline < wp->w_buffer->b_ml.ml_line_count - && plines_m_win(wp, wp->w_topline, wp->w_cursor.lnum, FALSE) - > wp->w_height) + && plines_m_win(wp, wp->w_topline, wp->w_cursor.lnum, + wp->w_height + 1) > wp->w_height) ++wp->w_topline; } diff --git a/src/proto.h b/src/proto.h index 50802ce..94e34b0 100644 --- a/src/proto.h +++ b/src/proto.h @@ -94,6 +94,7 @@ extern int _stricoll(char *a, char *b); # include "float.pro" # include "fold.pro" # include "getchar.pro" +# include "gc.pro" # include "gui_xim.pro" # include "hardcopy.pro" # include "hashtab.pro" diff --git a/src/proto/cmdexpand.pro b/src/proto/cmdexpand.pro index 100c23d..dfa6d1b 100644 --- a/src/proto/cmdexpand.pro +++ b/src/proto/cmdexpand.pro @@ -6,6 +6,8 @@ int cmdline_pum_active(void); void cmdline_pum_remove(void); void cmdline_pum_cleanup(cmdline_info_T *cclp); int cmdline_compl_startcol(void); +char_u *cmdline_compl_pattern(void); +int cmdline_compl_is_fuzzy(void); char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode); void ExpandInit(expand_T *xp); void ExpandCleanup(expand_T *xp); diff --git a/src/proto/cmdhist.pro b/src/proto/cmdhist.pro index 9c9e56c..5bf4b8d 100644 --- a/src/proto/cmdhist.pro +++ b/src/proto/cmdhist.pro @@ -9,7 +9,7 @@ char_u *get_history_arg(expand_T *xp, int idx); void init_history(void); void clear_hist_entry(histentry_T *hisptr); int in_history(int type, char_u *str, int move_to_front, int sep, int writing); -void add_to_history(int histype, char_u *new_entry, int in_map, int sep); +void add_to_history(int histype, char_u *new_entry, size_t new_entrylen, int in_map, int sep); void f_histadd(typval_T *argvars, typval_T *rettv); void f_histdel(typval_T *argvars, typval_T *rettv); void f_histget(typval_T *argvars, typval_T *rettv); diff --git a/src/proto/dict.pro b/src/proto/dict.pro index fdccca5..346e1d5 100644 --- a/src/proto/dict.pro +++ b/src/proto/dict.pro @@ -37,6 +37,7 @@ varnumber_T dict_get_bool(dict_T *d, char *key, int def); char_u *dict2string(typval_T *tv, int copyID, int restore_copyID); char_u *get_literal_key(char_u **arg); int eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal); +int eval_lit_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg); void dict_extend(dict_T *d1, dict_T *d2, char_u *action, char *func_name); dictitem_T *dict_lookup(hashitem_T *hi); int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive); diff --git a/src/proto/eval.pro b/src/proto/eval.pro index 1c2d05d..7247265 100644 --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -48,19 +48,9 @@ int eval_addlist(typval_T *tv1, typval_T *tv2); int eval_leader(char_u **arg, int vim9); int handle_predefined(char_u *s, int len, typval_T *rettv); int check_can_index(typval_T *rettv, int evaluate, int verbose); -void f_slice(typval_T *argvars, typval_T *rettv); int eval_index_inner(typval_T *rettv, int is_range, typval_T *var1, typval_T *var2, int exclusive, char_u *key, int keylen, int verbose); char_u *partial_name(partial_T *pt); void partial_unref(partial_T *pt); -int get_copyID(void); -int garbage_collect(int testing); -int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack); -int set_ref_in_dict(dict_T *d, int copyID); -int set_ref_in_list(list_T *ll, int copyID); -int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack); -int set_ref_in_callback(callback_T *cb, int copyID); -int set_ref_in_item_class(class_T *cl, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); -int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val); char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx); diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro index 3fd20b2..3bd7705 100644 --- a/src/proto/ex_docmd.pro +++ b/src/proto/ex_docmd.pro @@ -66,8 +66,8 @@ void restore_current_state(save_state_T *sst); void ex_normal(exarg_T *eap); void exec_normal_cmd(char_u *cmd, int remap, int silent); void exec_normal(int was_typed, int use_vpeekc, int may_use_terminal_loop); -int find_cmdline_var(char_u *src, int *usedlen); -char_u *eval_vars(char_u *src, char_u *srcstart, int *usedlen, linenr_T *lnump, char **errormsg, int *escaped, int empty_is_error); +int find_cmdline_var(char_u *src, size_t *usedlen); +char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, int *escaped, int empty_is_error); char_u *expand_sfile(char_u *arg); void dialog_msg(char_u *buff, char *format, char_u *fname); void set_no_hlsearch(int flag); diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro index 3f7b30d..8d97870 100644 --- a/src/proto/fileio.pro +++ b/src/proto/fileio.pro @@ -26,6 +26,7 @@ char_u *modname(char_u *fname, char_u *ext, int prepend_dot); char_u *buf_modname(int shortname, char_u *fname, char_u *ext, int prepend_dot); int vim_fgets(char_u *buf, int size, FILE *fp); int vim_rename(char_u *from, char_u *to); +int vim_copyfile(char_u *from, char_u *to); int check_timestamps(int focus); int buf_check_timestamp(buf_T *buf, int focus); void buf_reload(buf_T *buf, int orig_mode, int reload_options); diff --git a/src/proto/filepath.pro b/src/proto/filepath.pro index fd8de80..46f51cb 100644 --- a/src/proto/filepath.pro +++ b/src/proto/filepath.pro @@ -1,11 +1,12 @@ /* filepath.c */ -int modify_fname(char_u *src, int tilde_file, int *usedlen, char_u **fnamep, char_u **bufp, int *fnamelen); +int modify_fname(char_u *src, int tilde_file, size_t *usedlen, char_u **fnamep, char_u **bufp, int *fnamelen); void shorten_dir(char_u *str); int file_is_readable(char_u *fname); void f_chdir(typval_T *argvars, typval_T *rettv); void f_delete(typval_T *argvars, typval_T *rettv); void f_executable(typval_T *argvars, typval_T *rettv); void f_exepath(typval_T *argvars, typval_T *rettv); +void f_filecopy(typval_T *argvars, typval_T *rettv); void f_filereadable(typval_T *argvars, typval_T *rettv); void f_filewritable(typval_T *argvars, typval_T *rettv); void f_finddir(typval_T *argvars, typval_T *rettv); diff --git a/src/proto/gc.pro b/src/proto/gc.pro new file mode 100644 index 0000000..e13dbda --- /dev/null +++ b/src/proto/gc.pro @@ -0,0 +1,12 @@ +/* gc.c */ +int get_copyID(void); +int garbage_collect(int testing); +int set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack); +int set_ref_in_dict(dict_T *d, int copyID); +int set_ref_in_list(list_T *ll, int copyID); +int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack); +int set_ref_in_callback(callback_T *cb, int copyID); +int set_ref_in_item_class(class_T *cl, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); +int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); +/* vim: set ft=c : */ + diff --git a/src/proto/indent.pro b/src/proto/indent.pro index bafcefc..6e56a0e 100644 --- a/src/proto/indent.pro +++ b/src/proto/indent.pro @@ -1,15 +1,15 @@ /* indent.c */ int tabstop_set(char_u *var, int **array); int tabstop_padding(colnr_T col, int ts_arg, int *vts); -int tabstop_at(colnr_T col, int ts, int *vts); +int tabstop_at(colnr_T col, int ts, int *vts, int left); colnr_T tabstop_start(colnr_T col, int ts, int *vts); void tabstop_fromto(colnr_T start_col, colnr_T end_col, int ts_arg, int *vts, int *ntabs, int *nspcs); int *tabstop_copy(int *oldts); int tabstop_count(int *ts); int tabstop_first(int *ts); long get_sw_value(buf_T *buf); -long get_sw_value_indent(buf_T *buf); -long get_sw_value_col(buf_T *buf, colnr_T col); +long get_sw_value_indent(buf_T *buf, int left); +long get_sw_value_col(buf_T *buf, colnr_T col, int left); long get_sts_value(void); int get_indent(void); int get_indent_lnum(linenr_T lnum); diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro index 51f5db0..dbd5ef7 100644 --- a/src/proto/insexpand.pro +++ b/src/proto/insexpand.pro @@ -27,9 +27,10 @@ int ins_compl_accept_char(int c); int ins_compl_add_infercase(char_u *str_arg, int len, int icase, char_u *fname, int dir, int cont_s_ipos); int ins_compl_has_shown_match(void); int ins_compl_long_shown_match(void); -void completeopt_was_set(void); +unsigned int get_cot_flags(void); int pum_wanted(void); void ins_compl_show_pum(void); +char_u *ins_compl_leader(void); char_u *find_word_start(char_u *ptr); char_u *find_word_end(char_u *ptr); void ins_compl_clear(void); diff --git a/src/proto/list.pro b/src/proto/list.pro index 0b58c69..1659b8f 100644 --- a/src/proto/list.pro +++ b/src/proto/list.pro @@ -65,4 +65,5 @@ void f_insert(typval_T *argvars, typval_T *rettv); void f_remove(typval_T *argvars, typval_T *rettv); void f_reverse(typval_T *argvars, typval_T *rettv); void f_reduce(typval_T *argvars, typval_T *rettv); +void f_slice(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro index e76bf75..d64f961 100644 --- a/src/proto/misc1.pro +++ b/src/proto/misc1.pro @@ -7,7 +7,7 @@ int plines_nofill(linenr_T lnum); int plines_win_nofill(win_T *wp, linenr_T lnum, int limit_winheight); int plines_win_nofold(win_T *wp, linenr_T lnum); int plines_win_col(win_T *wp, linenr_T lnum, long column); -int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int limit_winheight); +int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max); int gchar_pos(pos_T *pos); int gchar_cursor(void); void pchar_cursor(int c); diff --git a/src/proto/normal.pro b/src/proto/normal.pro index 6dcbe41..36a26ec 100644 --- a/src/proto/normal.pro +++ b/src/proto/normal.pro @@ -31,5 +31,6 @@ int get_visual_text(cmdarg_T *cap, char_u **pp, int *lenp); void start_selection(void); void may_start_select(int c); int unadjust_for_sel(void); +int unadjust_for_sel_inner(pos_T *pp); void set_cursor_for_append_to_line(void); /* vim: set ft=c : */ diff --git a/src/proto/option.pro b/src/proto/option.pro index be7ee95..69463d4 100644 --- a/src/proto/option.pro +++ b/src/proto/option.pro @@ -139,7 +139,7 @@ int reset_option_was_set(char_u *name); int can_bs(int what); long get_scrolloff_value(void); long get_sidescrolloff_value(void); -unsigned int get_bkc_value(buf_T *buf); +unsigned int get_bkc_flags(buf_T *buf); char_u *get_flp_value(buf_T *buf); unsigned int get_ve_flags(void); char_u *get_showbreak_value(win_T *win); diff --git a/src/proto/search.pro b/src/proto/search.pro index 5b2b889..08526c8 100644 --- a/src/proto/search.pro +++ b/src/proto/search.pro @@ -1,7 +1,7 @@ /* search.c */ -int search_regcomp(char_u *pat, char_u **used_pat, int pat_save, int pat_use, int options, regmmatch_T *regmatch); +int search_regcomp(char_u *pat, size_t patlen, char_u **used_pat, int pat_save, int pat_use, int options, regmmatch_T *regmatch); char_u *get_search_pat(void); -void save_re_pat(int idx, char_u *pat, int magic); +void save_re_pat(int idx, char_u *pat, size_t patlen, int magic); void save_search_patterns(void); void restore_search_patterns(void); void free_search_patterns(void); @@ -21,9 +21,9 @@ char_u *last_search_pat(void); void reset_search_dir(void); void set_last_search_pat(char_u *s, int idx, int magic, int setlast); void last_pat_prog(regmmatch_T *regmatch); -int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, int dir, char_u *pat, long count, int options, int pat_use, searchit_arg_T *extra_arg); +int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, int dir, char_u *pat, size_t patlen, long count, int options, int pat_use, searchit_arg_T *extra_arg); void set_search_direction(int cdir); -int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, int options, searchit_arg_T *sia); +int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, size_t patlen, long count, int options, searchit_arg_T *sia); int search_for_exact_line(buf_T *buf, pos_T *pos, int dir, char_u *pat); int searchc(cmdarg_T *cap, int t_cmd); pos_T *findmatch(oparg_T *oap, int initc); @@ -40,6 +40,7 @@ int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, int_u void f_matchfuzzy(typval_T *argvars, typval_T *rettv); void f_matchfuzzypos(typval_T *argvars, typval_T *rettv); int fuzzy_match_str(char_u *str, char_u *pat); +garray_T *fuzzy_match_str_with_pos(char_u *str, char_u *pat); void fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count); int fuzzymatches_to_strmatches(fuzmatch_str_T *fuzmatch, char_u ***matches, int count, int funcsort); /* vim: set ft=c : */ diff --git a/src/proto/spell.pro b/src/proto/spell.pro index 98a1353..680bf34 100644 --- a/src/proto/spell.pro +++ b/src/proto/spell.pro @@ -6,7 +6,7 @@ int match_compoundrule(slang_T *slang, char_u *compflags); int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, int cond_req); int spell_valid_case(int wordflags, int treeflags); int spell_check_window(win_T *wp); -int spell_move_to(win_T *wp, int dir, int allwords, int curline, hlf_T *attrp); +int spell_move_to(win_T *wp, int dir, smt_T behaviour, int curline, hlf_T *attrp); void spell_cat_line(char_u *buf, char_u *line, int maxlen); char_u *spell_enc(void); slang_T *slang_alloc(char_u *lang); diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro index d3d3b99..1b5800c 100644 --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -40,7 +40,7 @@ int is_class_name(char_u *name, typval_T *rettv); void protected_method_access_errmsg(char_u *method_name); int object_empty(object_T *obj); int object_len(object_T *obj); -char_u *object_string(object_T *obj, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val); +char_u *object2string(object_T *obj, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val); int class_instance_of(class_T *cl, class_T *other_cl); void f_instanceof(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/quickfix.c b/src/quickfix.c index 2e5b693..414fe65 100644 --- a/src/quickfix.c +++ b/src/quickfix.c @@ -3388,7 +3388,7 @@ qf_jump_goto_line( // Move the cursor to the first line in the buffer save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) + if (!do_search(NULL, '/', '/', qf_pattern, STRLEN(qf_pattern), (long)1, SEARCH_KEEP, NULL)) curwin->w_cursor = save_cursor; } } @@ -4867,6 +4867,9 @@ qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int qf_winid) if (old_last == NULL) { + win_T *wp; + tabpage_T *tp; + if (buf != curbuf) { internal_error("qf_fill_buffer()"); @@ -4883,6 +4886,10 @@ qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int qf_winid) while ((curbuf->b_ml.ml_flags & ML_EMPTY) == 0) (void)ml_delete((linenr_T)1); + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer == curbuf) + wp->w_skipcol = 0; + // Remove all undo information u_clearallandblockfree(curbuf); } diff --git a/src/regexp.c b/src/regexp.c index 4373ae0..ff201d9 100644 --- a/src/regexp.c +++ b/src/regexp.c @@ -161,6 +161,7 @@ re_multi_type(int c) } static char_u *reg_prev_sub = NULL; +static size_t reg_prev_sublen = 0; /* * REGEXP_INRANGE contains all characters which are always special in a [] @@ -197,6 +198,30 @@ backslash_trans(int c) return c; } +enum +{ + CLASS_ALNUM = 0, + CLASS_ALPHA, + CLASS_BLANK, + CLASS_CNTRL, + CLASS_DIGIT, + CLASS_GRAPH, + CLASS_LOWER, + CLASS_PRINT, + CLASS_PUNCT, + CLASS_SPACE, + CLASS_UPPER, + CLASS_XDIGIT, + CLASS_TAB, + CLASS_RETURN, + CLASS_BACKSPACE, + CLASS_ESCAPE, + CLASS_IDENT, + CLASS_KEYWORD, + CLASS_FNAME, + CLASS_NONE = 99 +}; + /* * Check for a character class name "[:name:]". "pp" points to the '['. * Returns one of the CLASS_ items. CLASS_NONE means that no item was @@ -205,58 +230,56 @@ backslash_trans(int c) static int get_char_class(char_u **pp) { - static const char *(class_names[]) = + // must be sorted by the 'value' field because it is used by bsearch()! + static keyvalue_T char_class_tab[] = { - "alnum:]", -#define CLASS_ALNUM 0 - "alpha:]", -#define CLASS_ALPHA 1 - "blank:]", -#define CLASS_BLANK 2 - "cntrl:]", -#define CLASS_CNTRL 3 - "digit:]", -#define CLASS_DIGIT 4 - "graph:]", -#define CLASS_GRAPH 5 - "lower:]", -#define CLASS_LOWER 6 - "print:]", -#define CLASS_PRINT 7 - "punct:]", -#define CLASS_PUNCT 8 - "space:]", -#define CLASS_SPACE 9 - "upper:]", -#define CLASS_UPPER 10 - "xdigit:]", -#define CLASS_XDIGIT 11 - "tab:]", -#define CLASS_TAB 12 - "return:]", -#define CLASS_RETURN 13 - "backspace:]", -#define CLASS_BACKSPACE 14 - "escape:]", -#define CLASS_ESCAPE 15 - "ident:]", -#define CLASS_IDENT 16 - "keyword:]", -#define CLASS_KEYWORD 17 - "fname:]", -#define CLASS_FNAME 18 + KEYVALUE_ENTRY(CLASS_ALNUM, "alnum:]"), + KEYVALUE_ENTRY(CLASS_ALPHA, "alpha:]"), + KEYVALUE_ENTRY(CLASS_BACKSPACE, "backspace:]"), + KEYVALUE_ENTRY(CLASS_BLANK, "blank:]"), + KEYVALUE_ENTRY(CLASS_CNTRL, "cntrl:]"), + KEYVALUE_ENTRY(CLASS_DIGIT, "digit:]"), + KEYVALUE_ENTRY(CLASS_ESCAPE, "escape:]"), + KEYVALUE_ENTRY(CLASS_FNAME, "fname:]"), + KEYVALUE_ENTRY(CLASS_GRAPH, "graph:]"), + KEYVALUE_ENTRY(CLASS_IDENT, "ident:]"), + KEYVALUE_ENTRY(CLASS_KEYWORD, "keyword:]"), + KEYVALUE_ENTRY(CLASS_LOWER, "lower:]"), + KEYVALUE_ENTRY(CLASS_PRINT, "print:]"), + KEYVALUE_ENTRY(CLASS_PUNCT, "punct:]"), + KEYVALUE_ENTRY(CLASS_RETURN, "return:]"), + KEYVALUE_ENTRY(CLASS_SPACE, "space:]"), + KEYVALUE_ENTRY(CLASS_TAB, "tab:]"), + KEYVALUE_ENTRY(CLASS_UPPER, "upper:]"), + KEYVALUE_ENTRY(CLASS_XDIGIT, "xdigit:]") }; -#define CLASS_NONE 99 - int i; - if ((*pp)[1] == ':') + // check that the value of "pp" has a chance of matching + if ((*pp)[1] == ':' && ASCII_ISLOWER((*pp)[2]) + && ASCII_ISLOWER((*pp)[3]) && ASCII_ISLOWER((*pp)[4])) { - for (i = 0; i < (int)ARRAY_LENGTH(class_names); ++i) - if (STRNCMP(*pp + 2, class_names[i], STRLEN(class_names[i])) == 0) - { - *pp += STRLEN(class_names[i]) + 2; - return i; - } + keyvalue_T target; + keyvalue_T *entry; + // this function can be called repeatedly with the same value for "pp" + // so we cache the last found entry. + static keyvalue_T *last_entry = NULL; + + target.key = 0; + target.value = (char *)*pp + 2; + target.length = 0; // not used, see cmp_keyvalue_value_n() + + if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0) + entry = last_entry; + else + entry = (keyvalue_T *)bsearch(&target, &char_class_tab, + ARRAY_LENGTH(char_class_tab), + sizeof(char_class_tab[0]), cmp_keyvalue_value_n); + if (entry != NULL) + { + last_entry = entry; + *pp += entry->length + 2; + return entry->key; + } } return CLASS_NONE; } @@ -597,6 +620,7 @@ skip_regexp_ex( { magic_T mymagic; char_u *p = startp; + size_t startplen = 0; if (magic) mymagic = MAGIC_ON; @@ -620,16 +644,21 @@ skip_regexp_ex( if (dirc == '?' && newp != NULL && p[1] == '?') { // change "\?" to "?", make a copy first. + if (startplen == 0) + startplen = STRLEN(startp); if (*newp == NULL) { - *newp = vim_strsave(startp); + *newp = vim_strnsave(startp, startplen); if (*newp != NULL) + { p = *newp + (p - startp); + startp = *newp; + } } if (dropped != NULL) ++*dropped; if (*newp != NULL) - STRMOVE(p, p + 1); + mch_memmove(p, p + 1, startplen - ((p + 1) - startp) + 1); else ++p; } @@ -1189,20 +1218,114 @@ reg_iswordc(int c) return vim_iswordc_buf(c, rex.reg_buf); } +#ifdef FEAT_EVAL +static int can_f_submatch = FALSE; // TRUE when submatch() can be used + +// This struct is used for reg_submatch(). Needed for when the +// substitution string is an expression that contains a call to substitute() +// and submatch(). +typedef struct { + regmatch_T *sm_match; + regmmatch_T *sm_mmatch; + linenr_T sm_firstlnum; + linenr_T sm_maxline; + int sm_line_lbr; +} regsubmatch_T; + +static regsubmatch_T rsm; // can only be used when can_f_submatch is TRUE +#endif + +typedef enum +{ + RGLF_LINE = 0x01, + RGLF_LENGTH = 0x02 +#ifdef FEAT_EVAL + , + RGLF_SUBMATCH = 0x04 +#endif +} reg_getline_flags_T; + +// +// common code for reg_getline(), reg_getline_len(), reg_getline_submatch() and +// reg_getline_submatch_len(). +// the flags argument (which is a bitmask) controls what info is to be returned and whether +// or not submatch is in effect. +// note: +// submatch is available only if FEAT_EVAL is defined. + static void +reg_getline_common(linenr_T lnum, reg_getline_flags_T flags, char_u **line, colnr_T *length) +{ + int get_line = flags & RGLF_LINE; + int get_length = flags & RGLF_LENGTH; + linenr_T firstlnum; + linenr_T maxline; + +#ifdef FEAT_EVAL + if (flags & RGLF_SUBMATCH) + { + firstlnum = rsm.sm_firstlnum + lnum; + maxline = rsm.sm_maxline; + } + else +#endif + { + firstlnum = rex.reg_firstlnum + lnum; + maxline = rex.reg_maxline; + } + + // when looking behind for a match/no-match lnum is negative. but we + // can't go before line 1. + if (firstlnum < 1) + { + if (get_line) + *line = NULL; + if (get_length) + *length = 0; + + return; + } + + if (lnum > maxline) + { + // must have matched the "\n" in the last line. + if (get_line) + *line = (char_u *)""; + if (get_length) + *length = 0; + + return; + } + + if (get_line) + *line = ml_get_buf(rex.reg_buf, firstlnum, FALSE); + if (get_length) + *length = ml_get_buf_len(rex.reg_buf, firstlnum); +} + /* * Get pointer to the line "lnum", which is relative to "reg_firstlnum". */ static char_u * reg_getline(linenr_T lnum) { - // when looking behind for a match/no-match lnum is negative. But we - // can't go before line 1 - if (rex.reg_firstlnum + lnum < 1) - return NULL; - if (lnum > rex.reg_maxline) - // Must have matched the "\n" in the last line. - return (char_u *)""; - return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, FALSE); + char_u *line; + + reg_getline_common(lnum, RGLF_LINE, &line, NULL); + + return line; +} + +/* + * Get length of line "lnum", which is relative to "reg_firstlnum". + */ + static colnr_T +reg_getline_len(linenr_T lnum) +{ + colnr_T length; + + reg_getline_common(lnum, RGLF_LENGTH, NULL, &length); + + return length; } #ifdef FEAT_SYN_HL @@ -1484,7 +1607,7 @@ match_with_backref( if (clnum == end_lnum) len = end_col - ccol; else - len = (int)STRLEN(p + ccol); + len = (int)reg_getline_len(clnum) - ccol; if (cstrncmp(p + ccol, rex.input, &len) != 0) return RA_NOMATCH; // doesn't match @@ -1745,49 +1868,71 @@ regtilde(char_u *source, int magic) { char_u *newsub = source; char_u *p; + size_t newsublen = 0; + char_u tilde[3] = {'~', NUL, NUL}; + size_t tildelen = 1; + int error = FALSE; + + if (!magic) + { + tilde[0] = '\\'; + tilde[1] = '~'; + tilde[2] = NUL; + tildelen = 2; + } for (p = newsub; *p; ++p) { - if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) + if (STRNCMP(p, tilde, tildelen) == 0) { - if (reg_prev_sub != NULL) + size_t prefixlen = p - newsub; // not including the tilde + char_u *postfix = p + tildelen; + size_t postfixlen; + size_t tmpsublen; + + if (newsublen == 0) + newsublen = STRLEN(newsub); + newsublen -= tildelen; + postfixlen = newsublen - prefixlen; + tmpsublen = prefixlen + reg_prev_sublen + postfixlen; + + if (tmpsublen > 0 && reg_prev_sub != NULL) { - // length = len(newsub) - 1 + len(prev_sub) + 1 + char_u *tmpsub; + // Avoid making the text longer than MAXCOL, it will cause // trouble at some point. - size_t prevsublen = STRLEN(reg_prev_sub); - size_t newsublen = STRLEN(newsub); - if (prevsublen > MAXCOL || newsublen > MAXCOL - || newsublen + prevsublen > MAXCOL) + if (tmpsublen > MAXCOL) { emsg(_(e_resulting_text_too_long)); + error = TRUE; break; } - char_u *tmpsub = alloc(newsublen + prevsublen); - if (tmpsub != NULL) + tmpsub = alloc(tmpsublen + 1); + if (tmpsub == NULL) { - // copy prefix - size_t prefixlen = p - newsub; // not including ~ - mch_memmove(tmpsub, newsub, prefixlen); - // interpret tilde - mch_memmove(tmpsub + prefixlen, reg_prev_sub, - prevsublen); - // copy postfix - if (!magic) - ++p; // back off backslash - STRCPY(tmpsub + prefixlen + prevsublen, p + 1); - - if (newsub != source) // allocated newsub before - vim_free(newsub); - newsub = tmpsub; - p = newsub + prefixlen + prevsublen; + emsg(_(e_out_of_memory)); + error = TRUE; + break; } + + // copy prefix + mch_memmove(tmpsub, newsub, prefixlen); + // interpret tilde + mch_memmove(tmpsub + prefixlen, reg_prev_sub, reg_prev_sublen); + // copy postfix + STRCPY(tmpsub + prefixlen + reg_prev_sublen, postfix); + + if (newsub != source) // allocated newsub before + vim_free(newsub); + newsub = tmpsub; + newsublen = tmpsublen; + p = newsub + prefixlen + reg_prev_sublen; } - else if (magic) - STRMOVE(p, p + 1); // remove '~' else - STRMOVE(p, p + 2); // remove '\~' + mch_memmove(p, postfix, postfixlen + 1); // remove the tilde (+1 for the NUL) + --p; } else @@ -1799,32 +1944,34 @@ regtilde(char_u *source, int magic) } } + if (error) + { + if (newsub != source) + vim_free(newsub); + return source; + } + // Store a copy of newsub in reg_prev_sub. It is always allocated, // because recursive calls may make the returned string invalid. - vim_free(reg_prev_sub); - reg_prev_sub = vim_strsave(newsub); + // Only store it if there something to store. + newsublen = p - newsub; + if (newsublen == 0) + VIM_CLEAR(reg_prev_sub); + else + { + vim_free(reg_prev_sub); + reg_prev_sub = vim_strnsave(newsub, newsublen); + } + + if (reg_prev_sub == NULL) + reg_prev_sublen = 0; + else + reg_prev_sublen = newsublen; return newsub; } #ifdef FEAT_EVAL -static int can_f_submatch = FALSE; // TRUE when submatch() can be used - -// These pointers are used for reg_submatch(). Needed for when the -// substitution string is an expression that contains a call to substitute() -// and submatch(). -typedef struct { - regmatch_T *sm_match; - regmmatch_T *sm_mmatch; - linenr_T sm_firstlnum; - linenr_T sm_maxline; - int sm_line_lbr; -} regsubmatch_T; - -static regsubmatch_T rsm; // can only be used when can_f_submatch is TRUE -#endif - -#ifdef FEAT_EVAL /* * Put the submatches in "argv[argskip]" which is a list passed into @@ -2028,12 +2175,16 @@ vim_regsub_both( // "flags & REGSUB_COPY" != 0. if (copy) { - if (eval_result[nested] != NULL && - (int)STRLEN(eval_result[nested]) < destlen) + if (eval_result[nested] != NULL) { - STRCPY(dest, eval_result[nested]); - dst += STRLEN(eval_result[nested]); - VIM_CLEAR(eval_result[nested]); + int eval_len = (int)STRLEN(eval_result[nested]); + + if (eval_len < destlen) + { + STRCPY(dest, eval_result[nested]); + dst += eval_len; + VIM_CLEAR(eval_result[nested]); + } } } else @@ -2325,7 +2476,7 @@ vim_regsub_both( len = rex.reg_mmatch->endpos[no].col - rex.reg_mmatch->startpos[no].col; else - len = (int)STRLEN(s); + len = (int)reg_getline_len(clnum) - rex.reg_mmatch->startpos[no].col; } } else @@ -2360,7 +2511,7 @@ vim_regsub_both( if (rex.reg_mmatch->endpos[no].lnum == clnum) len = rex.reg_mmatch->endpos[no].col; else - len = (int)STRLEN(s); + len = (int)reg_getline_len(clnum); } else break; @@ -2465,26 +2616,25 @@ exit: } #ifdef FEAT_EVAL -/* - * Call reg_getline() with the line numbers from the submatch. If a - * substitute() was used the reg_maxline and other values have been - * overwritten. - */ + static char_u * reg_getline_submatch(linenr_T lnum) { - char_u *s; - linenr_T save_first = rex.reg_firstlnum; - linenr_T save_max = rex.reg_maxline; + char_u *line; + + reg_getline_common(lnum, RGLF_LINE | RGLF_SUBMATCH, &line, NULL); + + return line; +} - rex.reg_firstlnum = rsm.sm_firstlnum; - rex.reg_maxline = rsm.sm_maxline; + static colnr_T +reg_getline_submatch_len(linenr_T lnum) +{ + colnr_T length; - s = reg_getline(lnum); + reg_getline_common(lnum, RGLF_LENGTH | RGLF_SUBMATCH, NULL, &length); - rex.reg_firstlnum = save_first; - rex.reg_maxline = save_max; - return s; + return length; } /* @@ -2533,7 +2683,7 @@ reg_submatch(int no) { // Multiple lines: take start line from start col, middle // lines completely and end line up to end col. - len = (int)STRLEN(s); + len = (int)reg_getline_submatch_len(lnum) - rsm.sm_mmatch->startpos[no].col; if (round == 2) { STRCPY(retval, s); @@ -2543,13 +2693,14 @@ reg_submatch(int no) ++lnum; while (lnum < rsm.sm_mmatch->endpos[no].lnum) { - s = reg_getline_submatch(lnum++); + s = reg_getline_submatch(lnum); if (round == 2) STRCPY(retval + len, s); - len += (int)STRLEN(s); + len += (int)reg_getline_submatch_len(lnum); if (round == 2) retval[len] = '\n'; ++len; + ++lnum; } if (round == 2) STRNCPY(retval + len, reg_getline_submatch(lnum), @@ -2624,9 +2775,11 @@ reg_submatch_list(int no) } else { + int max_lnum = elnum - slnum; + if (list_append_string(list, s, -1) == FAIL) error = TRUE; - for (i = 1; i < elnum - slnum; i++) + for (i = 1; i < max_lnum; i++) { s = reg_getline_submatch(slnum + i); if (list_append_string(list, s, -1) == FAIL) diff --git a/src/regexp_bt.c b/src/regexp_bt.c index 5d9450d..5452dda 100644 --- a/src/regexp_bt.c +++ b/src/regexp_bt.c @@ -2564,14 +2564,22 @@ bt_regcomp(char_u *expr, int re_flags) if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW) && !(flags & HASNL)) { + size_t scanlen; + longest = NULL; len = 0; for (; scan != NULL; scan = regnext(scan)) - if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) + { + if (OP(scan) == EXACTLY) { - longest = OPERAND(scan); - len = (int)STRLEN(OPERAND(scan)); + scanlen = STRLEN(OPERAND(scan)); + if (scanlen >= (size_t)len) + { + longest = OPERAND(scan); + len = (int)scanlen; + } } + } r->regmust = longest; r->regmlen = len; } @@ -3406,8 +3414,7 @@ regmatch( { colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum && pos->col == MAXCOL - ? (colnr_T)STRLEN(reg_getline( - pos->lnum - rex.reg_firstlnum)) + ? reg_getline_len(pos->lnum - rex.reg_firstlnum) : pos->col; if ((pos->lnum == rex.lnum + rex.reg_firstlnum @@ -4695,7 +4702,7 @@ regmatch( // right. if (rex.line == NULL) break; - rex.input = rex.line + STRLEN(rex.line); + rex.input = rex.line + reg_getline_len(rex.lnum); fast_breakcheck(); } else @@ -5249,8 +5256,10 @@ regprop(char_u *op) { char *p; static char buf[50]; + static size_t buflen = 0; STRCPY(buf, ":"); + buflen = 1; switch ((int) OP(op)) { @@ -5491,7 +5500,7 @@ regprop(char_u *op) case MOPEN + 7: case MOPEN + 8: case MOPEN + 9: - sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); + buflen += sprintf(buf + buflen, "MOPEN%d", OP(op) - MOPEN); p = NULL; break; case MCLOSE + 0: @@ -5506,7 +5515,7 @@ regprop(char_u *op) case MCLOSE + 7: case MCLOSE + 8: case MCLOSE + 9: - sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); + buflen += sprintf(buf + buflen, "MCLOSE%d", OP(op) - MCLOSE); p = NULL; break; case BACKREF + 1: @@ -5518,7 +5527,7 @@ regprop(char_u *op) case BACKREF + 7: case BACKREF + 8: case BACKREF + 9: - sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); + buflen += sprintf(buf + buflen, "BACKREF%d", OP(op) - BACKREF); p = NULL; break; case NOPEN: @@ -5537,7 +5546,7 @@ regprop(char_u *op) case ZOPEN + 7: case ZOPEN + 8: case ZOPEN + 9: - sprintf(buf + STRLEN(buf), "ZOPEN%d", OP(op) - ZOPEN); + buflen += sprintf(buf + buflen, "ZOPEN%d", OP(op) - ZOPEN); p = NULL; break; case ZCLOSE + 1: @@ -5549,7 +5558,7 @@ regprop(char_u *op) case ZCLOSE + 7: case ZCLOSE + 8: case ZCLOSE + 9: - sprintf(buf + STRLEN(buf), "ZCLOSE%d", OP(op) - ZCLOSE); + buflen += sprintf(buf + buflen, "ZCLOSE%d", OP(op) - ZCLOSE); p = NULL; break; case ZREF + 1: @@ -5561,7 +5570,7 @@ regprop(char_u *op) case ZREF + 7: case ZREF + 8: case ZREF + 9: - sprintf(buf + STRLEN(buf), "ZREF%d", OP(op) - ZREF); + buflen += sprintf(buf + buflen, "ZREF%d", OP(op) - ZREF); p = NULL; break; #endif @@ -5602,7 +5611,7 @@ regprop(char_u *op) case BRACE_COMPLEX + 7: case BRACE_COMPLEX + 8: case BRACE_COMPLEX + 9: - sprintf(buf + STRLEN(buf), "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); + buflen += sprintf(buf + buflen, "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); p = NULL; break; case MULTIBYTECODE: @@ -5612,12 +5621,12 @@ regprop(char_u *op) p = "NEWL"; break; default: - sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); + buflen += sprintf(buf + buflen, "corrupt %d", OP(op)); p = NULL; break; } if (p != NULL) - STRCAT(buf, p); + STRCPY(buf + buflen, p); return (char_u *)buf; } #endif // DEBUG diff --git a/src/regexp_nfa.c b/src/regexp_nfa.c index 5e4fadd..4f07a21 100644 --- a/src/regexp_nfa.c +++ b/src/regexp_nfa.c @@ -5387,7 +5387,7 @@ recursive_regmatch( rex.input = rex.line; } else - rex.input = rex.line + STRLEN(rex.line); + rex.input = rex.line + reg_getline_len(rex.lnum); } if ((int)(rex.input - rex.line) >= state->val) { @@ -6937,8 +6937,7 @@ nfa_regmatch( { colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum && pos->col == MAXCOL - ? (colnr_T)STRLEN(reg_getline( - pos->lnum - rex.reg_firstlnum)) + ? reg_getline_len(pos->lnum - rex.reg_firstlnum) : pos->col; result = (pos->lnum == rex.lnum + rex.reg_firstlnum diff --git a/src/scriptfile.c b/src/scriptfile.c index d5ec7cf..711f576 100644 --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -435,8 +435,13 @@ check_script_symlink(int sid) si = SCRIPT_ITEM(sid); si->sn_sourced_sid = real_sid; if (new_sid) + { SCRIPT_ITEM(real_sid)->sn_import_autoload = si->sn_import_autoload; + if (si->sn_autoload_prefix != NULL) + SCRIPT_ITEM(real_sid)->sn_autoload_prefix = + vim_strsave(si->sn_autoload_prefix); + } } } vim_free(real_fname); @@ -682,7 +687,7 @@ find_script_in_rtp(char_u *name) { int sid = -1; - (void)do_in_path_and_pp(p_rtp, name, DIP_NOAFTER, + (void)do_in_path_and_pp(p_rtp, name, DIP_START | DIP_NOAFTER, find_script_callback, &sid); return sid; } diff --git a/src/search.c b/src/search.c index 166ef4a..0b39d90 100644 --- a/src/search.c +++ b/src/search.c @@ -17,8 +17,8 @@ static void set_vv_searchforward(void); static int first_submatch(regmmatch_T *rp); #endif #ifdef FEAT_FIND_ID -static void show_pat_in_path(char_u *, int, - int, int, FILE *, linenr_T *, long); +static char_u *get_line_and_copy(linenr_T lnum, char_u *buf); +static void show_pat_in_path(char_u *, int, int, int, FILE *, linenr_T *, long); #endif typedef struct searchstat @@ -32,8 +32,27 @@ typedef struct searchstat int last_maxcount; // the max count of the last search } searchstat_T; -static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, int recompute, int maxcount, long timeout); +#ifdef FEAT_SEARCH_EXTRA +static void save_incsearch_state(void); +static void restore_incsearch_state(void); +#endif +static int check_prevcol(char_u *linep, int col, int ch, int *prevcol); +static int find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos); +static void find_mps_values(int *initc, int *findc, int *backwards, int switchit); +static int is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction); +static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, size_t msgbuflen, int recompute, int maxcount, long timeout); static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout); +static int fuzzy_match_compute_score(char_u *str, int strSz, int_u *matches, int numMatches); +static int fuzzy_match_recursive(char_u *fuzpat, char_u *str, int_u strIdx, int *outScore, char_u *strBegin, int strLen, int_u *srcMatches, int_u *matches, int maxMatches, int nextMatch, int *recursionCount); +#if defined(FEAT_EVAL) || defined(FEAT_PROTO) +static int fuzzy_match_item_compare(const void *s1, const void *s2); +static void fuzzy_match_in_list(list_T *l, char_u *str, int matchseq, char_u *key, callback_T *item_cb, int retmatchpos, list_T *fmatchlist, long max_matches); +static void do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos); +#endif +static int fuzzy_match_str_compare(const void *s1, const void *s2); +static void fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz); +static int fuzzy_match_func_compare(const void *s1, const void *s2); +static void fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz); #define SEARCH_STAT_DEF_TIMEOUT 40L #define SEARCH_STAT_DEF_MAX_COUNT 99 @@ -69,8 +88,8 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst */ static spat_T spats[2] = { - {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, // last used search pat - {NULL, TRUE, FALSE, {'/', 0, 0, 0L}} // last used substitute pat + {NULL, 0, TRUE, FALSE, {'/', 0, 0, 0L}}, // last used search pat + {NULL, 0, TRUE, FALSE, {'/', 0, 0, 0L}} // last used substitute pat }; static int last_idx = 0; // index in spats[] for RE_LAST @@ -82,8 +101,9 @@ static char_u lastc_bytes[MB_MAXBYTES + 1]; static int lastc_bytelen = 1; // >1 for multi-byte char // copy of spats[], for keeping the search patterns while executing autocmds -static spat_T saved_spats[2]; +static spat_T saved_spats[ARRAY_LENGTH(spats)]; static char_u *saved_mr_pattern = NULL; +static size_t saved_mr_patternlen = 0; # ifdef FEAT_SEARCH_EXTRA static int saved_spats_last_idx = 0; static int saved_spats_no_hlsearch = 0; @@ -91,6 +111,7 @@ static int saved_spats_no_hlsearch = 0; // allocated copy of pattern used by search_regcomp() static char_u *mr_pattern = NULL; +static size_t mr_patternlen = 0; #ifdef FEAT_FIND_ID /* @@ -123,6 +144,7 @@ typedef struct SearchedFile int search_regcomp( char_u *pat, + size_t patlen, char_u **used_pat, int pat_save, int pat_use, @@ -130,7 +152,6 @@ search_regcomp( regmmatch_T *regmatch) // return: pattern and ignore-case flag { int magic; - int i; rc_did_emsg = FALSE; magic = magic_isset(); @@ -140,6 +161,8 @@ search_regcomp( */ if (pat == NULL || *pat == NUL) { + int i; + if (pat_use == RE_LAST) i = last_idx; else @@ -154,11 +177,12 @@ search_regcomp( return FAIL; } pat = spats[i].pat; + patlen = spats[i].patlen; magic = spats[i].magic; no_smartcase = spats[i].no_scs; } else if (options & SEARCH_HIS) // put new pattern in history - add_to_history(HIST_SEARCH, pat, TRUE, NUL); + add_to_history(HIST_SEARCH, pat, patlen, TRUE, NUL); if (used_pat) *used_pat = pat; @@ -169,7 +193,11 @@ search_regcomp( mr_pattern = reverse_text(pat); else #endif - mr_pattern = vim_strsave(pat); + mr_pattern = vim_strnsave(pat, patlen); + if (mr_pattern == NULL) + mr_patternlen = 0; + else + mr_patternlen = patlen; /* * Save the currently used pattern in the appropriate place, @@ -180,10 +208,10 @@ search_regcomp( { // search or global command if (pat_save == RE_SEARCH || pat_save == RE_BOTH) - save_re_pat(RE_SEARCH, pat, magic); + save_re_pat(RE_SEARCH, pat, patlen, magic); // substitute or global command if (pat_save == RE_SUBST || pat_save == RE_BOTH) - save_re_pat(RE_SUBST, pat, magic); + save_re_pat(RE_SUBST, pat, patlen, magic); } regmatch->rmm_ic = ignorecase(pat); @@ -204,13 +232,17 @@ get_search_pat(void) } void -save_re_pat(int idx, char_u *pat, int magic) +save_re_pat(int idx, char_u *pat, size_t patlen, int magic) { if (spats[idx].pat == pat) return; vim_free(spats[idx].pat); - spats[idx].pat = vim_strsave(pat); + spats[idx].pat = vim_strnsave(pat, patlen); + if (spats[idx].pat == NULL) + spats[idx].patlen = 0; + else + spats[idx].patlen = patlen; spats[idx].magic = magic; spats[idx].no_scs = no_smartcase; last_idx = idx; @@ -231,19 +263,31 @@ static int save_level = 0; void save_search_patterns(void) { + int i; + if (save_level++ != 0) return; - saved_spats[0] = spats[0]; - if (spats[0].pat != NULL) - saved_spats[0].pat = vim_strsave(spats[0].pat); - saved_spats[1] = spats[1]; - if (spats[1].pat != NULL) - saved_spats[1].pat = vim_strsave(spats[1].pat); + for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i) + { + saved_spats[i] = spats[i]; + if (spats[i].pat != NULL) + { + saved_spats[i].pat = vim_strnsave(spats[i].pat, spats[i].patlen); + if (saved_spats[i].pat == NULL) + saved_spats[i].patlen = 0; + else + saved_spats[i].patlen = spats[i].patlen; + } + } if (mr_pattern == NULL) saved_mr_pattern = NULL; else - saved_mr_pattern = vim_strsave(mr_pattern); + saved_mr_pattern = vim_strnsave(mr_pattern, mr_patternlen); + if (saved_mr_pattern == NULL) + saved_mr_patternlen = 0; + else + saved_mr_patternlen = mr_patternlen; #ifdef FEAT_SEARCH_EXTRA saved_spats_last_idx = last_idx; saved_spats_no_hlsearch = no_hlsearch; @@ -253,18 +297,22 @@ save_search_patterns(void) void restore_search_patterns(void) { + int i; + if (--save_level != 0) return; - vim_free(spats[0].pat); - spats[0] = saved_spats[0]; + for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i) + { + vim_free(spats[i].pat); + spats[i] = saved_spats[i]; + } #if defined(FEAT_EVAL) set_vv_searchforward(); #endif - vim_free(spats[1].pat); - spats[1] = saved_spats[1]; vim_free(mr_pattern); mr_pattern = saved_mr_pattern; + mr_patternlen = saved_mr_patternlen; #ifdef FEAT_SEARCH_EXTRA last_idx = saved_spats_last_idx; set_no_hlsearch(saved_spats_no_hlsearch); @@ -275,9 +323,15 @@ restore_search_patterns(void) void free_search_patterns(void) { - vim_free(spats[0].pat); - vim_free(spats[1].pat); + int i; + + for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i) + { + VIM_CLEAR(spats[i].pat); + spats[i].patlen = 0; + } VIM_CLEAR(mr_pattern); + mr_patternlen = 0; } #endif @@ -308,7 +362,13 @@ save_last_search_pattern(void) saved_last_search_spat = spats[RE_SEARCH]; if (spats[RE_SEARCH].pat != NULL) - saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat); + { + saved_last_search_spat.pat = vim_strnsave(spats[RE_SEARCH].pat, spats[RE_SEARCH].patlen); + if (saved_last_search_spat.pat == NULL) + saved_last_search_spat.patlen = 0; + else + saved_last_search_spat.patlen = spats[RE_SEARCH].patlen; + } saved_last_idx = last_idx; saved_no_hlsearch = no_hlsearch; } @@ -328,6 +388,7 @@ restore_last_search_pattern(void) vim_free(spats[RE_SEARCH].pat); spats[RE_SEARCH] = saved_last_search_spat; saved_last_search_spat.pat = NULL; + saved_last_search_spat.patlen = 0; # if defined(FEAT_EVAL) set_vv_searchforward(); # endif @@ -513,7 +574,12 @@ set_last_search_pat( if (*s == NUL) spats[idx].pat = NULL; else - spats[idx].pat = vim_strsave(s); + { + spats[idx].patlen = STRLEN(s); + spats[idx].pat = vim_strnsave(s, spats[idx].patlen); + } + if (spats[idx].pat == NULL) + spats[idx].patlen = 0; spats[idx].magic = magic; spats[idx].no_scs = FALSE; spats[idx].off.dir = '/'; @@ -532,7 +598,11 @@ set_last_search_pat( if (spats[idx].pat == NULL) saved_spats[idx].pat = NULL; else - saved_spats[idx].pat = vim_strsave(spats[idx].pat); + saved_spats[idx].pat = vim_strnsave(spats[idx].pat, spats[idx].patlen); + if (saved_spats[idx].pat == NULL) + saved_spats[idx].patlen = 0; + else + saved_spats[idx].patlen = spats[idx].patlen; # ifdef FEAT_SEARCH_EXTRA saved_spats_last_idx = last_idx; # endif @@ -560,7 +630,7 @@ last_pat_prog(regmmatch_T *regmatch) return; } ++emsg_off; // So it doesn't beep if bad expr - (void)search_regcomp((char_u *)"", NULL, 0, last_idx, SEARCH_KEEP, regmatch); + (void)search_regcomp((char_u *)"", 0, NULL, 0, last_idx, SEARCH_KEEP, regmatch); --emsg_off; } #endif @@ -594,6 +664,7 @@ searchit( pos_T *end_pos, // set to end of the match, unless NULL int dir, char_u *pat, + size_t patlen, long count, int options, int pat_use, // which pattern to use when "pat" is empty @@ -623,8 +694,9 @@ searchit( linenr_T stop_lnum = 0; // stop after this line number when != 0 int unused_timeout_flag = FALSE; int *timed_out = &unused_timeout_flag; // set when timed out. + int search_from_match_end; // vi-compatible search? - if (search_regcomp(pat, NULL, RE_SEARCH, pat_use, + if (search_regcomp(pat, patlen, NULL, RE_SEARCH, pat_use, (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { if ((options & SEARCH_MSG) && !rc_did_emsg) @@ -632,6 +704,8 @@ searchit( return FAIL; } + search_from_match_end = vim_strchr(p_cpo, CPO_SEARCH) != NULL; + if (extra_arg != NULL) { stop_lnum = extra_arg->sa_stop_lnum; @@ -778,7 +852,7 @@ searchit( * of the match, otherwise continue one position * forward. */ - if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) + if (search_from_match_end) { if (nmatched > 1) { @@ -890,7 +964,7 @@ searchit( * of the match, otherwise continue one position * forward. */ - if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) + if (search_from_match_end) { if (nmatched > 1) break; @@ -1173,12 +1247,14 @@ do_search( int search_delim, // the delimiter for the search, e.g. '%' in // s%regex%replacement% char_u *pat, + size_t patlen, long count, int options, searchit_arg_T *sia) // optional arguments or NULL { pos_T pos; // position of the last match char_u *searchstr; + size_t searchstrlen; soffset_T old_off; int retval; // Return value char_u *p; @@ -1186,10 +1262,13 @@ do_search( char_u *dircp; char_u *strcopy = NULL; char_u *ps; + int show_search_stats; char_u *msgbuf = NULL; - size_t len; + size_t msgbuflen = 0; int has_offset = FALSE; + searchcmdlen = 0; + /* * A line offset is not remembered, this is vi compatible. */ @@ -1267,24 +1346,28 @@ do_search( int show_top_bot_msg = FALSE; searchstr = pat; + searchstrlen = patlen; + dircp = NULL; // use previous pattern if (pat == NULL || *pat == NUL || *pat == search_delim) { if (spats[RE_SEARCH].pat == NULL) // no previous pattern { - searchstr = spats[RE_SUBST].pat; - if (searchstr == NULL) + if (spats[RE_SUBST].pat == NULL) { emsg(_(e_no_previous_regular_expression)); retval = 0; goto end_do_search; } + searchstr = spats[RE_SUBST].pat; + searchstrlen = spats[RE_SUBST].patlen; } else { // make search_regcomp() use spats[RE_SEARCH].pat searchstr = (char_u *)""; + searchstrlen = 0; } } @@ -1299,13 +1382,17 @@ do_search( &strcopy, NULL, NULL); if (strcopy != ps) { + size_t len = STRLEN(strcopy); // made a copy of "pat" to change "\?" to "?" - searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); + searchcmdlen += (int)(patlen - len); pat = strcopy; + patlen = len; searchstr = strcopy; + searchstrlen = len; } if (*p == search_delim) { + searchstrlen = p - pat; dircp = p; // remember where we put the NUL *p++ = NUL; } @@ -1344,16 +1431,19 @@ do_search( // compute length of search command for get_address() searchcmdlen += (int)(p - pat); + patlen -= p - pat; pat = p; // put pat after search command } + show_search_stats = FALSE; if ((options & SEARCH_ECHO) && messaging() && !msg_silent && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) { - char_u *trunc; char_u off_buf[40]; size_t off_len = 0; + size_t plen; + size_t msgbufsize; // Compute msg_row early. msg_start(); @@ -1362,24 +1452,28 @@ do_search( if (!cmd_silent && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) { - p = off_buf; - *p++ = dirc; + off_buf[off_len++] = dirc; if (spats[0].off.end) - *p++ = 'e'; + off_buf[off_len++] = 'e'; else if (!spats[0].off.line) - *p++ = 's'; + off_buf[off_len++] = 's'; if (spats[0].off.off > 0 || spats[0].off.line) - *p++ = '+'; - *p = NUL; + off_buf[off_len++] = '+'; + off_buf[off_len] = NUL; if (spats[0].off.off != 0 || spats[0].off.line) - sprintf((char *)p, "%ld", spats[0].off.off); - off_len = STRLEN(off_buf); + off_len += vim_snprintf((char *)off_buf + off_len, sizeof(off_buf) - off_len, "%ld", spats[0].off.off); } if (*searchstr == NUL) + { p = spats[0].pat; + plen = spats[0].patlen; + } else + { p = searchstr; + plen = searchstrlen; + } if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent) { @@ -1389,45 +1483,53 @@ do_search( // msg_strtrunc() will shorten in the middle. if (msg_scrolled != 0 && !cmd_silent) // Use all the columns. - len = (int)(Rows - msg_row) * Columns - 1; + msgbufsize = (int)(Rows - msg_row) * Columns - 1; else // Use up to 'showcmd' column. - len = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; - if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3) - len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3; + msgbufsize = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; + if (msgbufsize < plen + off_len + SEARCH_STAT_BUF_LEN + 3) + msgbufsize = plen + off_len + SEARCH_STAT_BUF_LEN + 3; } else // Reserve enough space for the search pattern + offset. - len = STRLEN(p) + off_len + 3; + msgbufsize = plen + off_len + 3; vim_free(msgbuf); - msgbuf = alloc(len); - if (msgbuf != NULL) + msgbuf = alloc(msgbufsize); + if (msgbuf == NULL) { - vim_memset(msgbuf, ' ', len); - msgbuf[len - 1] = NUL; + msgbuflen = 0; + } + else + { + vim_memset(msgbuf, ' ', msgbufsize); + msgbuflen = msgbufsize - 1; + msgbuf[msgbuflen] = NUL; // do not fill the msgbuf buffer, if cmd_silent is set, leave it // empty for the search_stat feature. if (!cmd_silent) { + char_u *trunc; + msgbuf[0] = dirc; if (enc_utf8 && utf_iscomposing(utf_ptr2char(p))) { // Use a space to draw the composing char on. msgbuf[1] = ' '; - mch_memmove(msgbuf + 2, p, STRLEN(p)); + mch_memmove(msgbuf + 2, p, plen); } else - mch_memmove(msgbuf + 1, p, STRLEN(p)); + mch_memmove(msgbuf + 1, p, plen); if (off_len > 0) - mch_memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len); + mch_memmove(msgbuf + plen + 1, off_buf, off_len); trunc = msg_strtrunc(msgbuf, TRUE); if (trunc != NULL) { vim_free(msgbuf); msgbuf = trunc; + msgbuflen = STRLEN(msgbuf); } #ifdef FEAT_RIGHTLEFT @@ -1448,7 +1550,7 @@ do_search( // move reversed text to beginning of buffer while (*r != NUL && *r == ' ') r++; - pat_len = msgbuf + STRLEN(msgbuf) - r; + pat_len = msgbuf + msgbuflen - r; mch_memmove(msgbuf, r, pat_len); // overwrite old text if ((size_t)(r - msgbuf) >= pat_len) @@ -1466,7 +1568,10 @@ do_search( out_flush(); msg_nowait = TRUE; // don't wait for this message } - } + + if (!shortmess(SHM_SEARCHCOUNT)) + show_search_stats = TRUE; + } // msgbuf != NULL } /* @@ -1507,7 +1612,7 @@ do_search( */ c = searchit(curwin, curbuf, &pos, NULL, dirc == '/' ? FORWARD : BACKWARD, - searchstr, count, spats[0].off.end + (options & + searchstr, searchstrlen, count, spats[0].off.end + (options & (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG + SEARCH_START + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF))), @@ -1574,14 +1679,9 @@ do_search( } // Show [1/15] if 'S' is not in 'shortmess'. - if ((options & SEARCH_ECHO) - && messaging() - && !msg_silent - && c != FAIL - && !shortmess(SHM_SEARCHCOUNT) - && msgbuf != NULL) + if (show_search_stats) cmdline_search_stat(dirc, &pos, &curwin->w_cursor, - show_top_bot_msg, msgbuf, + show_top_bot_msg, msgbuf, msgbuflen, (count != 1 || has_offset #ifdef FEAT_FOLDING || (!(fdo_flags & FDO_SEARCH) @@ -1612,6 +1712,7 @@ do_search( goto end_do_search; } ++pat; + --patlen; } if (options & SEARCH_MARK) @@ -2825,7 +2926,7 @@ showmatch( * Returns TRUE, FALSE or -1 for failure. */ static int -is_zero_width(char_u *pattern, int move, pos_T *cur, int direction) +is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction) { regmmatch_T regmatch; int nmatched = 0; @@ -2835,9 +2936,12 @@ is_zero_width(char_u *pattern, int move, pos_T *cur, int direction) int flag = 0; if (pattern == NULL) + { pattern = spats[last_idx].pat; + patternlen = spats[last_idx].patlen; + } - if (search_regcomp(pattern, NULL, RE_SEARCH, RE_SEARCH, + if (search_regcomp(pattern, patternlen, NULL, RE_SEARCH, RE_SEARCH, SEARCH_KEEP, ®match) == FAIL) return -1; @@ -2855,7 +2959,7 @@ is_zero_width(char_u *pattern, int move, pos_T *cur, int direction) flag = SEARCH_START; } - if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1, + if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, patternlen, 1, SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if @@ -2925,8 +3029,8 @@ current_search( } // Is the pattern is zero-width?, this time, don't care about the direction - zero_width = is_zero_width(spats[last_idx].pat, TRUE, &curwin->w_cursor, - FORWARD); + zero_width = is_zero_width(spats[last_idx].pat, spats[last_idx].patlen, + TRUE, &curwin->w_cursor, FORWARD); if (zero_width == -1) return FAIL; // pattern not found @@ -2957,7 +3061,7 @@ current_search( result = searchit(curwin, curbuf, &pos, &end_pos, (dir ? FORWARD : BACKWARD), - spats[last_idx].pat, (long) (i ? count : 1), + spats[last_idx].pat, spats[last_idx].patlen, (long) (i ? count : 1), SEARCH_KEEP | flags, RE_SEARCH, NULL); p_ws = old_p_ws; @@ -3061,6 +3165,7 @@ cmdline_search_stat( pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, + size_t msgbuflen, int recompute, int maxcount, long timeout) @@ -3079,34 +3184,33 @@ cmdline_search_stat( if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { if (stat.incomplete == 1) - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); else if (stat.cnt > maxcount && stat.cur > maxcount) - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", maxcount, maxcount); else if (stat.cnt > maxcount) - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", maxcount, stat.cur); else - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", stat.cnt, stat.cur); } else #endif { if (stat.incomplete == 1) - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); else if (stat.cnt > maxcount && stat.cur > maxcount) - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", maxcount, maxcount); else if (stat.cnt > maxcount) - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", stat.cur, maxcount); else - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", stat.cur, stat.cnt); } - len = STRLEN(t); if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { mch_memmove(t + 2, t, len); @@ -3115,10 +3219,9 @@ cmdline_search_stat( len += 2; } - size_t msgbuf_len = STRLEN(msgbuf); - if (len > msgbuf_len) - len = msgbuf_len; - mch_memmove(msgbuf + msgbuf_len - len, t, len); + if (len > msgbuflen) + len = msgbuflen; + mch_memmove(msgbuf + msgbuflen - len, t, len); if (dirc == '?' && stat.cur == maxcount + 1) stat.cur = -1; @@ -3214,7 +3317,7 @@ update_search_stat( profile_setlimit(timeout, &start); #endif while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos, - FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL) + FORWARD, NULL, 0, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL) { done_search = TRUE; #ifdef FEAT_RELTIME @@ -3342,7 +3445,7 @@ find_pattern_in_path( pat = alloc(len + 5); if (pat == NULL) goto fpip_end; - sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr); + vim_snprintf((char *)pat, len + 5, whole ? "\\<%.*s\\>" : "%.*s", len, ptr); // ignore case according to p_ic, p_scs and pat regmatch.rm_ic = ignorecase(pat); regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); @@ -3361,8 +3464,7 @@ find_pattern_in_path( } if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) { - def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL - ? p_def : curbuf->b_p_def, + def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL ? p_def : curbuf->b_p_def, magic_isset() ? RE_MAGIC : 0); if (def_regmatch.regprog == NULL) goto fpip_end; @@ -3879,7 +3981,7 @@ exit_matched: && action == ACTION_EXPAND && !compl_status_sol() && *startp != NUL - && *(p = startp + mb_ptr2len(startp)) != NUL) + && *(startp + mb_ptr2len(startp)) != NUL) goto search_line; } line_breakcheck(); @@ -3976,6 +4078,7 @@ show_pat_in_path( long count) { char_u *p; + size_t linelen; if (did_show) msg_putchar('\n'); // cursor below last one @@ -3983,9 +4086,10 @@ show_pat_in_path( gotocmdline(TRUE); // cursor at status line if (got_int) // 'q' typed at "--more--" message return; + linelen = STRLEN(line); for (;;) { - p = line + STRLEN(line) - 1; + p = line + linelen - 1; if (fp != NULL) { // We used fgets(), so get rid of newline at end @@ -4015,6 +4119,7 @@ show_pat_in_path( { if (vim_fgets(line, LSIZE, fp)) // end of file break; + linelen = STRLEN(line); ++*lnum; } else @@ -4022,6 +4127,7 @@ show_pat_in_path( if (++*lnum > curbuf->b_ml.ml_line_count) break; line = ml_get(*lnum); + linelen = ml_get_len(*lnum); } msg_putchar('\n'); } @@ -4149,7 +4255,10 @@ f_searchcount(typval_T *argvars, typval_T *rettv) if (*pattern == NUL) goto the_end; vim_free(spats[last_idx].pat); - spats[last_idx].pat = vim_strsave(pattern); + spats[last_idx].patlen = STRLEN(pattern); + spats[last_idx].pat = vim_strnsave(pattern, spats[last_idx].patlen); + if (spats[last_idx].pat == NULL) + spats[last_idx].patlen = 0; } if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) goto the_end; // the previous pattern was never defined @@ -4983,6 +5092,54 @@ fuzzy_match_str(char_u *str, char_u *pat) } /* + * Fuzzy match the position of string 'pat' in string 'str'. + * Returns a dynamic array of matching positions. If there is no match, + * returns NULL. + */ + garray_T * +fuzzy_match_str_with_pos(char_u *str UNUSED, char_u *pat UNUSED) +{ +#ifdef FEAT_SEARCH_EXTRA + int score = 0; + garray_T *match_positions = NULL; + int_u matches[MAX_FUZZY_MATCHES]; + int j = 0; + + if (str == NULL || pat == NULL) + return NULL; + + match_positions = ALLOC_ONE(garray_T); + if (match_positions == NULL) + return NULL; + ga_init2(match_positions, sizeof(int_u), 10); + + if (!fuzzy_match(str, pat, FALSE, &score, matches, MAX_FUZZY_MATCHES) + || score == 0) + { + ga_clear(match_positions); + vim_free(match_positions); + return NULL; + } + + for (char_u *p = pat; *p != NUL; MB_PTR_ADV(p)) + { + if (!VIM_ISWHITE(PTR2CHAR(p))) + { + ga_grow(match_positions, 1); + ((int_u *)match_positions->ga_data)[match_positions->ga_len] = + matches[j]; + match_positions->ga_len++; + j++; + } + } + + return match_positions; +#else + return NULL; +#endif +} + +/* * Free an array of fuzzy string matches "fuzmatch[count]". */ void diff --git a/src/spell.c b/src/spell.c index 43c521d..909d426 100644 --- a/src/spell.c +++ b/src/spell.c @@ -1336,7 +1336,7 @@ no_spell_checking(win_T *wp) spell_move_to( win_T *wp, int dir, // FORWARD or BACKWARD - int allwords, // TRUE for "[s"/"]s", FALSE for "[S"/"]S" + smt_T behaviour, // Behaviour of the function int curline, hlf_T *attrp) // return: attributes of bad word or NULL // (only when "dir" is FORWARD) @@ -1441,7 +1441,9 @@ spell_move_to( if (attr != HLF_COUNT) { // We found a bad word. Check the attribute. - if (allwords || attr == HLF_SPB) + if (behaviour == SMT_ALL + || (behaviour == SMT_BAD && attr == HLF_SPB) + || (behaviour == SMT_RARE && attr == HLF_SPR)) { // When searching forward only accept a bad word after // the cursor. @@ -2953,6 +2955,7 @@ ex_spellrepall(exarg_T *eap UNUSED) { pos_T pos = curwin->w_cursor; char_u *frompat; + size_t frompatlen; char_u *line; char_u *p; int save_ws = p_ws; @@ -2970,7 +2973,7 @@ ex_spellrepall(exarg_T *eap UNUSED) frompat = alloc(repl_from_len + 7); if (frompat == NULL) return; - sprintf((char *)frompat, "\\V\\<%s\\>", repl_from); + frompatlen = vim_snprintf((char *)frompat, repl_from_len + 7, "\\V\\<%s\\>", repl_from); p_ws = FALSE; sub_nsubs = 0; @@ -2978,7 +2981,7 @@ ex_spellrepall(exarg_T *eap UNUSED) curwin->w_cursor.lnum = 0; while (!got_int) { - if (do_search(NULL, '/', '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 + if (do_search(NULL, '/', '/', frompat, frompatlen, 1L, SEARCH_KEEP, NULL) == 0 || u_save_cursor() == FAIL) break; diff --git a/src/spellsuggest.c b/src/spellsuggest.c index c6e6183..b305bfb 100644 --- a/src/spellsuggest.c +++ b/src/spellsuggest.c @@ -512,7 +512,7 @@ spell_suggest(int count) badlen = ml_get_curline_len() - (int)curwin->w_cursor.col; } // Find the start of the badly spelled word. - else if (spell_move_to(curwin, FORWARD, TRUE, TRUE, NULL) == 0 + else if (spell_move_to(curwin, FORWARD, SMT_ALL, TRUE, NULL) == 0 || curwin->w_cursor.col > prev_cursor.col) { // No bad word or it starts after the cursor: use the word under the diff --git a/src/strings.c b/src/strings.c index 33de175..6b2ff0a 100644 --- a/src/strings.c +++ b/src/strings.c @@ -151,7 +151,7 @@ vim_strsave_shellescape(char_u *string, int do_special, int do_newline) char_u *p; char_u *d; char_u *escaped_string; - int l; + size_t l; int csh_like; int fish_like; char_u *shname; @@ -272,8 +272,9 @@ vim_strsave_shellescape(char_u *string, int do_special, int do_newline) if (do_special && find_cmdline_var(p, &l) >= 0) { *d++ = '\\'; // insert backslash - while (--l >= 0) // copy the var - *d++ = *p++; + memcpy(d, p, l); // copy the var + d += l; + p += l; continue; } if (*p == '\\' && fish_like) diff --git a/src/structs.h b/src/structs.h index 36339c4..7e21f0f 100644 --- a/src/structs.h +++ b/src/structs.h @@ -3222,6 +3222,8 @@ struct file_buffer #ifdef FEAT_FOLDING char_u *b_p_cms; // 'commentstring' #endif + char_u *b_p_cot; // 'completeopt' local value + unsigned b_cot_flags; // flags for 'completeopt' char_u *b_p_cpt; // 'complete' #ifdef BACKSLASH_IN_FILENAME char_u *b_p_csl; // 'completeslash' @@ -4468,6 +4470,8 @@ typedef struct char_u *pum_kind; // extra kind text (may be truncated) char_u *pum_extra; // extra menu text (may be truncated) char_u *pum_info; // extra info + int pum_score; // fuzzy match score + int pum_idx; // index of item before sorting by score } pumitem_T; /* @@ -4795,6 +4799,7 @@ typedef struct soffset typedef struct spat { char_u *pat; // the pattern (in allocated memory) or NULL + size_t patlen; // the length of the pattern (0 if pat is NULL) int magic; // magicness of the pattern int no_scs; // no smartcase for this pattern soffset_T off; @@ -3901,6 +3901,8 @@ jumpto_tag( str = skip_regexp(pbuf + 1, pbuf[0], FALSE) + 1; if (str > pbuf_end - 1) // search command with nothing following { + size_t pbuflen = pbuf_end - pbuf; + save_p_ws = p_ws; save_p_ic = p_ic; save_p_scs = p_scs; @@ -3914,7 +3916,7 @@ jumpto_tag( else // start search before first line curwin->w_cursor.lnum = 0; - if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, + if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, pbuflen - 1, (long)1, search_options, NULL)) retval = OK; else @@ -3926,7 +3928,7 @@ jumpto_tag( * try again, ignore case now */ p_ic = TRUE; - if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, + if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, pbuflen - 1, (long)1, search_options, NULL)) { /* @@ -3936,14 +3938,14 @@ jumpto_tag( (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; - sprintf((char *)pbuf, "^%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', '/', pbuf, (long)1, + pbuflen = vim_snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); + if (!do_search(NULL, '/', '/', pbuf, pbuflen, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" - sprintf((char *)pbuf, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", + pbuflen = vim_snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', '/', pbuf, (long)1, + if (!do_search(NULL, '/', '/', pbuf, pbuflen, (long)1, search_options, NULL)) found = 0; } diff --git a/src/terminal.c b/src/terminal.c index 25a6a5d..648fc78 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -538,9 +538,16 @@ term_start( split_ea.addr_count = 1; } + int cmod_split_modified = FALSE; if (vertical) + { + if (!(cmdmod.cmod_split & WSP_VERT)) + cmod_split_modified = TRUE; cmdmod.cmod_split |= WSP_VERT; + } ex_splitview(&split_ea); + if (cmod_split_modified) + cmdmod.cmod_split &= ~WSP_VERT; if (curwin == old_curwin) { // split failed @@ -6164,8 +6171,16 @@ f_term_getjob(typval_T *argvars, typval_T *rettv) buf = term_get_buf(argvars, "term_getjob()"); if (buf == NULL) { - rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = VVAL_NULL; + if (in_vim9script()) + { + rettv->v_type = VAR_JOB; + rettv->vval.v_job = NULL; + } + else + { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = VVAL_NULL; + } return; } diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index b41f5f4..e31d2b5 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -143,6 +143,7 @@ NEW_TESTS = \ test_file_perm \ test_file_size \ test_filechanged \ + test_filecopy \ test_fileformat \ test_filetype \ test_filter_cmd \ @@ -278,6 +279,7 @@ NEW_TESTS = \ test_spell \ test_spell_utf8 \ test_spellfile \ + test_spellrare \ test_startup \ test_startup_utf8 \ test_stat \ @@ -403,6 +405,7 @@ NEW_TESTS_RES = \ test_expr.res \ test_file_size.res \ test_filechanged.res \ + test_filecopy.res \ test_fileformat.res \ test_filetype.res \ test_filter_cmd.res \ @@ -521,6 +524,7 @@ NEW_TESTS_RES = \ test_spell.res \ test_spell_utf8.res \ test_spellfile.res \ + test_spellrare.res \ test_startup.res \ test_stat.res \ test_statusline.res \ diff --git a/src/testdir/dumps/Test_balloon_eval_term_03.dump b/src/testdir/dumps/Test_balloon_eval_term_03.dump new file mode 100644 index 0000000..ccbe9d0 --- /dev/null +++ b/src/testdir/dumps/Test_balloon_eval_term_03.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@38|e|n|o| |e|n|o| |e|n>o +| @38|o|w|t| |o|X|t| |o|w|t +| @27| +0#0000001#ffd7ff255@17|e+0#0000000#ffffff0|r|h|t +| +0#4040ff13&@27| +0#0000001#ffd7ff255|:|6| |n|m|u|l|o|c| |2| |e|n|i|l| | +0#4040ff13#ffffff0@2|~ +| @27| +0#0000001#ffd7ff255@12|<|o|X|t| | +0#4040ff13#ffffff0@2|~ +| @27| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@2|~ +| @48|~ +| @48|~ +| @48|~ +|:+0#0000000&|c|a|l@1| |T|r|i|g@1|e|r|(|)| @16|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_mouse_popup_position_01.dump b/src/testdir/dumps/Test_mouse_popup_position_01.dump new file mode 100644 index 0000000..54ba886 --- /dev/null +++ b/src/testdir/dumps/Test_mouse_popup_position_01.dump @@ -0,0 +1,20 @@ +|0+0&#ffffff0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| >1|8| |1|9| +|~+0#4040ff13&| @31| +0#0000001#ffd7ff255|U|n|d|o| @11 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255@16 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|P|a|s|t|e| @10 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255@16 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|S|e|l|e|c|t| |W|o|r|d| @4 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|S|e|l|e|c|t| |S|e|n|t|e|n|c|e| +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|S|e|l|e|c|t| |P|a|r|a|g|r|a|p|h +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|S|e|l|e|c|t| |L|i|n|e| @4 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|S|e|l|e|c|t| |B|l|o|c|k| @3 +|~+0#4040ff13#ffffff0| @31| +0#0000001#ffd7ff255|S|e|l|e|c|t| |A|l@1| @5 +|~+0#4040ff13#ffffff0| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|:+0#0000000&|c|a|l@1| |T|r|i|g@1|e|r|(|4|5|)| @14|1|,|1| @10|A|l@1| diff --git a/src/testdir/dumps/Test_mouse_popup_position_02.dump b/src/testdir/dumps/Test_mouse_popup_position_02.dump new file mode 100644 index 0000000..7dfab52 --- /dev/null +++ b/src/testdir/dumps/Test_mouse_popup_position_02.dump @@ -0,0 +1,20 @@ +| +0&#ffffff0|9|1| |8>1| |7|1| |6|1| |5|1| |4|1| |3|1| |2|1| |1@1| |0|1| |9| |8| |7| |6| |5| |4| |3| |2| |1| |0 +| +0#0000001#ffd7ff255@11|o|d|n|U| | +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@16| +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@10|e|t|s|a|P| | +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@16| +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@4|d|r|o|W| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255|e|c|n|e|t|n|e|S| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@31|~ +|h+0#0000001#ffd7ff255|p|a|r|g|a|r|a|P| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@4|e|n|i|L| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@3|k|c|o|l|B| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@31|~ +| +0#0000001#ffd7ff255@5|l@1|A| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@31|~ +| @48|~ +| @48|~ +| @48|~ +| @48|~ +| @48|~ +| @48|~ +| @48|~ +| @48|~ +|:+0#0000000&|c|a|l@1| |T|r|i|g@1|e|r|(|5|0| |+| |1| |-| |4|5|)| @5|1|,|4|5| @9|A|l@1| diff --git a/src/testdir/dumps/Test_popup_command_rl.dump b/src/testdir/dumps/Test_popup_command_rl.dump new file mode 100644 index 0000000..b38ccd9 --- /dev/null +++ b/src/testdir/dumps/Test_popup_command_rl.dump @@ -0,0 +1,20 @@ +| +0&#ffffff0@51|e|v|i|f| |r|u|o|f| |e@1|r|h|t| |o|w|t| |e|n|o +| @46|e|v|i|f| |r|u|o|f| |e@1|r|h|t>X| |o|w|t| |e|n|o| |d|n|a +| @45| +0#0000001#ffd7ff255@12|o|d|n|U| |w+0#0000000#ffffff0|t| |e|r|o|m| |e|n|o +| +0#4040ff13&@45| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@11|e|t|s|a|P| | +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@17| +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@5|d|r|o|W| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@1|e|c|n|e|t|n|e|S| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255|h|p|a|r|g|a|r|a|P| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@5|e|n|i|L| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@4|k|c|o|l|B| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@9|~ +| @45| +0#0000001#ffd7ff255@6|l@1|A| |t|c|e|l|e|S| | +0#4040ff13#ffffff0@9|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +|:+0#0000000&|p|o|p|u|p| |P|o|p|U|p| @62 diff --git a/src/testdir/dumps/Test_pum_highlights_03.dump b/src/testdir/dumps/Test_pum_highlights_03.dump new file mode 100644 index 0000000..77d87d4 --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_03.dump @@ -0,0 +1,20 @@ +|f+0&#ffffff0|o> @72 +|f+0#00e0e07#e0e0e08|o|o+0#0000001&| @4|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|f|o@1| @1|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|b|a|r| @1|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|B|a|z| @1|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|b|a|l|a| |f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_04.dump b/src/testdir/dumps/Test_pum_highlights_04.dump new file mode 100644 index 0000000..486a59e --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_04.dump @@ -0,0 +1,20 @@ +|你*0&#ffffff0> +&@72 +|你*0#00e0e07#e0e0e08|好*0#0000001&| +&@10| +0#4040ff13#ffffff0@59 +|你*0#0000e05#ffd7ff255|好*0#0000001&|吗| +&@8| +0#4040ff13#ffffff0@59 +|你*0#0000e05#ffd7ff255|不*0#0000001&|好|吗| +&@6| +0#4040ff13#ffffff0@59 +|你*0#0000e05#ffd7ff255|可*0#0000001&|好|吗| +&@6| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_05.dump b/src/testdir/dumps/Test_pum_highlights_05.dump new file mode 100644 index 0000000..a2ce2cf --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_05.dump @@ -0,0 +1,20 @@ +|你*0&#ffffff0|吗> +&@70 +|你*0#00e0e07#e0e0e08|好*0#0000001&|吗*0#00e0e07&| +0#0000001&@8| +0#4040ff13#ffffff0@59 +|你*0#0000e05#ffd7ff255|不*0#0000001&|好|吗*0#0000e05&| +0#0000001&@6| +0#4040ff13#ffffff0@59 +|你*0#0000e05#ffd7ff255|可*0#0000001&|好|吗*0#0000e05&| +0#0000001&@6| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_06.dump b/src/testdir/dumps/Test_pum_highlights_06.dump new file mode 100644 index 0000000..3eb1524 --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_06.dump @@ -0,0 +1,20 @@ +| +0&#ffffff0@71> |o|f +| +0#4040ff13&@58| +0#0000001#e0e0e08|d|n|i|k|o@1|f| @4|o|o+0#00e0e07&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| @1|o@1|f|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| @1|r|a|b|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| @1|z|a|B|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| |a|l|a|b|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_06a.dump b/src/testdir/dumps/Test_pum_highlights_06a.dump new file mode 100644 index 0000000..5d51f11 --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_06a.dump @@ -0,0 +1,20 @@ +| +0&#ffffff0@71> |你*& +| +0#4040ff13&@59| +0#0000001#e0e0e08@10|好*&|你*0#00e0e07& +| +0#4040ff13#ffffff0@59| +0#0000001#ffd7ff255@8|吗*&|好|你*0#0000e05& +| +0#4040ff13#ffffff0@59| +0#0000001#ffd7ff255@6|吗*&|好|不|你*0#0000e05& +| +0#4040ff13#ffffff0@59| +0#0000001#ffd7ff255@6|吗*&|好|可|你*0#0000e05& +| +0#4040ff13#ffffff0@73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_06b.dump b/src/testdir/dumps/Test_pum_highlights_06b.dump new file mode 100644 index 0000000..41314ab --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_06b.dump @@ -0,0 +1,20 @@ +| +0&#ffffff0@69> |吗*&|你 +| +0#4040ff13&@59| +0#0000001#e0e0e08@8|吗*0#00e0e07&|好*0#0000001&|你*0#00e0e07& +| +0#4040ff13#ffffff0@59| +0#0000001#ffd7ff255@6|吗*0#0000e05&|好*0#0000001&|不|你*0#0000e05& +| +0#4040ff13#ffffff0@59| +0#0000001#ffd7ff255@6|吗*0#0000e05&|好*0#0000001&|可|你*0#0000e05& +| +0#4040ff13#ffffff0@73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_07.dump b/src/testdir/dumps/Test_pum_highlights_07.dump new file mode 100644 index 0000000..77d87d4 --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_07.dump @@ -0,0 +1,20 @@ +|f+0&#ffffff0|o> @72 +|f+0#00e0e07#e0e0e08|o|o+0#0000001&| @4|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|f|o@1| @1|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|b|a|r| @1|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|B|a|z| @1|f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|f+0#0000e05#ffd7ff255|o|o+0#0000001&|b|a|l|a| |f|o@1|k|i|n|d| | +0#4040ff13#ffffff0@58 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_08.dump b/src/testdir/dumps/Test_pum_highlights_08.dump new file mode 100644 index 0000000..3eb1524 --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_08.dump @@ -0,0 +1,20 @@ +| +0&#ffffff0@71> |o|f +| +0#4040ff13&@58| +0#0000001#e0e0e08|d|n|i|k|o@1|f| @4|o|o+0#00e0e07&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| @1|o@1|f|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| @1|r|a|b|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| @1|z|a|B|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@58| +0#0000001#ffd7ff255|d|n|i|k|o@1|f| |a|l|a|b|o|o+0#0000e05&|f +| +0#4040ff13#ffffff0@73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +| @73|~ +|-+2#0000000&@1| |O|m|n|i| |c|o|m|p|l|e|t|i|o|n| |(|^|O|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |9| +0#0000000&@34 diff --git a/src/testdir/dumps/Test_pum_highlights_09.dump b/src/testdir/dumps/Test_pum_highlights_09.dump new file mode 100644 index 0000000..3616c80 --- /dev/null +++ b/src/testdir/dumps/Test_pum_highlights_09.dump @@ -0,0 +1,20 @@ +|f+0&#ffffff0> @73 +|f+0#00e0e07#e0e0e08|o+0#0000001&@1| @11| +0#4040ff13#ffffff0@59 +|F+0#0000e05#ffd7ff255|o+0#0000001&@1|b|a|r| @8| +0#4040ff13#ffffff0@59 +|f+0#0000e05#ffd7ff255|o+0#0000001&@1|B|a|z| @8| +0#4040ff13#ffffff0@59 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|~| @73 +|-+2#0000000&@1| |I|N|S|E|R|T| |-@1| +0&&@62 diff --git a/src/testdir/dumps/Test_smooth_long_6.dump b/src/testdir/dumps/Test_smooth_long_6.dump index 507aa46..ba48c28 100644 --- a/src/testdir/dumps/Test_smooth_long_6.dump +++ b/src/testdir/dumps/Test_smooth_long_6.dump @@ -3,4 +3,4 @@ |t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o |f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e |x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w -|:|s|e|t| |s|c|r|o|l@1|o| @9|3|,|9|0| @9|6@1|%| +| @21|3|,|9|0| @9|6@1|%| diff --git a/src/testdir/dumps/Test_smooth_long_7.dump b/src/testdir/dumps/Test_smooth_long_7.dump index 225207f..222e001 100644 --- a/src/testdir/dumps/Test_smooth_long_7.dump +++ b/src/testdir/dumps/Test_smooth_long_7.dump @@ -3,4 +3,4 @@ |t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o |f| |t|e|x|t| |w|i>t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e |x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w|i|t|h| |l|o|t|s| |o|f| |t|e|x|t| |w -|:|s|e|t| |s|c|r|o|l@1|o| @9|3|,|1|7|0| @8|6@1|%| +| @21|3|,|1|7|0| @8|6@1|%| diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_1.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_1.dump new file mode 100644 index 0000000..6c1d223 --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_1.dump @@ -0,0 +1,8 @@ +| +0&#ffffff0@39 +@40 +@40 +> @39 +@40 +@40 +@40 +@40 diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_2.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_2.dump new file mode 100644 index 0000000..9162df0 --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_2.dump @@ -0,0 +1,8 @@ +|<+0#4040ff13#ffffff0@2|t+0#0000000&|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t +|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l +|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| +|t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| +>l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g +| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o +| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n +| @39 diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_3.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_3.dump new file mode 100644 index 0000000..1a1fcae --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_3.dump @@ -0,0 +1,8 @@ +|<+0#4040ff13#ffffff0@2|l+0#0000000&|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l +|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| +|t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| +|l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g +> |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o +| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n +|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| @20 +@40 diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_4.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_4.dump new file mode 100644 index 0000000..4ed62b6 --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_4.dump @@ -0,0 +1,8 @@ +|<+0#4040ff13#ffffff0@2|l+0#0000000&|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l +|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| +|t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| +|l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g +| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o +> |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n +|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| @20 +@40 diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_5.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_5.dump new file mode 100644 index 0000000..6d7e157 --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_5.dump @@ -0,0 +1,8 @@ +|<+0#4040ff13#ffffff0@2|l+0#0000000&|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l +|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| +|t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| +|l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g +| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o +| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n +>g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| @20 +@40 diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_6.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_6.dump new file mode 100644 index 0000000..4f5dcea --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_6.dump @@ -0,0 +1,8 @@ +|<+0#4040ff13#ffffff0@2| +0#0000000&|l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| +|l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g +| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o +| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n +|g| |t|w|o| |l|o|n|g| |t|w|o| |l|o|n|g| @20 +>t|h|r|e@1| @34 +|f|o|u|r| @35 +@40 diff --git a/src/testdir/dumps/Test_smooth_long_scrolloff_7.dump b/src/testdir/dumps/Test_smooth_long_scrolloff_7.dump new file mode 100644 index 0000000..c21c022 --- /dev/null +++ b/src/testdir/dumps/Test_smooth_long_scrolloff_7.dump @@ -0,0 +1,8 @@ +| +0&#ffffff0@39 +@40 +@40 +@40 +@40 +@40 +> @39 +@40 diff --git a/src/testdir/dumps/Test_smoothscroll_in_qf_window_1.dump b/src/testdir/dumps/Test_smoothscroll_in_qf_window_1.dump new file mode 100644 index 0000000..efdec1e --- /dev/null +++ b/src/testdir/dumps/Test_smoothscroll_in_qf_window_1.dump @@ -0,0 +1,20 @@ +> +0&#ffffff0@59 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|[+3#0000000&|N|o| |N|a|m|e|]| @50 +|<+0#4040ff13&@2| +0#af5f00255&|2+0#0000000&|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| @29 +| +0#af5f00255&|1|0| ||+0#0000000&@1| |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| +| +0#af5f00255&@3|2+0#0000000&|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| @29 +| +0#af5f00255&|1@1| ||+0#0000000&@1| |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| +| +0#af5f00255&@3|2+0#0000000&|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| @29 +|[+1&&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| @44 +| +0&&@59 diff --git a/src/testdir/dumps/Test_smoothscroll_in_qf_window_2.dump b/src/testdir/dumps/Test_smoothscroll_in_qf_window_2.dump new file mode 100644 index 0000000..200df91 --- /dev/null +++ b/src/testdir/dumps/Test_smoothscroll_in_qf_window_2.dump @@ -0,0 +1,20 @@ +> +0&#ffffff0@59 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|[+3#0000000&|N|o| |N|a|m|e|]| @50 +| +0#af5f00255&@1|1| | +0#0000000&@55 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +|~| @58 +|[+1#0000000&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| @44 +|:+0&&|c|a|l@1| |s|e|t|q|f|l|i|s|t|(|[|]|,| |'|r|'|)| @35 diff --git a/src/testdir/dumps/Test_smoothscroll_in_qf_window_3.dump b/src/testdir/dumps/Test_smoothscroll_in_qf_window_3.dump new file mode 100644 index 0000000..54b726f --- /dev/null +++ b/src/testdir/dumps/Test_smoothscroll_in_qf_window_3.dump @@ -0,0 +1,20 @@ +> +0&#ffffff0@59 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|[+3#0000000&|N|o| |N|a|m|e|]| @50 +| +0#af5f00255&@1|1| ||+0#0000000#ffff4012@1| |f|o@1| @49 +| +0#af5f00255#ffffff0@1|2| ||+0#0000000&@1| |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| +| +0#af5f00255&@3|2+0#0000000&|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| @29 +| +0#af5f00255&@1|3| ||+0#0000000&@1| |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| +| +0#af5f00255&@3|2+0#0000000&|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| @29 +|[+1&&|Q|u|i|c|k|f|i|x| |L|i|s|t|]| @44 +|:+0&&|c|a|l@1| |s|e|t|q|f|l|i|s|t|(|g|:|l|,| |'|r|'|)| @34 diff --git a/src/testdir/dumps/Test_smoothscroll_in_qf_window_4.dump b/src/testdir/dumps/Test_smoothscroll_in_qf_window_4.dump new file mode 100644 index 0000000..1a4cdd2 --- /dev/null +++ b/src/testdir/dumps/Test_smoothscroll_in_qf_window_4.dump @@ -0,0 +1,20 @@ +> +0&#ffffff0@59 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|[+3#0000000&|N|o| |N|a|m|e|]| @50 +| +0#af5f00255&@1|1| ||+0#0000000#ffff4012@1| |0| |1| |2| |3| |4| |5| |6| |7| |8| |9| |1|0| |1@1| |1|2| |1|3| |1|4| |1|5| |1|6| |1|7| |1|8| |1|9| |2|0| +| +0#af5f00255#ffffff0@3|2+0#0000000#ffff4012|1| |2@1| |2|3| |2|4| |2|5| |2|6| |2|7| |2|8| |2|9| |3|0| |3|1| |3|2| |3@1| |3|4| |3|5| |3|6| |3|7| |3|8| |3|9 +| +0#af5f00255#ffffff0@3| +0#0000000#ffff4012|4|0| |4|1| |4|2| |4|3| |4@1| |4|5| |4|6| |4|7| |4|8| |4|9| |5|0| |5|1| |5|2| |5|3| |5|4| |5@1| |5|6| |5|7| |5 +| +0#af5f00255#ffffff0@3|8+0#0000000#ffff4012| |5|9| |6|0| |6|1| |6|2| |6|3| |6|4| |6|5| |6@1| |6|7| |6|8| |6|9| |7|0| |7|1| |7|2| |7|3| |7|4| |7|5| |7|6| +| +0#af5f00255#ffffff0@3|7+0#0000000#ffff4012@1| |7|8| |7|9| |8|0| |8|1| |8|2| |8|3| |8|4| |8|5| |8|6| |8|7| |8@1| |8|9| |9|0| |9|1| |9|2| |9|3| |9|4| |9|5 +|[+1&#ffffff0|Q|u|i|c|k|f|i|x| |L|i|s|t|]| @44 +|:+0&&|c|a|l@1| |s|e|t|q|f|l|i|s|t|(|g|:|l|1|,| |'|r|'|)| @33 diff --git a/src/testdir/dumps/Test_smoothscroll_in_qf_window_5.dump b/src/testdir/dumps/Test_smoothscroll_in_qf_window_5.dump new file mode 100644 index 0000000..fcc2106 --- /dev/null +++ b/src/testdir/dumps/Test_smoothscroll_in_qf_window_5.dump @@ -0,0 +1,20 @@ +> +0&#ffffff0@59 +|~+0#4040ff13&| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|~| @58 +|[+3#0000000&|N|o| |N|a|m|e|]| @50 +|<+0#4040ff13&@2| +0#af5f00255&| +0#0000000#ffff4012|9|3|7| |9|3|8| |9|3|9| |9|4|0| |9|4|1| |9|4|2| |9|4|3| |9|4@1| |9|4|5| |9|4|6| |9|4|7| |9|4|8| |9|4|9| |9|5|0 +| +0#af5f00255#ffffff0@3| +0#0000000#ffff4012|9|5|1| |9|5|2| |9|5|3| |9|5|4| |9|5@1| |9|5|6| |9|5|7| |9|5|8| |9|5|9| |9|6|0| |9|6|1| |9|6|2| |9|6|3| |9|6|4 +| +0#af5f00255#ffffff0@3| +0#0000000#ffff4012|9|6|5| |9|6@1| |9|6|7| |9|6|8| |9|6|9| |9|7|0| |9|7|1| |9|7|2| |9|7|3| |9|7|4| |9|7|5| |9|7|6| |9|7@1| |9|7|8 +| +0#af5f00255#ffffff0@3| +0#0000000#ffff4012|9|7|9| |9|8|0| |9|8|1| |9|8|2| |9|8|3| |9|8|4| |9|8|5| |9|8|6| |9|8|7| |9|8@1| |9|8|9| |9@1|0| |9@1|1| |9@1|2 +| +0#af5f00255#ffffff0@3| +0#0000000#ffff4012|9@1|3| |9@1|4| |9@1|5| |9@1|6| |9@1|7| |9@1|8| |9@2| @27 +|[+1&#ffffff0|Q|u|i|c|k|f|i|x| |L|i|s|t|]| @44 +|:+0&&|c|a|l@1| |s|e|t|q|f|l|i|s|t|(|g|:|l|1|,| |'|r|'|)| @33 diff --git a/src/testdir/dumps/Test_wildmenu_pum_hl_match_1.dump b/src/testdir/dumps/Test_wildmenu_pum_hl_match_1.dump new file mode 100644 index 0000000..0772682 --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_hl_match_1.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +|~+0#4040ff13&| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @3| +0#0000001#e0e0e08|p+0#00e0e07&|l|a+0#0000001&|c+0#00e0e07&|e+0#0000001&| @9| +0#4040ff13#ffffff0@28 +|~| @3| +0#0000001#ffd7ff255|u|n|p+0#0000e05&|l|a+0#0000001&|c+0#0000e05&|e+0#0000001&| @7| +0#4040ff13#ffffff0@28 +|:+0#0000000&|s|i|g|n| |p|l|a|c|e> @38 diff --git a/src/testdir/dumps/Test_wildmenu_pum_hl_match_2.dump b/src/testdir/dumps/Test_wildmenu_pum_hl_match_2.dump new file mode 100644 index 0000000..ec781ef --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_hl_match_2.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +|~+0#4040ff13&| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @3| +0#0000001#ffd7ff255|p+0#0000e05&|l|a+0#0000001&|c+0#0000e05&|e+0#0000001&| @9| +0#4040ff13#ffffff0@28 +|~| @3| +0#0000001#e0e0e08|u|n|p+0#00e0e07&|l|a+0#0000001&|c+0#00e0e07&|e+0#0000001&| @7| +0#4040ff13#ffffff0@28 +|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @36 diff --git a/src/testdir/dumps/Test_wildmenu_pum_hl_match_3.dump b/src/testdir/dumps/Test_wildmenu_pum_hl_match_3.dump new file mode 100644 index 0000000..afb792a --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_hl_match_3.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +|~+0#4040ff13&| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @3| +0#0000001#ffd7ff255|p+0#0000e05&|l|a+0#0000001&|c+0#0000e05&|e+0#0000001&| @9| +0#4040ff13#ffffff0@28 +|~| @3| +0#0000001#ffd7ff255|u|n|p+0#0000e05&|l|a+0#0000001&|c+0#0000e05&|e+0#0000001&| @7| +0#4040ff13#ffffff0@28 +|:+0#0000000&|s|i|g|n| |p|l|c> @40 diff --git a/src/testdir/dumps/Test_wildmenu_pum_hl_match_4.dump b/src/testdir/dumps/Test_wildmenu_pum_hl_match_4.dump new file mode 100644 index 0000000..0be8053 --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_hl_match_4.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +|~+0#4040ff13&| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @3| +0#0000001#e0e0e08|u+0#00e0e07&|n|d+0#0000001&|e|f|i|n|e| @6| +0#4040ff13#ffffff0@28 +|~| @3| +0#0000001#ffd7ff255|u+0#0000e05&|n|p+0#0000001&|l|a|c|e| @7| +0#4040ff13#ffffff0@28 +|:+0#0000000&|s|i|g|n| |u|n|d|e|f|i|n|e> @35 diff --git a/src/testdir/dumps/Test_wildmenu_pum_hl_match_5.dump b/src/testdir/dumps/Test_wildmenu_pum_hl_match_5.dump new file mode 100644 index 0000000..7453cb9 --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_hl_match_5.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +|~+0#4040ff13&| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @3| +0#0000001#ffd7ff255|u+0#0000e05&|n|d+0#0000001&|e|f|i|n|e| @6| +0#4040ff13#ffffff0@28 +|~| @3| +0#0000001#e0e0e08|u+0#00e0e07&|n|p+0#0000001&|l|a|c|e| @7| +0#4040ff13#ffffff0@28 +|:+0#0000000&|s|i|g|n| |u|n|p|l|a|c|e> @36 diff --git a/src/testdir/dumps/Test_wildmenu_pum_hl_match_6.dump b/src/testdir/dumps/Test_wildmenu_pum_hl_match_6.dump new file mode 100644 index 0000000..8345007 --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_hl_match_6.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +|~+0#4040ff13&| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @48 +|~| @3| +0#0000001#ffd7ff255|u+0#0000e05&|n|d+0#0000001&|e|f|i|n|e| @6| +0#4040ff13#ffffff0@28 +|~| @3| +0#0000001#ffd7ff255|u+0#0000e05&|n|p+0#0000001&|l|a|c|e| @7| +0#4040ff13#ffffff0@28 +|:+0#0000000&|s|i|g|n| |u|n> @41 diff --git a/src/testdir/dumps/Test_wildmenu_pum_rl.dump b/src/testdir/dumps/Test_wildmenu_pum_rl.dump new file mode 100644 index 0000000..55db4ca --- /dev/null +++ b/src/testdir/dumps/Test_wildmenu_pum_rl.dump @@ -0,0 +1,10 @@ +| +0&#ffffff0@49 +| +0#4040ff13&@48|~ +| @48|~ +| @4| +0#0000001#e0e0e08|d|e|f|i|n|e| @8| +0#4040ff13#ffffff0@27|~ +| @4| +0#0000001#ffd7ff255|j|u|m|p| @10| +0#4040ff13#ffffff0@27|~ +| @4| +0#0000001#ffd7ff255|l|i|s|t| @10| +0#4040ff13#ffffff0@27|~ +| @4| +0#0000001#ffd7ff255|p|l|a|c|e| @9| +0#4040ff13#ffffff0@27|~ +| @4| +0#0000001#ffd7ff255|u|n|d|e|f|i|n|e| @6| +0#4040ff13#ffffff0@27|~ +| @4| +0#0000001#ffd7ff255|u|n|p|l|a|c|e| @7| +0#4040ff13#ffffff0@27|~ +|:+0#0000000&|s|i|g|n| |d|e|f|i|n|e> @37 diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim index a195bca..8cca2b9 100644 --- a/src/testdir/gen_opt_test.vim +++ b/src/testdir/gen_opt_test.vim @@ -76,7 +76,7 @@ let test_values = { \ 'clipboard': [['', 'unnamed', 'autoselect,unnamed', 'html', 'exclude:vimdisplay'], ['xxx', '\ze*', 'exclude:\\%(']], \ 'colorcolumn': [['', '8', '+2'], ['xxx']], \ 'comments': [['', 'b:#'], ['xxx']], - \ 'commentstring': [['', '/*%s*/'], ['xxx']], + \ 'commentstring': [['', '/*\ %s\ */'], ['xxx']], \ 'complete': [['', 'w,b'], ['xxx']], \ 'concealcursor': [['', 'n', 'nvic'], ['xxx']], \ 'completeopt': [['', 'menu', 'menu,longest'], ['xxx', 'menu,,,longest,']], diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index 24e1daf..c9f257a 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -4024,6 +4024,32 @@ func Test_autocmd_get() \ event: 'BufAdd', pattern: '*.abc'})) call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns', \ event: 'BufWipeout'})) + + " Test for getting autocmds after removing one inside an autocmd + func CheckAutocmdGet() + augroup TestAutoCmdFns + autocmd! BufAdd *.vim + augroup END + + let expected = [ + \ #{cmd: 'echo "bufadd-py"', group: 'TestAutoCmdFns', + \ pattern: '*.py', nested: v:false, once: v:false, + \ event: 'BufAdd'}, + \ #{cmd: 'echo "bufhidden"', group: 'TestAutoCmdFns', + \ pattern: '*.vim', nested: v:false, + \ once: v:false, event: 'BufHidden'}] + + call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'})) + call assert_equal([expected[0]], + \ autocmd_get(#{group: 'TestAutoCmdFns', pattern: '*.py'})) + call assert_equal([expected[1]], + \ autocmd_get(#{group: 'TestAutoCmdFns', pattern: '*.vim'})) + endfunc + + autocmd User Xauget call CheckAutocmdGet() + doautocmd User Xauget + autocmd! User Xauget + call assert_fails("call autocmd_get(#{group: 'abc', event: 'BufAdd'})", \ 'E367:') let cmd = "echo autocmd_get(#{group: 'TestAutoCmdFns', event: 'abc'})" diff --git a/src/testdir/test_autoload.vim b/src/testdir/test_autoload.vim index 835b81e..d0f9136 100644 --- a/src/testdir/test_autoload.vim +++ b/src/testdir/test_autoload.vim @@ -26,5 +26,4 @@ func Test_autoload_vim9script() call assert_equal(49, auto9#Add42(7)) endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_balloon.vim b/src/testdir/test_balloon.vim index 5e84f9e..80d5831 100644 --- a/src/testdir/test_balloon.vim +++ b/src/testdir/test_balloon.vim @@ -64,4 +64,29 @@ func Test_balloon_eval_term_visual() call StopVimInTerminal(buf) endfunc +func Test_balloon_eval_term_rightleft() + CheckFeature rightleft + + " Use <Ignore> after <MouseMove> to return from vgetc() without removing + " the balloon. + let xtra_lines =<< trim [CODE] + set rightleft + func Trigger() + call test_setmouse(2, 50 + 1 - 6) + call feedkeys("\<MouseMove>\<Ignore>", "xt") + endfunc + [CODE] + call writefile(s:common_script + xtra_lines, 'XTest_beval_rl', 'D') + + " Check that the balloon shows up after a mouse move + let buf = RunVimInTerminal('-S XTest_beval_rl', {'rows': 10, 'cols': 50}) + call TermWait(buf, 50) + call term_sendkeys(buf, 'll') + call term_sendkeys(buf, ":call Trigger()\<CR>") + call VerifyScreenDump(buf, 'Test_balloon_eval_term_03', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_blob.vim b/src/testdir/test_blob.vim index cccecb7..f0e8209 100644 --- a/src/testdir/test_blob.vim +++ b/src/testdir/test_blob.vim @@ -74,6 +74,13 @@ func Test_blob_assign() VAR l = [0z12] VAR m = deepcopy(l) LET m[0] = 0z34 #" E742 or E741 should not occur. + + VAR blob1 = 0z10 + LET blob1 += test_null_blob() + call assert_equal(0z10, blob1) + LET blob1 = test_null_blob() + LET blob1 += 0z20 + call assert_equal(0z20, blob1) END call v9.CheckLegacyAndVim9Success(lines) @@ -97,6 +104,18 @@ func Test_blob_assign() let lines =<< trim END VAR b = 0zDEADBEEF + LET b[0 : 1] = 0x1122 + END + call v9.CheckLegacyAndVim9Failure(lines, ['E709:', 'E1012:', 'E709:']) + + let lines =<< trim END + VAR b = 0zDEADBEEF + LET b[0] = 0z11 + END + call v9.CheckLegacyAndVim9Failure(lines, ['E974:', 'E974:', 'E1012:']) + + let lines =<< trim END + VAR b = 0zDEADBEEF LET b ..= 0z33 END call v9.CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:']) @@ -331,6 +350,17 @@ func Test_blob_for_loop() call assert_equal(5, i) END call v9.CheckLegacyAndVim9Success(lines) + + " Test for skipping the loop var assignment in a for loop + let lines =<< trim END + VAR blob = 0z998877 + VAR c = 0 + for _ in blob + LET c += 1 + endfor + call assert_equal(3, c) + END + call v9.CheckLegacyAndVim9Success(lines) endfunc func Test_blob_concatenate() @@ -819,6 +849,7 @@ func Test_indexof() call assert_equal(-1, indexof(test_null_blob(), "v:val == 0xde")) call assert_equal(-1, indexof(b, test_null_string())) call assert_equal(-1, indexof(b, test_null_function())) + call assert_equal(-1, indexof(b, "")) let b = 0z01020102 call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0})) @@ -830,6 +861,7 @@ func Test_indexof() " failure cases call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:') call assert_fails('let i = indexof(b, {})', 'E1256:') + call assert_fails('let i = indexof(b, " ")', 'E15:') endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim index ed8f89d..8b7489e 100644 --- a/src/testdir/test_cmdline.vim +++ b/src/testdir/test_cmdline.vim @@ -2741,6 +2741,55 @@ func Test_wildmenu_pum_odd_wildchar() call StopVimInTerminal(buf) endfunc +" Test that 'rightleft' should not affect cmdline completion popup menu. +func Test_wildmenu_pum_rightleft() + CheckFeature rightleft + CheckScreendump + + let lines =<< trim END + set wildoptions=pum + set rightleft + END + call writefile(lines, 'Xwildmenu_pum_rl', 'D') + let buf = RunVimInTerminal('-S Xwildmenu_pum_rl', #{rows: 10, cols: 50}) + + call term_sendkeys(buf, ":sign \<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_rl', {}) + + call StopVimInTerminal(buf) +endfunc + +" Test highlighting matched text in cmdline completion popup menu. +func Test_wildmenu_pum_hl_match() + CheckScreendump + + let lines =<< trim END + set wildoptions=pum,fuzzy + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xwildmenu_pum_hl', 'D') + let buf = RunVimInTerminal('-S Xwildmenu_pum_hl', #{rows: 10, cols: 50}) + + call term_sendkeys(buf, ":sign plc\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_1', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_2', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_3', {}) + call term_sendkeys(buf, "\<Esc>:set wildoptions-=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, ":sign un\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_4', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_5', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_6', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) +endfunc + " Test for completion after a :substitute command followed by a pipe (|) " character func Test_cmdline_complete_substitute() diff --git a/src/testdir/test_edit.vim b/src/testdir/test_edit.vim index 789e44c..1665330 100644 --- a/src/testdir/test_edit.vim +++ b/src/testdir/test_edit.vim @@ -1952,6 +1952,11 @@ func Test_edit_insert_reg() let @r = 'sample' call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt") call assert_equal('"', g:Line) + + " Test for inserting an null and an empty list + call feedkeys("a\<C-R>=test_null_list()\<CR>", "xt") + call feedkeys("a\<C-R>=[]\<CR>", "xt") + call assert_equal(['r'], getbufline('', 1, '$')) call test_override('ALL', 0) close! endfunc diff --git a/src/testdir/test_filecopy.vim b/src/testdir/test_filecopy.vim new file mode 100644 index 0000000..b526dce --- /dev/null +++ b/src/testdir/test_filecopy.vim @@ -0,0 +1,72 @@ +" Test filecopy() + +source check.vim +source shared.vim + +func Test_copy_file_to_file() + call writefile(['foo'], 'Xcopy1') + + call assert_true(filecopy('Xcopy1', 'Xcopy2')) + + call assert_equal(['foo'], readfile('Xcopy2')) + + " When the destination file already exists, it should not be overwritten. + call writefile(['foo'], 'Xcopy1') + call writefile(['bar'], 'Xcopy2', 'D') + call assert_false(filecopy('Xcopy1', 'Xcopy2')) + call assert_equal(['bar'], readfile('Xcopy2')) + + call delete('Xcopy2') + call delete('Xcopy1') +endfunc + +func Test_copy_symbolic_link() + CheckUnix + + call writefile(['text'], 'Xtestfile', 'D') + silent !ln -s -f Xtestfile Xtestlink + + call assert_true(filecopy('Xtestlink', 'Xtestlink2')) + call assert_equal('link', getftype('Xtestlink2')) + call assert_equal(['text'], readfile('Xtestlink2')) + + " When the destination file already exists, it should not be overwritten. + call assert_false(filecopy('Xtestlink', 'Xtestlink2')) + + call delete('Xtestlink2') + call delete('Xtestlink') + call delete('Xtestfile') +endfunc + +func Test_copy_dir_to_dir() + call mkdir('Xcopydir1') + call writefile(['foo'], 'Xcopydir1/Xfilecopy') + call mkdir('Xcopydir2') + + " Directory copy is not supported + call assert_false(filecopy('Xcopydir1', 'Xcopydir2')) + + call delete('Xcopydir2', 'rf') + call delete('Xcopydir1', 'rf') +endfunc + +func Test_copy_fails() + CheckUnix + + call writefile(['foo'], 'Xfilecopy', 'D') + + " Can't copy into a non-existing directory. + call assert_false(filecopy('Xfilecopy', 'Xdoesnotexist/Xfilecopy')) + + " Can't copy a non-existing file. + call assert_false(filecopy('Xdoesnotexist', 'Xfilecopy2')) + call assert_equal('', glob('Xfilecopy2')) + + " Can't copy to en empty file name. + call assert_false(filecopy('Xfilecopy', '')) + + call assert_fails('call filecopy("Xfilecopy", [])', 'E1174:') + call assert_fails('call filecopy(0z, "Xfilecopy")', 'E1174:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_filetype.vim b/src/testdir/test_filetype.vim index f8fe9ec..d0a078f 100644 --- a/src/testdir/test_filetype.vim +++ b/src/testdir/test_filetype.vim @@ -131,7 +131,7 @@ def s:GetFilenameChecks(): dict<list<string>> bst: ['file.bst'], bzl: ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'], bzr: ['bzr_log.any', 'bzr_log.file'], - c: ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'], + c: ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg', 'file.mdh', 'file.epro'], cabal: ['file.cabal'], cabalconfig: ['cabal.config', expand("$HOME/.config/cabal/config")] + WhenConfigHome('$XDG_CONFIG_HOME/cabal/config'), cabalproject: ['cabal.project', 'cabal.project.local'], @@ -336,6 +336,7 @@ def s:GetFilenameChecks(): dict<list<string>> htmlm4: ['file.html.m4'], httest: ['file.htt', 'file.htb'], hurl: ['file.hurl'], + hyprlang: ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf'], i3config: ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], ibasic: ['file.iba', 'file.ibi'], icemenu: ['/.icewm/menu', 'any/.icewm/menu'], @@ -344,6 +345,7 @@ def s:GetFilenameChecks(): dict<list<string>> inform: ['file.inf', 'file.INF'], initng: ['/etc/initng/any/file.i', 'file.ii', 'any/etc/initng/any/file.i'], inittab: ['inittab'], + inko: ['file.inko'], ipfilter: ['ipf.conf', 'ipf6.conf', 'ipf.rules'], iss: ['file.iss'], ist: ['file.ist', 'file.mst'], @@ -358,10 +360,11 @@ def s:GetFilenameChecks(): dict<list<string>> javascriptreact: ['file.jsx'], jess: ['file.clp'], jgraph: ['file.jgr'], + jj: ['file.jjdescription'], jq: ['file.jq'], jovial: ['file.jov', 'file.j73', 'file.jovial'], jproperties: ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'], - json: ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock'], + json: ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock'], json5: ['file.json5'], jsonc: ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc'], jsonl: ['file.jsonl'], @@ -417,7 +420,7 @@ def s:GetFilenameChecks(): dict<list<string>> mail: ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml', 'reportbug-file'], mailaliases: ['/etc/mail/aliases', '/etc/aliases', 'any/etc/aliases', 'any/etc/mail/aliases'], mailcap: ['.mailcap', 'mailcap'], - make: ['file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile'], + make: ['file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile', 'Kbuild'], mallard: ['file.page'], man: ['file.man'], manconf: ['/etc/man.conf', 'man.config', 'any/etc/man.conf'], @@ -574,6 +577,7 @@ def s:GetFilenameChecks(): dict<list<string>> psl: ['file.psl'], pug: ['file.pug'], puppet: ['file.pp'], + purescript: ['file.purs'], pymanifest: ['MANIFEST.in'], pyret: ['file.arr'], pyrex: ['file.pyx', 'file.pxd'], @@ -588,6 +592,7 @@ def s:GetFilenameChecks(): dict<list<string>> radiance: ['file.rad', 'file.mat'], raku: ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], raml: ['file.raml'], + rasi: ['file.rasi'], ratpoison: ['.ratpoisonrc', 'ratpoisonrc'], rbs: ['file.rbs'], rc: ['file.rc', 'file.rch'], @@ -640,7 +645,7 @@ def s:GetFilenameChecks(): dict<list<string>> sh: ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '.bash_history', '.bash-history', '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'APKBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', 'file.bats', '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', - 'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf'], + 'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport'], sieve: ['file.siv', 'file.sieve'], sil: ['file.sil'], simula: ['file.sim'], @@ -651,6 +656,7 @@ def s:GetFilenameChecks(): dict<list<string>> slang: ['file.sl'], sage: ['file.sage'], slice: ['file.ice'], + slint: ['file.slint'], slpconf: ['/etc/slp.conf', 'any/etc/slp.conf'], slpreg: ['/etc/slp.reg', 'any/etc/slp.reg'], slpspi: ['/etc/slp.spi', 'any/etc/slp.spi'], @@ -663,6 +669,7 @@ def s:GetFilenameChecks(): dict<list<string>> smith: ['file.smt', 'file.smith'], smithy: ['file.smithy'], sml: ['file.sml'], + snakemake: ['file.smk', 'Snakefile'], snobol4: ['file.sno', 'file.spt'], solidity: ['file.sol'], solution: ['file.sln'], @@ -686,6 +693,7 @@ def s:GetFilenameChecks(): dict<list<string>> starlark: ['file.ipd', 'file.star', 'file.starlark'], stata: ['file.ado', 'file.do', 'file.imata', 'file.mata'], stp: ['file.stp'], + stylus: ['a.styl', 'file.stylus'], sudoers: ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'], supercollider: ['file.quark'], surface: ['file.sface'], @@ -746,11 +754,12 @@ def s:GetFilenameChecks(): dict<list<string>> tcl: ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc', '.tclsh-history', '.xsctcmdhistory', '.xsdbcmdhistory'], tablegen: ['file.td'], teal: ['file.tl'], + templ: ['file.templ'], template: ['file.tmpl'], teraterm: ['file.ttl'], terminfo: ['file.ti'], 'terraform-vars': ['file.tfvars'], - tex: ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl', 'any/.texlive/texmf-config/tex/latex/file/file.cfg', 'file.pgf', 'file.nlo', 'file.nls', 'file.out', 'file.thm', 'file.eps_tex', 'file.pygtex', 'file.pygstyle', 'file.clo', 'file.aux', 'file.brf', 'file.ind', 'file.lof', 'file.loe', 'file.nav', 'file.vrb', 'file.ins', 'file.tikz', 'file.bbx', 'file.cbx', 'file.beamer'], + tex: ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl', 'any/.texlive/texmf-config/tex/latex/file/file.cfg', 'file.pgf', 'file.nlo', 'file.nls', 'file.thm', 'file.eps_tex', 'file.pygtex', 'file.pygstyle', 'file.clo', 'file.aux', 'file.brf', 'file.ind', 'file.lof', 'file.loe', 'file.nav', 'file.vrb', 'file.ins', 'file.tikz', 'file.bbx', 'file.cbx', 'file.beamer'], texinfo: ['file.texinfo', 'file.texi', 'file.txi'], texmf: ['texmf.cnf'], text: ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], @@ -985,6 +994,7 @@ def s:GetScriptChecks(): dict<list<list<string>>> ['#!/path/regina']], janet: [['#!/path/janet']], dart: [['#!/path/dart']], + vim: [['#!/path/vim']], } enddef @@ -1506,6 +1516,41 @@ func Test_git_file() filetype off endfunc +func Test_haredoc_file() + filetype on + call assert_true(mkdir('foo/bar', 'pR')) + + call writefile([], 'README', 'D') + split README + call assert_notequal('haredoc', &filetype) + bwipe! + + let g:filetype_haredoc = 1 + split README + call assert_notequal('haredoc', &filetype) + bwipe! + + call writefile([], 'foo/quux.ha') + split README + call assert_equal('haredoc', &filetype) + bwipe! + call delete('foo/quux.ha') + + call writefile([], 'foo/bar/baz.ha', 'D') + split README + call assert_notequal('haredoc', &filetype) + bwipe! + + let g:haredoc_search_depth = 2 + split README + call assert_equal('haredoc', &filetype) + bwipe! + unlet g:filetype_haredoc + unlet g:haredoc_search_depth + + filetype off +endfunc + func Test_hook_file() filetype on @@ -1684,14 +1729,14 @@ func Test_mod_file() call assert_equal('pim', b:modula2.dialect) bwipe! - " Modula-2 program MODULE with priorty (and uppercase extension) + " Modula-2 program MODULE with priority (and uppercase extension) call writefile(['MODULE Module2Mod [42];'], 'Xfile.MOD') split Xfile.MOD call assert_equal('modula2', &filetype) call assert_equal('pim', b:modula2.dialect) bwipe! - " Modula-2 implementation MODULE with priorty (and uppercase extension) + " Modula-2 implementation MODULE with priority (and uppercase extension) call writefile(['IMPLEMENTATION MODULE Module2Mod [42];'], 'Xfile.MOD') split Xfile.MOD call assert_equal('modula2', &filetype) diff --git a/src/testdir/test_fold.vim b/src/testdir/test_fold.vim index 3c78e62..dedc4a2 100644 --- a/src/testdir/test_fold.vim +++ b/src/testdir/test_fold.vim @@ -8,7 +8,73 @@ func PrepIndent(arg) return [a:arg] + repeat(["\t".a:arg], 5) endfu -func Test_address_fold() +func Test_address_fold_new_default_commentstring() + " Test with the new commentstring defaults, that includes padding after v9.1.464 + new + call setline(1, ['int FuncName() {/* {{{ */', 1, 2, 3, 4, 5, '}/* }}} */', + \ 'after fold 1', 'after fold 2', 'after fold 3']) + setl fen fdm=marker + " The next commands should all copy the same part of the buffer, + " regardless of the addressing type, since the part to be copied + " is folded away + :1y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :.+y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :.,.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :sil .1,.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + " use silent to make E493 go away + :sil .+,.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :,y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :,+y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */','after fold 1'], getreg(0,1,1)) + " using .+3 as second address should c opy the whole folded line + the next 3 + " lines + :.,+3y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */', + \ 'after fold 1', 'after fold 2' , 'after fold 3'], getreg(0,1,1)) + :sil .,-2y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + + " now test again with folding disabled + set nofoldenable + :1y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + :.y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + :.+y + call assert_equal(['1'], getreg(0,1,1) ) + :.,.y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + " use silent to make E493 go away + :sil .1,.y + call assert_equal(['int FuncName() {/* {{{ */', '1'], getreg(0,1,1)) + " use silent to make E493 go away + :sil .+,.y + call assert_equal(['int FuncName() {/* {{{ */', '1'], getreg(0,1,1)) + :,y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + :,+y + call assert_equal(['int FuncName() {/* {{{ */', '1'], getreg(0,1,1)) + " using .+3 as second address should c opy the whole folded line + the next 3 + " lines + :.,+3y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3'], getreg(0,1,1)) + :7 + :sil .,-2y + call assert_equal(['4', '5', '}/* }}} */'], getreg(0,1,1)) + + quit! +endfunc + +func Test_address_fold_old_default_commentstring() + " Test with the old commentstring defaults, before v9.1.464 new call setline(1, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/', \ 'after fold 1', 'after fold 2', 'after fold 3']) @@ -719,7 +785,7 @@ func Test_fold_create_marker_in_C() call append(0, content) call cursor(c + 1, 1) norm! zfG - call assert_equal(content[c] . (c < 4 ? '{{{' : '/*{{{*/'), getline(c + 1)) + call assert_equal(content[c] . (c < 4 ? '{{{' : '/* {{{ */'), getline(c + 1)) endfor set fdm& fdl& @@ -1601,6 +1667,63 @@ func Test_foldtext_scriptlocal_func() delfunc s:FoldText endfunc +" Test for setting 'foldtext' from the modeline and executing the expression +" in a sandbox +func Test_foldtext_in_modeline() + func ModelineFoldText() + call feedkeys('aFoo', 'xt') + return "folded text" + endfunc + let lines =<< trim END + func T() + let i = 1 + endfunc + " vim: foldenable foldtext=ModelineFoldText() + END + call writefile(lines, 'Xmodelinefoldtext', 'D') + + set modeline modelineexpr + split Xmodelinefoldtext + + call cursor(1, 1) + normal! zf3j + call assert_equal('folded text', foldtextresult(1)) + call assert_equal(lines, getbufline('', 1, '$')) + + bw! + set modeline& modelineexpr& + delfunc ModelineFoldText +endfunc + +" Test for setting 'foldexpr' from the modeline and executing the expression +" in a sandbox +func Test_foldexpr_in_modeline() + func ModelineFoldExpr() + call feedkeys('aFoo', 'xt') + return strlen(matchstr(getline(v:lnum),'^\s*')) + endfunc + let lines =<< trim END + aaa + bbb + ccc + ccc + bbb + aaa + " vim: foldenable foldmethod=expr foldexpr=ModelineFoldExpr() + END + call writefile(lines, 'Xmodelinefoldexpr', 'D') + + set modeline modelineexpr + split Xmodelinefoldexpr + + call assert_equal(2, foldlevel(3)) + call assert_equal(lines, getbufline('', 1, '$')) + + bw! + set modeline& modelineexpr& + delfunc ModelineFoldExpr +endfunc + " Make sure a fold containing a nested fold is split correctly when using " foldmethod=indent func Test_fold_split() diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index f0d7385..acdb954 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -3697,6 +3697,73 @@ func Test_getmousepos() \ column: 8, \ coladd: 21, \ }, getmousepos()) + + 30vnew + setlocal smoothscroll number + call setline(1, join(range(100))) + exe "normal! \<C-E>" + call test_setmouse(1, 5) + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 5, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 5, + \ line: 1, + \ column: 27, + \ coladd: 0, + \ }, getmousepos()) + call test_setmouse(2, 5) + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 5, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 5, + \ line: 1, + \ column: 53, + \ coladd: 0, + \ }, getmousepos()) + + exe "normal! \<C-E>" + call test_setmouse(1, 5) + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 5, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 5, + \ line: 1, + \ column: 53, + \ coladd: 0, + \ }, getmousepos()) + call test_setmouse(2, 5) + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 5, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 5, + \ line: 1, + \ column: 79, + \ coladd: 0, + \ }, getmousepos()) + + vert resize 4 + call test_setmouse(2, 2) + " This used to crash Vim + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 2, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 2, + \ line: 1, + \ column: 53, + \ coladd: 0, + \ }, getmousepos()) + + bwipe! bwipe! endfunc @@ -3728,6 +3795,33 @@ func Test_glob() call assert_fails("call glob('*', 0, {})", 'E728:') endfunc +func Test_glob2() + call mkdir('[XglobDir]', 'R') + call mkdir('abc[glob]def', 'R') + + call writefile(['glob'], '[XglobDir]/Xglob') + call writefile(['glob'], 'abc[glob]def/Xglob') + if has("unix") + call assert_equal([], (glob('[XglobDir]/*', 0, 1))) + call assert_equal([], (glob('abc[glob]def/*', 0, 1))) + call assert_equal(['[XglobDir]/Xglob'], (glob('\[XglobDir]/*', 0, 1))) + call assert_equal(['abc[glob]def/Xglob'], (glob('abc\[glob]def/*', 0, 1))) + elseif has("win32") + let _sl=&shellslash + call assert_equal([], (glob('[XglobDir]\*', 0, 1))) + call assert_equal([], (glob('abc[glob]def\*', 0, 1))) + call assert_equal([], (glob('\[XglobDir]\*', 0, 1))) + call assert_equal([], (glob('abc\[glob]def\*', 0, 1))) + set noshellslash + call assert_equal(['[XglobDir]\Xglob'], (glob('[[]XglobDir]/*', 0, 1))) + call assert_equal(['abc[glob]def\Xglob'], (glob('abc[[]glob]def/*', 0, 1))) + set shellslash + call assert_equal(['[XglobDir]/Xglob'], (glob('[[]XglobDir]/*', 0, 1))) + call assert_equal(['abc[glob]def/Xglob'], (glob('abc[[]glob]def/*', 0, 1))) + let &shellslash=_sl + endif +endfunc + " Test for browse() func Test_browse() CheckFeature browse @@ -4039,6 +4133,8 @@ func Test_slice() call assert_equal('', 'ὰ̳β̳́γ̳̂δ̳̃ε̳̄ζ̳̅'->slice(1, -6)) END call v9.CheckLegacyAndVim9Success(lines) + + call assert_equal(0, slice(v:true, 1)) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_global.vim b/src/testdir/test_global.vim index 34857b2..0f72c3c 100644 --- a/src/testdir/test_global.vim +++ b/src/testdir/test_global.vim @@ -116,7 +116,16 @@ func Test_global_newline() close! endfunc -func Test_wrong_delimiter() +" Test :g with ? as delimiter. +func Test_global_question_delimiter() + new + call setline(1, ['aaaaa', 'b?bbb', 'ccccc', 'ddd?d', 'eeeee']) + g?\??delete + call assert_equal(['aaaaa', 'ccccc', 'eeeee'], getline(1, '$')) + bwipe! +endfunc + +func Test_global_wrong_delimiter() call assert_fails('g x^bxd', 'E146:') endfunc diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim index 2ff8d34..d53750f 100644 --- a/src/testdir/test_gui.vim +++ b/src/testdir/test_gui.vim @@ -899,7 +899,7 @@ func Test_set_term() endfunc func Test_windowid_variable() - if (g:x11_based_gui && empty($WAYLAND_DISPLAY)) || has('win32') + if g:x11_based_gui || has('win32') call assert_true(v:windowid > 0) else call assert_equal(0, v:windowid) diff --git a/src/testdir/test_history.vim b/src/testdir/test_history.vim index 19490f2..b288abc 100644 --- a/src/testdir/test_history.vim +++ b/src/testdir/test_history.vim @@ -96,6 +96,60 @@ function Test_History() call assert_fails('history xyz', 'E488:') call assert_fails('history ,abc', 'E488:') call assert_fails('call histdel(":", "\\%(")', 'E53:') + + " Test for filtering the history list + let hist_filter = execute(':filter /_\d/ :history all')->split('\n') + call assert_equal(20, len(hist_filter)) + let expected = [' # cmd history', + \ ' 2 text_2', + \ ' 3 text_3', + \ '> 4 text_4', + \ ' # search history', + \ ' 2 text_2', + \ ' 3 text_3', + \ '> 4 text_4', + \ ' # expr history', + \ ' 2 text_2', + \ ' 3 text_3', + \ '> 4 text_4', + \ ' # input history', + \ ' 2 text_2', + \ ' 3 text_3', + \ '> 4 text_4', + \ ' # debug history', + \ ' 2 text_2', + \ ' 3 text_3', + \ '> 4 text_4'] + call assert_equal(expected, hist_filter) + + let cmds = {'c': 'cmd', 's': 'search', 'e': 'expr', 'i': 'input', 'd': 'debug'} + for h in sort(keys(cmds)) + " find some items + let hist_filter = execute(':filter /_\d/ :history ' .. h)->split('\n') + call assert_equal(4, len(hist_filter)) + + let expected = [' # ' .. cmds[h] .. ' history', + \ ' 2 text_2', + \ ' 3 text_3', + \ '> 4 text_4'] + call assert_equal(expected, hist_filter) + + " Search for an item that is not there + let hist_filter = execute(':filter /XXXX/ :history ' .. h)->split('\n') + call assert_equal(1, len(hist_filter)) + + let expected = [' # ' .. cmds[h] .. ' history'] + call assert_equal(expected, hist_filter) + + " Invert the filter condition, find non-matches + let hist_filter = execute(':filter! /_3$/ :history ' .. h)->split('\n') + call assert_equal(3, len(hist_filter)) + + let expected = [' # ' .. cmds[h] .. ' history', + \ ' 2 text_2', + \ '> 4 text_4'] + call assert_equal(expected, hist_filter) + endfor endfunction function Test_history_truncates_long_entry() diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index eb89a15..48589ce 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -630,14 +630,14 @@ func Test_pum_with_preview_win() CheckScreendump let lines =<< trim END - funct Omni_test(findstart, base) - if a:findstart - return col(".") - 1 - endif - return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] - endfunc - set omnifunc=Omni_test - set completeopt+=longest + func Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] + endfunc + set omnifunc=Omni_test + set completeopt+=longest END call writefile(lines, 'Xpreviewscript', 'D') @@ -809,6 +809,74 @@ func Test_complete_with_longest() bwipe! endfunc +" Test for buffer-local value of 'completeopt' +func Test_completeopt_buffer_local() + set completeopt=menu + new + call setline(1, ['foofoo', 'foobar', 'foobaz', '']) + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + setlocal bufhidden=hide + enew + call setline(1, ['foofoo', 'foobar', 'foobaz', '']) + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + setlocal completeopt+=fuzzy,noinsert + call assert_equal('menu,fuzzy,noinsert', &l:completeopt) + call assert_equal('menu,fuzzy,noinsert', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>bz\<C-Y>", 'tnix') + call assert_equal('foobaz', getline('.')) + + setlocal completeopt= + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-Y>", 'tnix') + call assert_equal('foofoo', getline('.')) + + setlocal completeopt+=longest + call assert_equal('menu,longest', &l:completeopt) + call assert_equal('menu,longest', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-X>\<C-Z>", 'tnix') + call assert_equal('foo', getline('.')) + + setlocal bufhidden=hide + buffer # + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-Y>", 'tnix') + call assert_equal('foofoo', getline('.')) + + setlocal completeopt+=fuzzy,noinsert + call assert_equal('menu,fuzzy,noinsert', &l:completeopt) + call assert_equal('menu,fuzzy,noinsert', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>bz\<C-Y>", 'tnix') + call assert_equal('foobaz', getline('.')) + + buffer # + call assert_equal('menu,longest', &l:completeopt) + call assert_equal('menu,longest', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-X>\<C-Z>", 'tnix') + call assert_equal('foo', getline('.')) + + setlocal bufhidden=wipe + buffer! # + bwipe! + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + set completeopt& +endfunc " Test for completing words following a completed word in a line func Test_complete_wrapscan() @@ -2451,4 +2519,104 @@ func Test_completefunc_first_call_complete_add() bwipe! endfunc +func Test_complete_fuzzy_match() + func OnPumChange() + let g:item = get(v:event, 'completed_item', {}) + let g:word = get(g:item, 'word', v:null) + endfunction + + augroup AAAAA_Group + au! + autocmd CompleteChanged * :call OnPumChange() + augroup END + + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}] + endfunc + + new + set omnifunc=Omni_test + set completeopt+=noinsert,fuzzy + call feedkeys("Gi\<C-x>\<C-o>", 'tx') + call assert_equal('foo', g:word) + call feedkeys("S\<C-x>\<C-o>fb", 'tx') + call assert_equal('fooBaz', g:word) + call feedkeys("S\<C-x>\<C-o>fa", 'tx') + call assert_equal('foobar', g:word) + " select next + call feedkeys("S\<C-x>\<C-o>fb\<C-n>", 'tx') + call assert_equal('foobar', g:word) + " can cyclically select next + call feedkeys("S\<C-x>\<C-o>fb\<C-n>\<C-n>\<C-n>", 'tx') + call assert_equal(v:null, g:word) + " select prev + call feedkeys("S\<C-x>\<C-o>fb\<C-p>", 'tx') + call assert_equal(v:null, g:word) + " can cyclically select prev + call feedkeys("S\<C-x>\<C-o>fb\<C-p>\<C-p>\<C-p>\<C-p>", 'tx') + call assert_equal('fooBaz', g:word) + + func Comp() + call complete(col('.'), ["fooBaz", "foobar", "foobala"]) + return '' + endfunc + call feedkeys("i\<C-R>=Comp()\<CR>", 'tx') + call assert_equal('fooBaz', g:word) + + " respect noselect + set completeopt+=noselect + call feedkeys("S\<C-x>\<C-o>fb", 'tx') + call assert_equal(v:null, g:word) + call feedkeys("S\<C-x>\<C-o>fb\<C-n>", 'tx') + call assert_equal('fooBaz', g:word) + + " avoid breaking default completion behavior + set completeopt=fuzzy,menu + call setline(1, ['hello help hero h']) + " Use "!" flag of feedkeys() so that ex_normal_busy is not set and + " ins_compl_check_keys() is not skipped. + " Add a "0" after the <Esc> to avoid waiting for an escape sequence. + call feedkeys("A\<C-X>\<C-N>\<Esc>0", 'tx!') + call assert_equal('hello help hero hello', getline('.')) + set completeopt+=noinsert + call setline(1, ['hello help hero h']) + call feedkeys("A\<C-X>\<C-N>\<Esc>0", 'tx!') + call assert_equal('hello help hero h', getline('.')) + + " clean up + set omnifunc= + bw! + set complete& completeopt& + autocmd! AAAAA_Group + augroup! AAAAA_Group + delfunc OnPumChange + delfunc Omni_test + delfunc Comp + unlet g:item + unlet g:word +endfunc + +" Check that tie breaking is stable for completeopt+=fuzzy (which should +" behave the same on different platforms). +func Test_complete_fuzzy_match_tie() + new + set completeopt+=fuzzy,noselect + call setline(1, ['aaabbccc', 'aaabbCCC', 'aaabbcccc', 'aaabbCCCC', '']) + + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbccc', getline('.')) + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbCCC', getline('.')) + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-N>\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbcccc', getline('.')) + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-N>\<C-N>\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbCCCC', getline('.')) + + bwipe! + set completeopt& +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim index 4c69476..12a6dd4 100644 --- a/src/testdir/test_listdict.vim +++ b/src/testdir/test_listdict.vim @@ -60,6 +60,9 @@ func Test_list_slice() assert_equal([1, 2], l[-3 : -1]) END call v9.CheckDefAndScriptSuccess(lines) + + call assert_fails('let l[[]] = 1', 'E730: Using a List as a String') + call assert_fails('let l[1 : []] = [1]', 'E730: Using a List as a String') endfunc " List identity @@ -178,6 +181,19 @@ func Test_list_assign() END call v9.CheckScriptFailure(['vim9script'] + lines, 'E688:') call v9.CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 1') + + let lines =<< trim END + VAR l = [2] + LET l += test_null_list() + call assert_equal([2], l) + LET l = test_null_list() + LET l += [1] + call assert_equal([1], l) + END + call v9.CheckLegacyAndVim9Success(lines) + + let d = {'abc': [1, 2, 3]} + call assert_fails('let d.abc[0:0z10] = [10, 20]', 'E976: Using a Blob as a String') endfunc " test for range assign @@ -447,6 +463,9 @@ func Test_dict_assign() n.key = 3 END call v9.CheckDefFailure(lines, 'E1141:') + + let d = {'abc': {}} + call assert_fails("let d.abc[0z10] = 10", 'E976: Using a Blob as a String') endfunc " Function in script-local List or Dict @@ -1505,6 +1524,8 @@ func Test_indexof() call assert_equal(-1, indexof(test_null_list(), {i, v -> v == 'a'})) call assert_equal(-1, indexof(l, test_null_string())) call assert_equal(-1, indexof(l, test_null_function())) + call assert_equal(-1, indexof(l, "")) + call assert_fails('let i = indexof(l, " ")', 'E15:') " failure cases call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:') diff --git a/src/testdir/test_method.vim b/src/testdir/test_method.vim index 120fade..f18ac14 100644 --- a/src/testdir/test_method.vim +++ b/src/testdir/test_method.vim @@ -136,6 +136,13 @@ func Test_method_syntax() call assert_fails('eval [1, 2, 3] ->sort ()', 'E274:') call assert_fails('eval [1, 2, 3]-> sort ()', 'E274:') call assert_fails('eval [1, 2, 3]-> sort()', 'E274:') + + " Test for using a method name containing a curly brace name + let s = 'len' + call assert_equal(4, "xxxx"->str{s}()) + + " Test for using a method in an interpolated string + call assert_equal('4', $'{"xxxx"->strlen()}') endfunc func Test_method_lambda() diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim index 4b7e5e6..83594d2 100644 --- a/src/testdir/test_normal.vim +++ b/src/testdir/test_normal.vim @@ -402,17 +402,17 @@ func Test_normal08_fold() " First fold norm! V4jzf " check that folds have been created - call assert_equal(['50/*{{{*/', '51', '52', '53', '54/*}}}*/'], getline(50,54)) + call assert_equal(['50/* {{{ */', '51', '52', '53', '54/* }}} */'], getline(50,54)) " Second fold 46 norm! V10jzf " check that folds have been created - call assert_equal('46/*{{{*/', getline(46)) - call assert_equal('60/*}}}*/', getline(60)) + call assert_equal('46/* {{{ */', getline(46)) + call assert_equal('60/* }}} */', getline(60)) norm! k call assert_equal('45', getline('.')) norm! j - call assert_equal('46/*{{{*/', getline('.')) + call assert_equal('46/* {{{ */', getline('.')) norm! j call assert_equal('61', getline('.')) norm! k @@ -421,12 +421,12 @@ func Test_normal08_fold() norm! k call assert_equal('45', getline('.')) norm! j - call assert_equal('46/*{{{*/', getline('.')) + call assert_equal('46/* {{{ */', getline('.')) norm! j call assert_equal('47', getline('.')) norm! k norm! zcVzO - call assert_equal('46/*{{{*/', getline('.')) + call assert_equal('46/* {{{ */', getline('.')) norm! j call assert_equal('47', getline('.')) norm! j @@ -434,7 +434,7 @@ func Test_normal08_fold() norm! j call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('51', getline('.')) " delete folds @@ -1383,14 +1383,14 @@ func Test_normal18_z_fold() " First fold norm! 4zF " check that folds have been created - call assert_equal(['50/*{{{*/', '51', '52', '53/*}}}*/'], getline(50,53)) + call assert_equal(['50/* {{{ */', '51', '52', '53/* }}} */'], getline(50,53)) " Test for zd 51 norm! 2zF call assert_equal(2, foldlevel('.')) norm! kzd - call assert_equal(['50', '51/*{{{*/', '52/*}}}*/', '53'], getline(50,53)) + call assert_equal(['50', '51/* {{{ */', '52/* }}} */', '53'], getline(50,53)) norm! j call assert_equal(1, foldlevel('.')) @@ -1409,7 +1409,7 @@ func Test_normal18_z_fold() norm! 2zF 90 norm! 4zF - call assert_equal(['85/*{{{*/', '86/*{{{*/', '87/*}}}*/', '88/*}}}*/', '89', '90/*{{{*/', '91', '92', '93/*}}}*/'], getline(85,93)) + call assert_equal(['85/* {{{ */', '86/* {{{ */', '87/* }}} */', '88/* }}} */', '89', '90/* {{{ */', '91', '92', '93/* }}} */'], getline(85,93)) norm! zE call assert_equal(['85', '86', '87', '88', '89', '90', '91', '92', '93'], getline(85,93)) @@ -1421,9 +1421,9 @@ func Test_normal18_z_fold() norm! k call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) call assert_equal(0, &foldenable) @@ -1433,7 +1433,7 @@ func Test_normal18_z_fold() norm! zN call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) call assert_equal(1, &foldenable) @@ -1454,9 +1454,9 @@ func Test_normal18_z_fold() norm! k call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) 50 @@ -1464,7 +1464,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1473,14 +1473,14 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) 49 norm! za - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) set nofoldenable @@ -1494,11 +1494,11 @@ func Test_normal18_z_fold() norm! 2k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1510,11 +1510,11 @@ func Test_normal18_z_fold() norm! 2k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1526,7 +1526,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1546,7 +1546,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) set nofoldenable @@ -1555,7 +1555,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1565,7 +1565,7 @@ func Test_normal18_z_fold() norm! zCk call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1576,7 +1576,7 @@ func Test_normal18_z_fold() norm! zx call assert_equal(1, &foldenable) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1588,17 +1588,17 @@ func Test_normal18_z_fold() norm! 3k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) norm! j call assert_equal('53', getline('.')) norm! j - call assert_equal('54/*}}}*/', getline('.')) + call assert_equal('54/* }}} */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1610,15 +1610,15 @@ func Test_normal18_z_fold() call assert_equal(1, &foldenable) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) norm! j call assert_equal('53', getline('.')) norm! j - call assert_equal('54/*}}}*/', getline('.')) + call assert_equal('54/* }}} */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1631,7 +1631,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1648,7 +1648,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1667,7 +1667,7 @@ func Test_normal18_z_fold() call assert_equal(0, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1685,11 +1685,11 @@ func Test_normal18_z_fold() call assert_equal(2, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1705,24 +1705,24 @@ func Test_normal18_z_fold() call assert_equal(2, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) - call append(50, ['a /*{{{*/', 'b /*}}}*/']) + call append(50, ['a /* {{{ */', 'b /* }}} */']) 48 call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('a /*{{{*/', getline('.')) + call assert_equal('a /* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) 48 @@ -1731,15 +1731,15 @@ func Test_normal18_z_fold() call assert_equal(3, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('a /*{{{*/', getline('.')) + call assert_equal('a /* {{{ */', getline('.')) norm! j - call assert_equal('b /*}}}*/', getline('.')) + call assert_equal('b /* }}} */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -4260,4 +4260,22 @@ func Test_page_cursor_topbot() bwipe! endfunc +" Test for Ctrl-D with long line +func Test_halfpage_longline() + 10new + call setline(1, ['long'->repeat(1000), 'short']) + exe "norm! \<C-D>" + call assert_equal(2, line('.')) + bwipe! +endfunc + +" Test for Ctrl-E with long line and very narrow window, +" used to cause an inifite loop +func Test_scroll_longline_no_loop() + 4vnew + setl smoothscroll number showbreak=> scrolloff=2 + call setline(1, repeat(['Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'], 3)) + exe "normal! \<C-E>" + bwipe! +endfunc " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim index 4b054b5..b5a58f6 100644 --- a/src/testdir/test_partial.vim +++ b/src/testdir/test_partial.vim @@ -406,4 +406,18 @@ func Test_compare_partials() call assert_false(F1 is N1) endfunc +func Test_partial_method() + func Foo(x, y, z) + return x + y + z + endfunc + let d = {"Fn": function('Foo', [10, 20])} + call assert_fails('echo 30->d.Fn()', 'E1265: Cannot use a partial here') + delfunc Foo +endfunc + +func Test_non_callable_type_as_method() + let d = {"Fn": 10} + call assert_fails('echo 30->d.Fn()', 'E1085: Not a callable type: d.Fn') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim index f5cb8b2..dd01a57 100644 --- a/src/testdir/test_popup.vim +++ b/src/testdir/test_popup.vim @@ -908,6 +908,13 @@ func Test_popup_command_dump() call term_sendkeys(buf, "\<Esc>") + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>") + call VerifyScreenDump(buf, 'Test_popup_command_rl', {}) + call term_sendkeys(buf, "\<Esc>:set norightleft\<CR>") + endif + " Set a timer to change a menu entry while it's displayed. The text should " not change but the command does. Making the screendump also verifies that " "changed" shows up, which means the timer triggered. @@ -930,6 +937,37 @@ func Test_popup_command_dump() call StopVimInTerminal(buf) endfunc +" Test position of right-click menu when clicking near window edge. +func Test_mouse_popup_position() + CheckFeature menu + CheckScreendump + + let script =<< trim END + set mousemodel=popup_setpos + source $VIMRUNTIME/menu.vim + call setline(1, join(range(20))) + func Trigger(col) + call test_setmouse(1, a:col) + call feedkeys("\<RightMouse>", 't') + endfunc + END + call writefile(script, 'XmousePopupPosition', 'D') + let buf = RunVimInTerminal('-S XmousePopupPosition', #{rows: 20, cols: 50}) + + call term_sendkeys(buf, ":call Trigger(45)\<CR>") + call VerifyScreenDump(buf, 'Test_mouse_popup_position_01', {}) + call term_sendkeys(buf, "\<Esc>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call term_sendkeys(buf, ":call Trigger(50 + 1 - 45)\<CR>") + call VerifyScreenDump(buf, 'Test_mouse_popup_position_02', {}) + call term_sendkeys(buf, "\<Esc>:set norightleft\<CR>") + endif + + call StopVimInTerminal(buf) +endfunc + func Test_popup_complete_backwards() new call setline(1, ['Post', 'Port', 'Po']) @@ -1175,6 +1213,8 @@ func Test_CompleteChanged() set completeopt=menu,menuone call feedkeys("i\<C-X>\<C-O>\<BS>\<BS>\<BS>f", 'tx') call assert_equal('five', g:word) + call feedkeys("i\<C-X>\<C-O>\<BS>\<BS>\<BS>f\<BS>", 'tx') + call assert_equal('one', g:word) autocmd! AAAAA_Group set complete& completeopt& @@ -1339,4 +1379,113 @@ func Test_pum_highlights_custom() call StopVimInTerminal(buf) endfunc +" Test match relate highlight group in pmenu +func Test_pum_highlights_match() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foo', 'kind': 'fookind' }, + \ { 'word': 'foofoo', 'kind': 'fookind' }, + \ { 'word': 'foobar', 'kind': 'fookind' }, + \ { 'word': 'fooBaz', 'kind': 'fookind' }, + \ { 'word': 'foobala', 'kind': 'fookind' }, + \ { 'word': '你好' }, + \ { 'word': '你好吗' }, + \ { 'word': '你不好吗' }, + \ { 'word': '你可好吗' }, + \]} + endfunc + + func Comp() + let col = col('.') + if getline('.') == 'f' + let col -= 1 + endif + call complete(col, [ + \ #{word: "foo", icase: 1}, + \ #{word: "Foobar", icase: 1}, + \ #{word: "fooBaz", icase: 1}, + \]) + return '' + endfunc + + set omnifunc=Omni_test + set completeopt=menu,noinsert,fuzzy + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_03', {}) + call term_sendkeys(buf, "\<Esc>S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "你") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_04', {}) + call term_sendkeys(buf, "吗") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_05', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_06', {}) + call term_sendkeys(buf, "\<Esc>S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "你") + call VerifyScreenDump(buf, 'Test_pum_highlights_06a', {}) + call term_sendkeys(buf, "吗") + call VerifyScreenDump(buf, 'Test_pum_highlights_06b', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call term_sendkeys(buf, ":set norightleft\<CR>") + call TermWait(buf) + endif + + call term_sendkeys(buf, ":set completeopt-=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_07', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_08', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call term_sendkeys(buf, ":set norightleft\<CR>") + endif + + call term_sendkeys(buf, "S\<C-R>=Comp()\<CR>f") + call VerifyScreenDump(buf, 'Test_pum_highlights_09', {}) + call term_sendkeys(buf, "o\<BS>\<C-R>=Comp()\<CR>") + call VerifyScreenDump(buf, 'Test_pum_highlights_09', {}) + + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim index c784a2d..0b61815 100644 --- a/src/testdir/test_quickfix.vim +++ b/src/testdir/test_quickfix.vim @@ -6496,4 +6496,74 @@ func Test_efm_format_b() call setqflist([], 'f') endfunc +func XbufferTests_range(cchar) + call s:setup_commands(a:cchar) + + enew! + let lines =<< trim END + Xtestfile7:700:10:Line 700 + Xtestfile8:800:15:Line 800 + END + silent! call setline(1, lines) + norm! Vy + " Note: We cannot use :Xbuffer here, + " it doesn't properly fail, so we need to + " test using the raw c/l commands. + " (also further down) + if (a:cchar == 'c') + exe "'<,'>cbuffer!" + else + exe "'<,'>lbuffer!" + endif + let l = g:Xgetlist() + call assert_true(len(l) == 1 && + \ l[0].lnum == 700 && l[0].col == 10 && l[0].text ==# 'Line 700') + + enew! + let lines =<< trim END + Xtestfile9:900:55:Line 900 + Xtestfile10:950:66:Line 950 + END + silent! call setline(1, lines) + if (a:cchar == 'c') + 1cgetbuffer + else + 1lgetbuffer + endif + let l = g:Xgetlist() + call assert_true(len(l) == 1 && + \ l[0].lnum == 900 && l[0].col == 55 && l[0].text ==# 'Line 900') + + enew! + let lines =<< trim END + Xtestfile11:700:20:Line 700 + Xtestfile12:750:25:Line 750 + END + silent! call setline(1, lines) + if (a:cchar == 'c') + 1,1caddbuffer + else + 1,1laddbuffer + endif + let l = g:Xgetlist() + call assert_true(len(l) == 2 && + \ l[0].lnum == 900 && l[0].col == 55 && l[0].text ==# 'Line 900' && + \ l[1].lnum == 700 && l[1].col == 20 && l[1].text ==# 'Line 700') + enew! + + " Check for invalid range + " Using Xbuffer will not run the range check in the cbuffer/lbuffer + " commands. So directly call the commands. + if (a:cchar == 'c') + call assert_fails('900,999caddbuffer', 'E16:') + else + call assert_fails('900,999laddbuffer', 'E16:') + endif +endfunc + +func Test_cbuffer_range() + call XbufferTests_range('c') + call XbufferTests_range('l') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_remote.vim b/src/testdir/test_remote.vim index 1475052..fd8b0af 100644 --- a/src/testdir/test_remote.vim +++ b/src/testdir/test_remote.vim @@ -12,7 +12,7 @@ source term_util.vim let s:remote_works = 0 let s:skip = 'Skipped: --remote feature is not possible' -" nees to be run as first test to verify, that vim --servername works +" needs to be run as first test to verify, that vim --servername works func Verify_remote_feature_works() CheckRunVimInTerminal enew diff --git a/src/testdir/test_scroll_opt.vim b/src/testdir/test_scroll_opt.vim index cda9bf0..1b60019 100644 --- a/src/testdir/test_scroll_opt.vim +++ b/src/testdir/test_scroll_opt.vim @@ -961,6 +961,51 @@ func Test_smoothscroll_insert_bottom() call StopVimInTerminal(buf) endfunc +func Test_smoothscroll_in_qf_window() + CheckFeature quickfix + CheckScreendump + + let lines =<< trim END + set nocompatible display=lastline + copen 5 + setlocal number smoothscroll + let g:l = [{'text': 'foo'}] + repeat([{'text': join(range(30))}], 10) + call setqflist(g:l, 'r') + normal! G + wincmd t + let g:l1 = [{'text': join(range(1000))}] + END + call writefile(lines, 'XSmoothScrollInQfWindow', 'D') + let buf = RunVimInTerminal('-u NONE -S XSmoothScrollInQfWindow', #{rows: 20, cols: 60}) + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_1', {}) + + call term_sendkeys(buf, ":call setqflist([], 'r')\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_2', {}) + + call term_sendkeys(buf, ":call setqflist(g:l, 'r')\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_3', {}) + + call term_sendkeys(buf, ":call setqflist(g:l1, 'r')\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_4', {}) + + call term_sendkeys(buf, "\<C-W>b$\<C-W>t") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_5', {}) + + call term_sendkeys(buf, ":call setqflist([], 'r')\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_2', {}) + + call term_sendkeys(buf, ":call setqflist(g:l1, 'r')\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_4', {}) + + call term_sendkeys(buf, "\<C-W>b$\<C-W>t") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_5', {}) + + call term_sendkeys(buf, ":call setqflist(g:l, 'r')\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_in_qf_window_3', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_smoothscroll_in_zero_width_window() set cpo+=n number smoothscroll set winwidth=99999 winminwidth=0 @@ -1108,4 +1153,39 @@ func Test_smoothscroll_long_line_zb() bwipe! endfunc +func Test_smooth_long_scrolloff() + CheckScreendump + + let lines =<< trim END + set smoothscroll scrolloff=3 + call setline(1, ['one', 'two long '->repeat(100), 'three', 'four', 'five', 'six']) + END + call writefile(lines, 'XSmoothLongScrolloff', 'D') + let buf = RunVimInTerminal('-u NONE -S XSmoothLongScrolloff', #{rows: 8, cols: 40}) + "FIXME: empty screen due to reset_skipcol()/curs_columns() shenanigans + call term_sendkeys(buf, ":norm j721|\<CR>") + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_1', {}) + + call term_sendkeys(buf, "gj") + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_2', {}) + + call term_sendkeys(buf, "gj") + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_3', {}) + + call term_sendkeys(buf, "gj") + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_4', {}) + + call term_sendkeys(buf, "gj") + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_5', {}) + + call term_sendkeys(buf, "gj") + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_6', {}) + + call term_sendkeys(buf, "gk") + "FIXME: empty screen due to reset_skipcol()/curs_columns() shenanigans + call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_7', {}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_shell.vim b/src/testdir/test_shell.vim index 7d91dff..2ac5596 100644 --- a/src/testdir/test_shell.vim +++ b/src/testdir/test_shell.vim @@ -158,6 +158,10 @@ func Test_shellescape() call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) call assert_equal("'te!xt'", shellescape("te!xt")) call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) + call assert_equal("'te<cword>xt'", shellescape("te<cword>xt")) + call assert_equal("'te\\<cword>xt'", shellescape("te<cword>xt", 1)) + call assert_equal("'te<cword>%xt'", shellescape("te<cword>%xt")) + call assert_equal("'te\\<cword>\\%xt'", shellescape("te<cword>%xt", 1)) call assert_equal("'te\nxt'", shellescape("te\nxt")) call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1)) diff --git a/src/testdir/test_shortpathname.vim b/src/testdir/test_shortpathname.vim index 5964630..0c41692 100644 --- a/src/testdir/test_shortpathname.vim +++ b/src/testdir/test_shortpathname.vim @@ -16,18 +16,14 @@ func TestIt(file, bits, expected) endif endfunc -func Test_ColonEight() - let save_dir = getcwd() - - " This could change for CygWin to //cygdrive/c . - let dir1 = 'c:/x.x.y' +func s:SetupDir(dir) let trycount = 5 while 1 - if !filereadable(dir1) && !isdirectory(dir1) + if !filereadable(a:dir) && !isdirectory(a:dir) break endif if trycount == 1 - call assert_report("Fatal: '" . dir1 . "' exists, cannot run this test") + call assert_report("Fatal: '" . a:dir . "' exists, cannot run this test") return endif " When tests run in parallel the directory may exist, wait a bit until it @@ -35,6 +31,15 @@ func Test_ColonEight() sleep 5 let trycount -= 1 endwhile +endfunc + + +func Test_ColonEight() + let save_dir = getcwd() + + " This could change for CygWin to //cygdrive/c . + let dir1 = 'c:/x.x.y' + call s:SetupDir(dir1) let file1 = dir1 . '/zz.y.txt' let nofile1 = dir1 . '/z.y.txt' @@ -78,7 +83,8 @@ func Test_ColonEight() endfunc func Test_ColonEight_MultiByte() - let dir = 'Xtest' + let dir = 'c:/Xtest_C8MB' + call s:SetupDir(dir) let file = dir . '/日本語のファイル.txt' diff --git a/src/testdir/test_spellrare.vim b/src/testdir/test_spellrare.vim new file mode 100644 index 0000000..ceb35cb --- /dev/null +++ b/src/testdir/test_spellrare.vim @@ -0,0 +1,61 @@ +" Test spell checking + +source check.vim +CheckFeature spell + +" Test spellbadword() with argument, specifically to move to "rare" words +" in normal mode. +func Test_spellrareword() + set spell + + " Create a small word list to test that spellbadword('...') + " can return ['...', 'rare']. + let lines =<< trim END + foo + foobar/? + foobara/? + END + call writefile(lines, 'Xwords', 'D') + + mkspell! Xwords.spl Xwords + set spelllang=Xwords.spl + call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) + + new + call setline(1, ['foo', '', 'foo bar foo bar foobara foo foo foo foobar', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('foo', expand('<cword>')) + normal ]s + call assert_equal('bar', expand('<cword>')) + + normal ]r + call assert_equal('foobara', expand('<cword>')) + normal ]r + call assert_equal('foobar', expand('<cword>')) + normal ]r + call assert_equal('foobara', expand('<cword>')) + normal 2]r + call assert_equal('foobara', expand('<cword>')) + + normal [r + call assert_equal('foobar', expand('<cword>')) + normal [r + call assert_equal('foobara', expand('<cword>')) + normal [r + call assert_equal('foobar', expand('<cword>')) + normal 2[r + call assert_equal('foobar', expand('<cword>')) + + bwipe! + set nospell + + call delete('Xwords.spl') + set spelllang& + set spell& + + " set 'encoding' to clear the word list + set encoding=utf-8 +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim index 7c2bbb4..afdc104 100644 --- a/src/testdir/test_substitute.vim +++ b/src/testdir/test_substitute.vim @@ -173,6 +173,16 @@ func Test_substitute_repeat() call feedkeys("Qsc\<CR>y", 'tx') bwipe! endfunc + +" Test :s with ? as delimiter. +func Test_substitute_question_delimiter() + new + call setline(1, '??:??') + %s?\?\??!!?g + call assert_equal('!!:!!', getline(1)) + bwipe! +endfunc + " Test %s/\n// which is implemented as a special case to use a " more efficient join rather than doing a regular substitution. func Test_substitute_join() @@ -1498,4 +1508,18 @@ func Test_substitute_expr_recursive() exe bufnr .. "bw!" endfunc +" Test for changing 'cpo' in a substitute expression +func Test_substitute_expr_cpo() + func XSubExpr() + set cpo= + return 'x' + endfunc + + let save_cpo = &cpo + call assert_equal('xxx', substitute('abc', '.', '\=XSubExpr()', 'g')) + call assert_equal(save_cpo, &cpo) + + delfunc XSubExpr +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_tagjump.vim b/src/testdir/test_tagjump.vim index 2abf1f6..432906e 100644 --- a/src/testdir/test_tagjump.vim +++ b/src/testdir/test_tagjump.vim @@ -1560,4 +1560,22 @@ func Test_tagbsearch() set tags& tagbsearch& endfunc +" Test tag guessing with very short names +func Test_tag_guess_short() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "y\tXf\t/^y()/"], + \ 'Xt', 'D') + set tags=Xt cpoptions+=t + call writefile(['', 'int * y () {}', ''], 'Xf', 'D') + + let v:statusmsg = '' + let @/ = '' + ta y + call assert_match('E435:', v:statusmsg) + call assert_equal(2, line('.')) + call assert_match('<y', @/) + + set tags& cpoptions-=t +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_termdebug.vim b/src/testdir/test_termdebug.vim index 68bffd4..a9762df 100644 --- a/src/testdir/test_termdebug.vim +++ b/src/testdir/test_termdebug.vim @@ -230,26 +230,26 @@ endfunc func Test_termdebug_mapping() %bw! - call assert_equal(maparg('K', 'n', 0, 1)->empty(), 1) - call assert_equal(maparg('-', 'n', 0, 1)->empty(), 1) - call assert_equal(maparg('+', 'n', 0, 1)->empty(), 1) + call assert_true(maparg('K', 'n', 0, 1)->empty()) + call assert_true(maparg('-', 'n', 0, 1)->empty()) + call assert_true(maparg('+', 'n', 0, 1)->empty()) Termdebug call WaitForAssert({-> assert_equal(3, winnr('$'))}) wincmd b - call assert_equal(maparg('K', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('-', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('+', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('K', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('-', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('+', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('K', 'n', 0, 1).rhs, ':Evaluate<CR>') + call assert_false(maparg('K', 'n', 0, 1)->empty()) + call assert_false(maparg('-', 'n', 0, 1)->empty()) + call assert_false(maparg('+', 'n', 0, 1)->empty()) + call assert_false(maparg('K', 'n', 0, 1).buffer) + call assert_false(maparg('-', 'n', 0, 1).buffer) + call assert_false(maparg('+', 'n', 0, 1).buffer) + call assert_equal(':Evaluate<CR>', maparg('K', 'n', 0, 1).rhs) wincmd t quit! redraw! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_equal(maparg('K', 'n', 0, 1)->empty(), 1) - call assert_equal(maparg('-', 'n', 0, 1)->empty(), 1) - call assert_equal(maparg('+', 'n', 0, 1)->empty(), 1) + call assert_true(maparg('K', 'n', 0, 1)->empty()) + call assert_true(maparg('-', 'n', 0, 1)->empty()) + call assert_true(maparg('+', 'n', 0, 1)->empty()) %bw! nnoremap K :echom "K"<cr> @@ -258,24 +258,24 @@ func Test_termdebug_mapping() Termdebug call WaitForAssert({-> assert_equal(3, winnr('$'))}) wincmd b - call assert_equal(maparg('K', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('-', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('+', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('K', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('-', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('+', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('K', 'n', 0, 1).rhs, ':Evaluate<CR>') + call assert_false(maparg('K', 'n', 0, 1)->empty()) + call assert_false(maparg('-', 'n', 0, 1)->empty()) + call assert_false(maparg('+', 'n', 0, 1)->empty()) + call assert_false(maparg('K', 'n', 0, 1).buffer) + call assert_false(maparg('-', 'n', 0, 1).buffer) + call assert_false(maparg('+', 'n', 0, 1).buffer) + call assert_equal(':Evaluate<CR>', maparg('K', 'n', 0, 1).rhs) wincmd t quit! redraw! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_equal(maparg('K', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('-', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('+', 'n', 0, 1)->empty(), 0) - call assert_equal(maparg('K', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('-', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('+', 'n', 0, 1).buffer, 0) - call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "K"<cr>') + call assert_false(maparg('K', 'n', 0, 1)->empty()) + call assert_false(maparg('-', 'n', 0, 1)->empty()) + call assert_false(maparg('+', 'n', 0, 1)->empty()) + call assert_false(maparg('K', 'n', 0, 1).buffer) + call assert_false(maparg('-', 'n', 0, 1).buffer) + call assert_false(maparg('+', 'n', 0, 1).buffer) + call assert_equal(':echom "K"<cr>', maparg('K', 'n', 0, 1).rhs) %bw! nnoremap <buffer> K :echom "bK"<cr> @@ -284,20 +284,70 @@ func Test_termdebug_mapping() Termdebug call WaitForAssert({-> assert_equal(3, winnr('$'))}) wincmd b - call assert_equal(maparg('K', 'n', 0, 1).buffer, 1) - call assert_equal(maparg('-', 'n', 0, 1).buffer, 1) - call assert_equal(maparg('+', 'n', 0, 1).buffer, 1) + call assert_true(maparg('K', 'n', 0, 1).buffer) + call assert_true(maparg('-', 'n', 0, 1).buffer) + call assert_true(maparg('+', 'n', 0, 1).buffer) call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "bK"<cr>') wincmd t quit! redraw! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_equal(maparg('K', 'n', 0, 1).buffer, 1) - call assert_equal(maparg('-', 'n', 0, 1).buffer, 1) - call assert_equal(maparg('+', 'n', 0, 1).buffer, 1) - call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "bK"<cr>') + call assert_true(maparg('K', 'n', 0, 1).buffer) + call assert_true(maparg('-', 'n', 0, 1).buffer) + call assert_true(maparg('+', 'n', 0, 1).buffer) + call assert_equal(':echom "bK"<cr>', maparg('K', 'n', 0, 1).rhs) %bw! endfunc +func Test_termdebug_bufnames() + " Test if user has filename/folders named gdb, Termdebug-gdb-console, + " etc. in the current directory + let g:termdebug_config = {} + let g:termdebug_config['use_prompt'] = 1 + let filename = 'gdb' + let replacement_filename = 'Termdebug-gdb-console' + + call writefile(['This', 'is', 'a', 'test'], filename, 'D') + " Throw away the file once the test has done. + Termdebug + " Once termdebug has completed the startup you should have 3 windows on screen + call WaitForAssert({-> assert_equal(3, winnr('$'))}) + " A file named filename already exists in the working directory, + " hence you must call the newly created buffer differently + call WaitForAssert({-> assert_false(bufexists(filename))}) + call WaitForAssert({-> assert_true(bufexists(replacement_filename))}) + quit! + call WaitForAssert({-> assert_equal(1, winnr('$'))}) + + " Check if error message is in :message + let g:termdebug_config['disasm_window'] = 1 + let filename = 'Termdebug-asm-listing' + call writefile(['This', 'is', 'a', 'test'], filename, 'D') + " Check only the head of the error message + let error_message = "You have a file/folder named '" .. filename .. "'" + Termdebug + " Once termdebug has completed the startup you should have 4 windows on screen + call WaitForAssert({-> assert_equal(4, winnr('$'))}) + call WaitForAssert({-> assert_notequal(-1, stridx(execute('messages'), error_message))}) + quit! + wincmd b + wincmd q + call WaitForAssert({-> assert_equal(1, winnr('$'))}) + + unlet g:termdebug_config +endfunc + +function Test_termdebug_save_restore_variables() + let &mousemodel='' + Termdebug + call WaitForAssert({-> assert_equal(3, winnr('$'))}) + call WaitForAssert({-> assert_match(&mousemodel, 'popup_setpos')}) + wincmd t + quit! + call WaitForAssert({-> assert_equal(1, winnr('$'))}) + call WaitForAssert({-> assert_true(empty(&mousemodel))}) +endfunction + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_terminal3.vim b/src/testdir/test_terminal3.vim index 223bcc5..3cca1b0 100644 --- a/src/testdir/test_terminal3.vim +++ b/src/testdir/test_terminal3.vim @@ -10,6 +10,8 @@ source screendump.vim source mouse.vim source term_util.vim +import './vim9.vim' as v9 + let $PROMPT_COMMAND='' func Test_terminal_altscreen() @@ -935,7 +937,14 @@ func Test_terminal_vt420() CheckRunVimInTerminal " For Termcap CheckUnix - let rows=15 + CheckExecutable infocmp + let a = system('infocmp vt420') + if v:shell_error + " reset v:shell_error + let a = system('true') + throw 'Skipped: vt420 terminfo not available' + endif + let rows = 15 call writefile([':set term=vt420'], 'Xterm420', 'D') let buf = RunVimInTerminal('-S Xterm420', #{rows: rows}) @@ -952,4 +961,18 @@ func Test_terminal_vt420() call StopVimInTerminal(buf) endfunc +" Test for using 'vertical' with term_start(). If a following term_start(), +" doesn't have the 'vertical' attribute, then it should be split horizontally. +func Test_terminal_vertical() + let lines =<< trim END + call term_start("NONE", {'vertical': 1}) + call term_start("NONE") + VAR layout = winlayout() + call assert_equal('row', layout[0], string(layout)) + call assert_equal('col', layout[1][0][0], string(layout)) + :%bw! + END + call v9.CheckLegacyAndVim9Success(lines) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_textformat.vim b/src/testdir/test_textformat.vim index 5d58486..e31ea22 100644 --- a/src/testdir/test_textformat.vim +++ b/src/testdir/test_textformat.vim @@ -1304,7 +1304,7 @@ func Test_correct_cursor_position() endfunc " This was crashing Vim -func Test_textwdith_overflow() +func Test_textwidth_overflow() new setl tw=999999999 normal 10ig diff --git a/src/testdir/test_vartabs.vim b/src/testdir/test_vartabs.vim index e15a072..bae00dd 100644 --- a/src/testdir/test_vartabs.vim +++ b/src/testdir/test_vartabs.vim @@ -454,5 +454,64 @@ func Test_vartabstop_latin1() set nocompatible linebreak& list& revins& smarttab& vartabstop& endfunc +" Verify that right-shifting and left-shifting adjust lines to the proper +" tabstops. +func Test_vartabstop_shift_right_left() + new + set expandtab + set shiftwidth=0 + set vartabstop=17,11,7 + exe "norm! aword" + let expect = "word" + call assert_equal(expect, getline(1)) + + " Shift to first tabstop. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift to second tabstop. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift to third tabstop. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift to fourth tabstop, repeating the third shift width. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the third tabstop. + norm! << + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the second tabstop. + norm! << + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the first tabstop. + norm! << + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the left margin. + norm! << + let expect = "word" + call assert_equal(expect, getline(1)) + + " Shift again back to the left margin. + norm! << + let expect = "word" + call assert_equal(expect, getline(1)) + + bwipeout! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim index 4296c37..bb08943 100644 --- a/src/testdir/test_vim9_assign.vim +++ b/src/testdir/test_vim9_assign.vim @@ -650,7 +650,7 @@ def Test_assign_index() var bl = 0z11 bl[1] = g:val END - v9.CheckDefExecAndScriptFailure(lines, 'E1030: Using a String as a Number: "22"') + v9.CheckDefExecAndScriptFailure(lines, ['E1030: Using a String as a Number: "22"', 'E1012: Type mismatch; expected number but got string']) # should not read the next line when generating "a.b" var a = {} @@ -3687,4 +3687,16 @@ def Test_final_var_with_blob_value() v9.CheckScriptSuccess(lines) enddef +" Test for overwriting a script-local function using the s: dictionary +def Test_override_script_local_func() + var lines =<< trim END + vim9script + def MyFunc() + enddef + var d: dict<any> = s: + d.MyFunc = function('min') + END + v9.CheckScriptFailure(lines, 'E705: Variable name conflicts with existing function: MyFunc', 5) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 42d09fe..6b1a50d 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -960,6 +960,8 @@ def Test_execute() assert_equal("\nhello", res) res = execute(["echo 'here'", "echo 'there'"]) assert_equal("\nhere\nthere", res) + res = execute("echo 'hi'\n# foo") + assert_equal("\nhi", res) v9.CheckSourceDefAndScriptFailure(['execute(123)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1222: String or List required for argument 1']) v9.CheckSourceDefFailure(['execute([123])'], 'E1013: Argument 1: type mismatch, expected list<string> but got list<number>') @@ -2195,6 +2197,7 @@ def Test_indexof() indexof(l, (i, v) => v.color == 'blue')->assert_equal(1) indexof(l, (i, v) => v.color == 'blue', {startidx: 1})->assert_equal(1) indexof(l, (i, v) => v.color == 'blue', {startidx: 2})->assert_equal(3) + indexof(l, "")->assert_equal(-1) var b = 0zdeadbeef indexof(b, "v:val == 0xef")->assert_equal(3) @@ -4554,6 +4557,7 @@ enddef def Test_term_getjob() CheckRunVimInTerminal v9.CheckSourceDefAndScriptFailure(['term_getjob(0z10)'], ['E1013: Argument 1: type mismatch, expected string but got blob', 'E1220: String or Number required for argument 1']) + v9.CheckSourceDefAndScriptSuccess(['assert_true(term_getjob(0) == null_job)']) enddef def Test_term_getline() @@ -4783,8 +4787,6 @@ def Test_typename() if has('channel') assert_equal('channel', test_null_channel()->typename()) endif - assert_equal('class<Unknown>', typename(null_class)) - assert_equal('object<Unknown>', typename(null_object)) var l: list<func(list<number>): number> = [function('min')] assert_equal('list<func(list<number>): number>', typename(l)) enddef @@ -5183,10 +5185,26 @@ enddef def Test_getregion() assert_equal(['x'], getregion(getpos('.'), getpos('.'))->map((_, _) => 'x')) - - v9.CheckSourceDefAndScriptFailure(['getregion(10, getpos("."))'], ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1']) - assert_equal([''], getregion(getpos('.'), getpos('.'))) + assert_equal(['x'], getregionpos(getpos('.'), getpos('.'))->map((_, _) => 'x')) + + v9.CheckSourceDefAndScriptFailure( + ['getregion(10, getpos("."))'], + ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1'] + ) + v9.CheckSourceDefAndScriptFailure( + ['getregionpos(10, getpos("."))'], + ['E1013: Argument 1: type mismatch, expected list<any> but got number', 'E1211: List required for argument 1'] + ) + assert_equal( + [''], + getregion(getpos('.'), getpos('.')) + ) + assert_equal( + [[[bufnr('%'), 1, 0, 0], [bufnr('%'), 1, 0, 0]]], + getregionpos(getpos('.'), getpos('.')) + ) v9.CheckSourceDefExecFailure(['getregion(getpos("a"), getpos("."))'], 'E1209:') + v9.CheckSourceDefExecFailure(['getregionpos(getpos("a"), getpos("."))'], 'E1209:') enddef " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim index bd06c6e..e92fcc5 100644 --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -103,6 +103,18 @@ def Test_class_basic() END v9.CheckSourceFailure(lines, "E488: Trailing characters: | var y = 10", 3) + # Comments are allowed after an inline block + lines =<< trim END + vim9script + class Foo + static const bar = { # {{{ + baz: 'qux' + } # }}} + endclass + assert_equal({baz: 'qux'}, Foo.bar) + END + v9.CheckSourceSuccess(lines) + # Try to define a class with the same name as an existing variable lines =<< trim END vim9script @@ -533,6 +545,28 @@ def Test_using_null_class() @_ = null_class.member END v9.CheckDefExecAndScriptFailure(lines, ['E715: Dictionary required', 'E1363: Incomplete type']) + + # Test for using a null class as a value + lines =<< trim END + vim9script + echo empty(null_class) + END + v9.CheckSourceFailure(lines, 'E1405: Class "" cannot be used as a value', 2) + + # Test for using a null class with string() + lines =<< trim END + vim9script + assert_equal('class [unknown]', string(null_class)) + END + v9.CheckSourceSuccess(lines) + + # Test for using a null class with type() and typename() + lines =<< trim END + vim9script + assert_equal(12, type(null_class)) + assert_equal('class<Unknown>', typename(null_class)) + END + v9.CheckSourceSuccess(lines) enddef def Test_class_interface_wrong_end() @@ -10690,4 +10724,24 @@ def Test_class_definition_in_a_function() v9.CheckScriptFailure(lines, 'E1429: Class can only be used in a script', 1) enddef +" Test for using [] with a class and an object +def Test_class_object_index() + var lines =<< trim END + vim9script + class A + endclass + A[10] = 1 + END + v9.CheckScriptFailure(lines, 'E689: Index not allowed after a class: A[10] = 1', 4) + + lines =<< trim END + vim9script + class A + endclass + var a = A.new() + a[10] = 1 + END + v9.CheckScriptFailure(lines, 'E689: Index not allowed after a object: a[10] = 1', 5) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index 7764b37..5734731 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -309,6 +309,9 @@ def Test_expr2() g:vals = [1] endif assert_equal([1], g:vals) + + # string interpolation with || + assert_equal('true', $"{0 || 1}") END v9.CheckDefAndScriptSuccess(lines) @@ -415,6 +418,7 @@ def Test_expr2_fails() v9.CheckDefExecAndScriptFailure(['var x = 3', 'if x', 'endif'], 'E1023:', 2) v9.CheckDefAndScriptFailure(["var x = [] || false"], ['E1012: Type mismatch; expected bool but got list<any>', 'E745:'], 1) + v9.CheckDefAndScriptFailure(["var x = $'{false || []}'"], ['E1012: Type mismatch; expected bool but got list<any>', 'E745:'], 1) var lines =<< trim END vim9script @@ -475,6 +479,9 @@ def Test_expr3() failed = true endif assert_false(failed) + + # string interpolation with && + assert_equal('false', $"{1 && 0}") END v9.CheckDefAndScriptSuccess(lines) enddef @@ -574,6 +581,8 @@ def Test_expr3_fails() echo true && s END v9.CheckDefAndScriptFailure(lines, ['E1012: Type mismatch; expected bool but got string', 'E1135: Using a String as a Bool: "asdf"']) + + v9.CheckDefAndScriptFailure(["var x = $'{true && []}'"], ['E1012: Type mismatch; expected bool but got list<any>', 'E745:'], 1) enddef " global variables to use for tests with the "any" type @@ -2109,6 +2118,12 @@ def Test_expr9_number() Test() END v9.CheckDefAndScriptSuccess(lines) + + lines =<< trim END + vim9script + eval("10\n") + END + v9.CheckSourceScriptFailure(lines, "E488: Trailing characters: \n") enddef def Test_expr9_float() @@ -2152,6 +2167,7 @@ def Test_expr9_blob() v9.CheckDefAndScriptSuccess(lines) v9.CheckDefAndScriptFailure(["var x = 0z123"], 'E973:', 1) + v9.CheckDefAndScriptFailure(["var x = null_blox"], ['E1001:', 'E121:'], 1) enddef def Test_expr9_string() diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index b008929..d07bbfb 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -4633,6 +4633,19 @@ def Run_Test_keytyped_in_nested_function() g:StopVimInTerminal(buf) enddef +" Test for test_override('defcompile') +def Test_test_override_defcompile() + var lines =<< trim END + vim9script + def Foo() + xxx + enddef + END + test_override('defcompile', 1) + v9.CheckScriptFailure(lines, 'E476: Invalid command: xxx') + test_override('defcompile', 0) +enddef + " The following messes up syntax highlight, keep near the end. if has('python3') def Test_python3_command() diff --git a/src/testdir/test_vim9_import.vim b/src/testdir/test_vim9_import.vim index 581925d..fb309cb 100644 --- a/src/testdir/test_vim9_import.vim +++ b/src/testdir/test_vim9_import.vim @@ -3068,7 +3068,10 @@ def Test_vim9_import_symlink() var lines =<< trim END vim9script import autoload 'bar.vim' - g:resultFunc = bar.Func() + def FooFunc(): string + return bar.Func() + enddef + g:resultFunc = FooFunc() g:resultValue = bar.value END writefile(lines, 'Xto/plugin/foo.vim') @@ -3222,4 +3225,109 @@ def Test_autoload_import_dict_func() &rtp = save_rtp enddef +" Test for changing the value of an imported Dict item +def Test_set_imported_dict_item() + var lines =<< trim END + vim9script + export var dict1: dict<bool> = {bflag: false} + export var dict2: dict<dict<bool>> = {x: {bflag: false}} + END + writefile(lines, 'XimportedDict.vim', 'D') + + lines =<< trim END + vim9script + import './XimportedDict.vim' + assert_equal(XimportedDict.dict1.bflag, false) + XimportedDict.dict1.bflag = true + assert_equal(XimportedDict.dict1.bflag, true) + XimportedDict.dict2.x.bflag = true + assert_equal(XimportedDict.dict2.x.bflag, true) + assert_equal('bool', typename(XimportedDict.dict1.bflag)) + assert_equal('bool', typename(XimportedDict.dict2.x.bflag)) + assert_equal('bool', typename(XimportedDict.dict2['x'].bflag)) + assert_equal('bool', typename(XimportedDict.dict2.x['bflag'])) + + assert_equal(XimportedDict.dict1['bflag'], true) + XimportedDict.dict1['bflag'] = false + assert_equal(XimportedDict.dict1.bflag, false) + XimportedDict.dict2['x']['bflag'] = false + assert_equal(XimportedDict.dict2['x'].bflag, false) + END + v9.CheckScriptSuccess(lines) + + lines =<< trim END + vim9script + import './XimportedDict.vim' + XimportedDict.dict2.x.bflag = [] + END + v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected bool but got list<any>', 3) +enddef + +" Test for changing the value of an imported class member +def Test_set_imported_class_member() + var lines =<< trim END + vim9script + export class Config + public static var option = false + endclass + END + writefile(lines, 'XimportedClass.vim', 'D') + + lines =<< trim END + vim9script + import './XimportedClass.vim' as foo + type FooConfig = foo.Config + assert_equal(false, FooConfig.option) + assert_equal(false, foo.Config.option) + foo.Config.option = true + assert_equal(true, foo.Config.option) + assert_equal(true, FooConfig.option) + END + v9.CheckScriptSuccess(lines) +enddef + +" Test for using an imported function from the vimrc file. The function is +" defined in the 'start' directory of a package. +def Test_import_from_vimrc() + mkdir('Ximport/pack/foobar/start/foo/autoload', 'pR') + var lines =<< trim END + vim9script + export def Foo() + writefile(['Foo called'], 'Xoutput.log') + enddef + END + writefile(lines, 'Ximport/pack/foobar/start/foo/autoload/foo.vim') + lines =<< trim END + vim9script + set packpath+=./Ximport + try + import autoload 'foo.vim' + foo.Foo() + catch + writefile(['Failed to import foo.vim'], 'Xoutput.log') + endtry + qall! + END + writefile(lines, 'Xvimrc', 'D') + g:RunVim([], [], '-u Xvimrc') + assert_equal(['Foo called'], readfile('Xoutput.log')) + delete('Xoutput.log') +enddef + +" Test for changing a locked imported variable +def Test_import_locked_var() + var lines =<< trim END + vim9script + export var Foo: number = 10 + lockvar Foo + END + writefile(lines, 'Ximportlockedvar.vim', 'D') + lines =<< trim END + vim9script + import './Ximportlockedvar.vim' as Bar + Bar.Foo = 20 + END + v9.CheckScriptFailure(lines, 'E741: Value is locked: Foo', 3) +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index a3f21be..0b17150 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -2159,6 +2159,18 @@ def Test_echo_cmd() assert_match('^two$', g:Screenline(&lines)) v9.CheckDefFailure(['echo "xxx"# comment'], 'E488:') + + # Test for echoing a script local function name + var lines =<< trim END + vim9script + def ScriptLocalEcho() + enddef + echo ScriptLocalEcho + END + new + setline(1, lines) + assert_match('<SNR>\d\+_ScriptLocalEcho', execute('source')->split("\n")[0]) + bw! enddef def Test_echomsg_cmd() @@ -2510,8 +2522,20 @@ def Test_for_loop() reslist->add('x') endfor assert_equal(['x', 'x', 'x'], reslist) + + # Test for trying to use the loop variable "_" inside the loop + for _ in "a" + assert_fails('echo _', 'E1181: Cannot use an underscore here') + endfor END v9.CheckDefAndScriptSuccess(lines) + + lines =<< trim END + for i : number : [1, 2] + echo i + endfor + END + v9.CheckSourceDefAndScriptFailure(lines, 'E1059: No white space allowed before colon: : [1, 2]', 1) enddef def Test_for_loop_list_of_lists() @@ -4551,11 +4575,11 @@ def Run_Test_debug_with_lambda() Func() END writefile(lines, 'XdebugFunc', 'D') - var buf = g:RunVimInTerminal('-S XdebugFunc', {rows: 6, wait_for_ruler: 0}) - g:WaitForAssert(() => assert_match('^>', term_getline(buf, 6))) + var buf = g:RunVimInTerminal('-S XdebugFunc', {rows: 10, wait_for_ruler: 0}) + g:WaitForAssert(() => assert_match('^>', term_getline(buf, 10))) term_sendkeys(buf, "cont\<CR>") - g:WaitForAssert(() => assert_match('\[0\]', term_getline(buf, 5))) + g:WaitForAssert(() => assert_match('\[0\]', term_getline(buf, 9))) g:StopVimInTerminal(buf) enddef @@ -4586,12 +4610,12 @@ def Run_Test_debug_running_out_of_lines() Crash() END writefile(lines, 'XdebugFunc', 'D') - var buf = g:RunVimInTerminal('-S XdebugFunc', {rows: 6, wait_for_ruler: 0}) - g:WaitForAssert(() => assert_match('^>', term_getline(buf, 6))) + var buf = g:RunVimInTerminal('-S XdebugFunc', {rows: 10, wait_for_ruler: 0}) + g:WaitForAssert(() => assert_match('^>', term_getline(buf, 10))) term_sendkeys(buf, "next\<CR>") g:TermWait(buf) - g:WaitForAssert(() => assert_match('^>', term_getline(buf, 6))) + g:WaitForAssert(() => assert_match('^>', term_getline(buf, 10))) term_sendkeys(buf, "cont\<CR>") g:TermWait(buf) @@ -5002,7 +5026,7 @@ def Test_invalid_type_in_for() enddef defcompile END - v9.CheckSourceFailure(lines, 'E1010: Type not recognized: x in range(10)', 1) + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: x', 1) enddef " Test for using a line break between the variable name and the type in a for @@ -5055,6 +5079,56 @@ def Test_eval_lambda_block() v9.CheckSourceSuccess(lines) enddef +" Test for using various null values +def Test_null_values() + var lines =<< trim END + var nullValues = [ + [null, 1, 'null', 7, 'special'], + [null_blob, 1, '0z', 10, 'blob'], + [null_channel, 1, 'channel fail', 9, 'channel'], + [null_dict, 1, '{}', 4, 'dict<any>'], + [null_function, 1, "function('')", 2, 'func(...): unknown'], + [null_job, 1, 'no process', 8, 'job'], + [null_list, 1, '[]', 3, 'list<any>'], + [null_object, 1, 'object of [unknown]', 13, 'object<Unknown>'], + [null_partial, 1, "function('')", 2, 'func(...): unknown'], + [null_string, 1, "''", 1, 'string'] + ] + + for [Val, emptyExp, stringExp, typeExp, typenameExp] in nullValues + assert_equal(emptyExp, empty(Val)) + assert_equal(stringExp, string(Val)) + assert_equal(typeExp, type(Val)) + assert_equal(typenameExp, typename(Val)) + assert_equal(Val, copy(Val)) + assert_equal(-1, test_refcount(Val)) + endfor + END + v9.CheckSourceDefAndScriptSuccess(lines) +enddef + +" Test for using an unknown type in a typecast +def Test_unknown_type_in_typecast() + var lines =<< trim END + vim9script + var a = <MyType>b + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: MyType', 2) + + lines =<< trim END + vim9script + var Fn = <funcx(number, number): number>b + END + v9.CheckSourceFailure(lines, 'E1010: Type not recognized: funcx(number, number): number', 2) + + # Wrong type in a type cast + lines =<< trim END + vim9script + var i: number = <number>true + END + v9.CheckSourceFailure(lines, 'E1012: Type mismatch; expected number but got bool', 2) +enddef + " Keep this last, it messes up highlighting. def Test_substitute_cmd() new diff --git a/src/testdir/test_vim9_typealias.vim b/src/testdir/test_vim9_typealias.vim index cf540c2..2792d45 100644 --- a/src/testdir/test_vim9_typealias.vim +++ b/src/testdir/test_vim9_typealias.vim @@ -363,7 +363,7 @@ def Test_typealias_import() var myNum: A.SomeType = 10 END - v9.CheckScriptFailure(lines, 'E1010: Type not recognized: A.SomeType = 10', 4) + v9.CheckScriptFailure(lines, 'E1010: Type not recognized: A.SomeType', 4) # Use a type alias that is not exported lines =<< trim END diff --git a/src/testdir/test_viminfo.vim b/src/testdir/test_viminfo.vim index 1f4a72d..7aab271 100644 --- a/src/testdir/test_viminfo.vim +++ b/src/testdir/test_viminfo.vim @@ -1299,4 +1299,34 @@ func Test_viminfo_merge_old_jumplist() bw! endfunc +func Test_viminfo_oldfiles_filter() + let v:oldfiles = [] + let _viminfofile = &viminfofile + let &viminfofile='' + let lines = [ + \ '# comment line', + \ '*encoding=utf-8', + \ "> /tmp/vimrc_one.vim", + \ "\t\"\t11\t0", + \ "", + \ "> /tmp/foobar.txt", + \ "\t\"\t11\t0", + \ "", + \ ] + call writefile(lines, 'Xviminfo1', 'D') + rviminfo! Xviminfo1 + new + " filter returns a single item + let a = execute('filter /vim/ oldfiles')->split('\n') + call assert_equal(1, len(a)) + " filter returns more than a single match + let a = execute('filter #tmp# oldfiles')->split('\n') + call assert_equal(2, len(a)) + " don't get prompted for the file, but directly open it + filter /vim/ browse oldfiles + call assert_equal("/tmp/vimrc_one.vim", expand("%")) + bw + let &viminfofile = _viminfofile +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vimscript.vim b/src/testdir/test_vimscript.vim index 0c4aedb..8976779 100644 --- a/src/testdir/test_vimscript.vim +++ b/src/testdir/test_vimscript.vim @@ -7509,6 +7509,14 @@ func Test_for_over_string() let res ..= c .. '-' endfor call assert_equal('', res) + + " Test for using "_" as the loop variable + let i = 0 + let s = 'abc' + for _ in s + call assert_equal(s[i], _) + let i += 1 + endfor endfunc " Test for deeply nested :source command {{{1 diff --git a/src/testdir/test_virtualedit.vim b/src/testdir/test_virtualedit.vim index 48d4aa0..3d3fdd0 100644 --- a/src/testdir/test_virtualedit.vim +++ b/src/testdir/test_virtualedit.vim @@ -709,5 +709,27 @@ func Test_virtualedit_replace_after_tab() bwipe! endfunc +" Test that setpos('.') and cursor() behave the same for v:maxcol +func Test_virtualedit_set_cursor_pos_maxcol() + new + set virtualedit=all + + call setline(1, 'foobar') + exe "normal! V\<Esc>" + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 1, v:maxcol, 0], getpos("'>")) + let pos = getpos("'>") + + call cursor(1, 1) + call setpos('.', pos) + call assert_equal([0, 1, 7, 0], getpos('.')) + + call cursor(1, 1) + call cursor(pos[1:]) + call assert_equal([0, 1, 7, 0], getpos('.')) + + set virtualedit& + bwipe! +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_visual.vim b/src/testdir/test_visual.vim index adcc3e9..3750ebf 100644 --- a/src/testdir/test_visual.vim +++ b/src/testdir/test_visual.vim @@ -1631,6 +1631,22 @@ func Test_visual_substitute_visual() bwipe! endfunc +func Test_virtualedit_exclusive_selection() + new + set virtualedit=all selection=exclusive + + call setline(1, "a\tb") + normal! 0v8ly + call assert_equal("a\t", getreg('"')) + normal! 0v6ly + call assert_equal('a ', getreg('"')) + normal! 06lv2ly + call assert_equal(' ', getreg('"')) + + set virtualedit& selection& + bwipe! +endfunc + func Test_visual_getregion() let lines =<< trim END new @@ -1640,18 +1656,52 @@ func Test_visual_getregion() #" Visual mode call cursor(1, 1) call feedkeys("\<ESC>vjl", 'tx') + call assert_equal(['one', 'tw'], \ 'v'->getpos()->getregion(getpos('.'))) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]] + \ ], + \ 'v'->getpos()->getregionpos(getpos('.'))) + call assert_equal(['one', 'tw'], \ '.'->getpos()->getregion(getpos('v'))) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]] + \ ], + \ '.'->getpos()->getregionpos(getpos('v'))) + call assert_equal(['o'], \ 'v'->getpos()->getregion(getpos('v'))) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 1, 0]], + \ ], + \ 'v'->getpos()->getregionpos(getpos('v'))) + call assert_equal(['w'], \ '.'->getpos()->getregion(getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 2, 0]], + \ ], + \ '.'->getpos()->getregionpos(getpos('.'), {'type': 'v' })) + call assert_equal(['one', 'two'], \ getpos('.')->getregion(getpos('v'), {'type': 'V' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ ], + \ getpos('.')->getregionpos(getpos('v'), {'type': 'V' })) + call assert_equal(['on', 'tw'], \ getpos('.')->getregion(getpos('v'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 0]], + \ ], + \ getpos('.')->getregionpos(getpos('v'), {'type': "\<C-v>" })) #" Line visual mode call cursor(1, 1) @@ -1698,6 +1748,9 @@ func Test_visual_getregion() \ "'a"->getpos()->getregion(getpos("'a"), {'type': 'V' })) call assert_equal(['one', 'two'], \ "."->getpos()->getregion(getpos("'a"), {'type': "\<c-v>" })) + call feedkeys("\<ESC>jVj\<ESC>", 'tx') + call assert_equal(['two', 'three'], getregion(getpos("'<"), getpos("'>"))) + call assert_equal(['two', 'three'], getregion(getpos("'>"), getpos("'<"))) #" Using List call cursor(1, 1) @@ -1717,24 +1770,185 @@ func Test_visual_getregion() call feedkeys("\<ESC>Vjj", 'tx') call assert_equal(['one', 'two', 'three'], \ getregion(getpos('v'), getpos('.'), {'type': 'V' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'V' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 4, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 6, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'V', 'eol': v:true })) #" Multiline with block visual mode call cursor(1, 1) call feedkeys("\<ESC>\<C-v>jj", 'tx') call assert_equal(['o', 't', 't'], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 1, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 1, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 1, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) call cursor(1, 1) call feedkeys("\<ESC>\<C-v>jj$", 'tx') call assert_equal(['one', 'two', 'three'], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", 'eol': v:true })) #" 'virtualedit' set virtualedit=all + call cursor(1, 1) call feedkeys("\<ESC>\<C-v>10ljj$", 'tx') call assert_equal(['one ', 'two ', 'three '], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 3]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 4, 3]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 6, 1]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", 'eol': v:true })) + + call cursor(3, 5) + call feedkeys("\<ESC>\<C-v>hkk", 'tx') + call assert_equal([' ', ' ', 'ee'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 0, 0], [bufnr('%'), 1, 0, 0]], + \ [[bufnr('%'), 2, 0, 0], [bufnr('%'), 2, 0, 0]], + \ [[bufnr('%'), 3, 4, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 4, 0], [bufnr('%'), 1, 4, 2]], + \ [[bufnr('%'), 2, 4, 0], [bufnr('%'), 2, 4, 2]], + \ [[bufnr('%'), 3, 4, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", 'eol': v:true })) + + call cursor(3, 5) + call feedkeys("\<ESC>\<C-v>kk", 'tx') + call assert_equal([' ', ' ', 'e'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 0, 0], [bufnr('%'), 1, 0, 0]], + \ [[bufnr('%'), 2, 0, 0], [bufnr('%'), 2, 0, 0]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 4, 1], [bufnr('%'), 1, 4, 2]], + \ [[bufnr('%'), 2, 4, 1], [bufnr('%'), 2, 4, 2]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", 'eol': v:true })) + + call cursor(1, 3) + call feedkeys("\<ESC>vjj4l", 'tx') + call assert_equal(['e', 'two', 'three '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 3, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 3, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 4, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 6, 2]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'v', 'eol': v:true })) + + call cursor(1, 3) + call feedkeys("\<ESC>lvjj3l", 'tx') + call assert_equal(['', 'two', 'three '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 0, 0], [bufnr('%'), 1, 0, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 4, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 4, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 6, 2]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'v', 'eol': v:true })) + + call cursor(3, 5) + call feedkeys("\<ESC>v3l", 'tx') + call assert_equal(['e '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 6, 3]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'v', 'eol': v:true })) + + call cursor(3, 5) + call feedkeys("\<ESC>lv3l", 'tx') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 3, 6, 0], [bufnr('%'), 3, 6, 4]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'v', 'eol': v:true })) + + call cursor(3, 5) + call feedkeys("\<ESC>3lv3l", 'tx') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 3, 6, 2], [bufnr('%'), 3, 6, 6]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'v', 'eol': v:true })) + set virtualedit& #" using wrong types for positions @@ -1743,9 +1957,21 @@ func Test_visual_getregion() call assert_fails("call getregion(1, 2)", 'E1211:') call assert_fails("call getregion(getpos('.'), {})", 'E1211:') call assert_fails(':echo "."->getpos()->getregion("$", [])', 'E1211:') + call assert_fails("call getregionpos(1, 2)", 'E1211:') + call assert_fails("call getregionpos(getpos('.'), {})", 'E1211:') + call assert_fails(':echo "."->getpos()->getregionpos("$", [])', 'E1211:') #" using invalid value for "type" call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': 'v0' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': 'v0' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': 'V0' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': 'V0' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '\<C-v>0' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '\<C-v>0' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '\<C-v>1:' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '\<C-v>1:' })", 'E475:') #" using a mark from another buffer to current buffer new @@ -1756,13 +1982,20 @@ func Test_visual_getregion() call assert_equal([g:buf, 10, 1, 0], getpos("'A")) call assert_equal([], getregion(getpos('.'), getpos("'A"), {'type': 'v' })) call assert_equal([], getregion(getpos("'A"), getpos('.'), {'type': 'v' })) + call assert_equal([], getregionpos(getpos('.'), getpos("'A"), {'type': 'v' })) + call assert_equal([], getregionpos(getpos("'A"), getpos('.'), {'type': 'v' })) #" using two marks from another buffer wincmd p normal! GmB wincmd p call assert_equal([g:buf, 10, 1, 0], getpos("'B")) - call assert_equal(['9'], getregion(getpos("'B"), getpos("'A"), {'type': 'v' })) + call assert_equal(['9'], + \ getregion(getpos("'B"), getpos("'A"), {'type': 'v' })) + call assert_equal([ + \ [[g:buf, 10, 1, 0], [g:buf, 10, 1, 0]], + \ ], + \ getregionpos(getpos("'B"), getpos("'A"), {'type': 'v' })) #" using two positions from another buffer for type in ['v', 'V', "\<C-V>"] @@ -1773,6 +2006,12 @@ func Test_visual_getregion() call assert_equal(range(10)->mapnew('string(v:val)'), \ getregion([g:buf, 10, 2, 0], [g:buf, 1, 1, 0], \ {'type': type, 'exclusive': exclusive })) + call assert_equal(range(1, 10)->mapnew('repeat([[g:buf, v:val, 1, 0]], 2)'), + \ getregionpos([g:buf, 1, 1, 0], [g:buf, 10, 2, 0], + \ {'type': type, 'exclusive': exclusive })) + call assert_equal(range(1, 10)->mapnew('repeat([[g:buf, v:val, 1, 0]], 2)'), + \ getregionpos([g:buf, 10, 2, 0], [g:buf, 1, 1, 0], + \ {'type': type, 'exclusive': exclusive })) endfor endfor @@ -1785,17 +2024,18 @@ func Test_visual_getregion() call assert_fails('call getregion([g:buf, 10, 0, 0], [g:buf, 1, 1, 0])', 'E964:') call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 10, 3, 0])', 'E964:') call assert_fails('call getregion([g:buf, 10, 3, 0], [g:buf, 1, 1, 0])', 'E964:') + call assert_fails('call getregion([g:buf, 1, 0, 0], [g:buf, 1, 1, 0])', 'E964:') + call assert_fails('call getregion([g:buf, 1, 1, 0], [g:buf, 1, 0, 0])', 'E964:') #" using invalid buffer call assert_fails('call getregion([10000, 10, 1, 0], [10000, 10, 1, 0])', 'E681:') exe $':{g:buf}bwipe!' unlet g:buf + bwipe! END call v9.CheckLegacyAndVim9Success(lines) - bwipe! - let lines =<< trim END #" Selection in starts or ends in the middle of a multibyte character new @@ -1804,35 +2044,119 @@ func Test_visual_getregion() \ "\U0001f1e6\u00ab\U0001f1e7\u00ab\U0001f1e8\u00ab\U0001f1e9", \ "1234567890" \ ]) + call cursor(1, 3) call feedkeys("\<Esc>\<C-v>ljj", 'xt') call assert_equal(['cd', "\u00ab ", '34'], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 3, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 5, 0], [bufnr('%'), 2, 7, 1]], + \ [[bufnr('%'), 3, 3, 0], [bufnr('%'), 3, 4, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call cursor(1, 4) call feedkeys("\<Esc>\<C-v>ljj", 'xt') call assert_equal(['de', "\U0001f1e7", '45'], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 4, 0], [bufnr('%'), 1, 5, 0]], + \ [[bufnr('%'), 2, 7, 0], [bufnr('%'), 2, 10, 0]], + \ [[bufnr('%'), 3, 4, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call cursor(1, 5) call feedkeys("\<Esc>\<C-v>jj", 'xt') call assert_equal(['e', ' ', '5'], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 5, 0]], + \ [[bufnr('%'), 2, 7, 1], [bufnr('%'), 2, 7, 2]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal(['efghijk«', '🇦«🇧«🇨«🇩', '12345'], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 13, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 22, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 5, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 5) + call feedkeys("\<Esc>\<C-v>5l2j", 'xt') + call assert_equal(['efghij', ' «🇨« ', '567890'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 10, 0]], + \ [[bufnr('%'), 2, 7, 1], [bufnr('%'), 2, 19, 1]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 10, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + + call cursor(1, 4) + call feedkeys("\<Esc>\<C-v>02j", 'xt') + call assert_equal(['abcd', '🇦« ', '1234'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 7, 1]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 4, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + + #" characterwise selection with multibyte chars call cursor(1, 1) call feedkeys("\<Esc>vj", 'xt') call assert_equal(['abcdefghijk«', "\U0001f1e6"], \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 13, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 4, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + set selection=exclusive + call feedkeys('l', 'xt') + call assert_equal(['abcdefghijk«', "\U0001f1e6"], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 13, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 4, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) #" marks on multibyte chars - :set selection=exclusive call setpos("'a", [0, 1, 11, 0]) call setpos("'b", [0, 2, 16, 0]) call setpos("'c", [0, 2, 0, 0]) call cursor(1, 1) + call assert_equal(['ghijk', '🇨«🇩'], - \ getregion(getpos("'a"), getpos("'b"), {'type': "\<c-v>" })) + \ getregion(getpos("'a"), getpos("'b"), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 7, 0], [bufnr('%'), 1, 11, 0]], + \ [[bufnr('%'), 2, 13, 0], [bufnr('%'), 2, 22, 0]], + \ ], + \ getregionpos(getpos("'a"), getpos("'b"), {'type': "\<C-v>" })) + call assert_equal(['k«', '🇦«🇧«🇨'], \ getregion(getpos("'a"), getpos("'b"), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 11, 0], [bufnr('%'), 1, 13, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 16, 0]], + \ ], + \ getregionpos(getpos("'a"), getpos("'b"), {'type': 'v' })) + call assert_equal(['k«'], \ getregion(getpos("'a"), getpos("'c"), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 11, 0], [bufnr('%'), 1, 13, 0]], + \ ], + \ getregionpos(getpos("'a"), getpos("'c"), {'type': 'v' })) #" use inclusive selection, although 'selection' is exclusive call setpos("'a", [0, 1, 11, 0]) @@ -1855,12 +2179,12 @@ func Test_visual_getregion() call assert_equal(['abcdefghijk«'], \ getregion(getpos("'a"), getpos("'b"), \ {'type': 'V', 'exclusive': 1 })) - :set selection& + + set selection& + bwipe! END call v9.CheckLegacyAndVim9Success(lines) - bwipe! - let lines =<< trim END #" Exclusive selection new @@ -1891,51 +2215,346 @@ func Test_visual_getregion() call assert_equal(["c", "x\tz"], \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) set selection& + bwipe! #" Exclusive selection 2 new call setline(1, ["a\tc", "x\tz", '', '']) + call cursor(1, 1) call feedkeys("\<Esc>v2l", 'xt') call assert_equal(["a\t"], \ getregion(getpos('v'), getpos('.'), {'exclusive': v:true })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'exclusive': v:true })) + call cursor(1, 1) call feedkeys("\<Esc>v$G", 'xt') call assert_equal(["a\tc", "x\tz", ''], \ getregion(getpos('v'), getpos('.'), {'exclusive': v:true })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'exclusive': v:true })) + call cursor(1, 1) call feedkeys("\<Esc>v$j", 'xt') call assert_equal(["a\tc", "x\tz"], \ getregion(getpos('v'), getpos('.'), {'exclusive': v:true })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'exclusive': v:true })) + call cursor(1, 1) call feedkeys("\<Esc>\<C-v>$j", 'xt') call assert_equal(["a\tc", "x\tz"], \ getregion(getpos('v'), getpos('.'), \ {'exclusive': v:true, 'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'exclusive': v:true, 'type': "\<C-v>" })) + call cursor(1, 1) call feedkeys("\<Esc>\<C-v>$G", 'xt') call assert_equal(["a", "x", '', ''], \ getregion(getpos('v'), getpos('.'), \ {'exclusive': v:true, 'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 1, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 1, 0]], + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ [[bufnr('%'), 4, 0, 0], [bufnr('%'), 4, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'exclusive': v:true, 'type': "\<C-v>" })) + call cursor(1, 1) call feedkeys("\<Esc>wv2j", 'xt') call assert_equal(["c", "x\tz"], \ getregion(getpos('v'), getpos('.'), {'exclusive': v:true })) + call assert_equal([ + \ [[bufnr('%'), 1, 3, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'exclusive': v:true })) - #" virtualedit + #" 'virtualedit' with exclusive selection set selection=exclusive set virtualedit=all + + call cursor(1, 1) + call feedkeys("\<Esc>vj", 'xt') + call assert_equal(["a\tc"], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call cursor(1, 1) - call feedkeys("\<Esc>2lv2lj", 'xt') + call feedkeys("\<Esc>v8l", 'xt') + call assert_equal(["a\t"], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>v6l", 'xt') + call assert_equal(['a '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 5]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>6lv2l", 'xt') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 5], [bufnr('%'), 1, 2, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>lv2l", 'xt') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 2, 2]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>2lv2l", 'xt') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 2, 3]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call feedkeys('j', 'xt') call assert_equal([' c', 'x '], \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 3]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>6l\<C-v>2lj", 'xt') + call assert_equal([' ', ' '], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 5], [bufnr('%'), 1, 2, 7]], + \ [[bufnr('%'), 2, 2, 5], [bufnr('%'), 2, 2, 7]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + + call cursor(1, 1) + call feedkeys("\<Esc>l\<C-v>2l2j", 'xt') + call assert_equal([' ', ' ', ' '], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 2, 2]], + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 2, 2]], + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 2, 2]], + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 2, 2]], + \ [[bufnr('%'), 3, 1, 1], [bufnr('%'), 3, 1, 3]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", "eol": v:true })) + call cursor(1, 1) call feedkeys("\<Esc>2l\<C-v>2l2j", 'xt') call assert_equal([' ', ' ', ' '], \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) - set virtualedit& + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 2, 3]], + \ [[bufnr('%'), 2, 2, 1], [bufnr('%'), 2, 2, 3]], + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 2, 3]], + \ [[bufnr('%'), 2, 2, 1], [bufnr('%'), 2, 2, 3]], + \ [[bufnr('%'), 3, 1, 2], [bufnr('%'), 3, 1, 4]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", "eol": v:true })) + + #" 'virtualedit' with inclusive selection set selection& + call cursor(1, 1) + call feedkeys("\<Esc>vj", 'xt') + call assert_equal(["a\tc", 'x'], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 1, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>v8l", 'xt') + call assert_equal(["a\tc"], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>v6l", 'xt') + call assert_equal(['a '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 2, 6]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>6lv2l", 'xt') + call assert_equal([' c'], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 5], [bufnr('%'), 1, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>lv2l", 'xt') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 2, 3]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>2lv2l", 'xt') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 2, 4]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call feedkeys('j', 'xt') + call assert_equal([' c', 'x '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 2, 4]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + + call cursor(1, 1) + call feedkeys("\<Esc>6l\<C-v>2lj", 'xt') + call assert_equal([' c', ' z'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 5], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 2, 5], [bufnr('%'), 2, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + + call cursor(1, 1) + call feedkeys("\<Esc>l\<C-v>2l2j", 'xt') + call assert_equal([' ', ' ', ' '], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 2, 3]], + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 2, 3]], + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 2, 3]], + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 2, 3]], + \ [[bufnr('%'), 3, 1, 1], [bufnr('%'), 3, 1, 4]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", "eol": v:true })) + + call cursor(1, 1) + call feedkeys("\<Esc>2l\<C-v>2l2j", 'xt') + call assert_equal([' ', ' ', ' '], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 2, 4]], + \ [[bufnr('%'), 2, 2, 1], [bufnr('%'), 2, 2, 4]], + \ [[bufnr('%'), 3, 0, 0], [bufnr('%'), 3, 0, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 2, 1], [bufnr('%'), 1, 2, 4]], + \ [[bufnr('%'), 2, 2, 1], [bufnr('%'), 2, 2, 4]], + \ [[bufnr('%'), 3, 1, 2], [bufnr('%'), 3, 1, 5]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", "eol": v:true })) + + set virtualedit& + bwipe! + END + call v9.CheckLegacyAndVim9Success(lines) + + let lines =<< trim END + #" 'virtualedit' with TABs at end of line + new + set virtualedit=all + call setline(1, ["\t", "a\t", "aa\t"]) + + call feedkeys("gg06l\<C-v>3l2j", 'xt') + call assert_equal([' ', ' ', ' '], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 6], [bufnr('%'), 1, 1, 0]], + \ [[bufnr('%'), 2, 2, 5], [bufnr('%'), 2, 2, 0]], + \ [[bufnr('%'), 3, 3, 4], [bufnr('%'), 3, 3, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 6], [bufnr('%'), 1, 2, 2]], + \ [[bufnr('%'), 2, 2, 5], [bufnr('%'), 2, 3, 2]], + \ [[bufnr('%'), 3, 3, 4], [bufnr('%'), 3, 4, 2]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': "\<C-v>", "eol": v:true })) + + call feedkeys("gg06lv3l", 'xt') + call assert_equal([' '], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 6], [bufnr('%'), 1, 1, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 6], [bufnr('%'), 1, 2, 2]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), + \ {'type': 'v', "eol": v:true })) + + set virtualedit& bwipe! END call v9.CheckLegacyAndVim9Success(lines) @@ -1955,4 +2574,144 @@ func Test_getregion_invalid_buf() bwipe! endfunc +func Test_getregion_after_yank() + func! Check_Results(type) + call assert_equal(g:expected_region, + \ getregion(getpos("'["), getpos("']"), #{ type: a:type })) + call assert_equal(g:expected_regionpos, + \ getregionpos(getpos("'["), getpos("']"), #{ type: a:type })) + call assert_equal(g:expected_region, + \ getregion(getpos("']"), getpos("'["), #{ type: a:type })) + call assert_equal(g:expected_regionpos, + \ getregionpos(getpos("']"), getpos("'["), #{ type: a:type })) + let g:checked = 1 + endfunc + + autocmd TextYankPost * + \ : if v:event.operator ==? 'y' + \ | call Check_Results(v:event.regtype) + \ | endif + + new + call setline(1, ['abcd', 'efghijk', 'lmn']) + + let g:expected_region = ['abcd'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ ] + let g:checked = 0 + normal yy + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + + let g:expected_region = ['cd', 'ghijk', 'n'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 3, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 3, 0], [bufnr('%'), 2, 7, 0]], + \ [[bufnr('%'), 3, 3, 0], [bufnr('%'), 3, 3, 0]], + \ ] + let g:checked = 0 + call feedkeys("gg0ll\<C-V>jj$y", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['bc', 'fg', 'mn'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 2, 0], [bufnr('%'), 3, 3, 0]], + \ ] + let g:checked = 0 + call feedkeys("gg0l\<C-V>jjly", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + bwipe! + + new + let lines = ['asdfghjkl', '«口=口»', 'qwertyuiop', '口口=口口', 'zxcvbnm'] + call setline(1, lines) + + let g:expected_region = lines + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 9, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 11, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 10, 0]], + \ [[bufnr('%'), 4, 1, 0], [bufnr('%'), 4, 13, 0]], + \ [[bufnr('%'), 5, 1, 0], [bufnr('%'), 5, 7, 0]], + \ ] + let g:checked = 0 + call feedkeys('ggyG', 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['=口»', 'qwertyuiop', '口口=口'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 2, 6, 0], [bufnr('%'), 2, 11, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 10, 0]], + \ [[bufnr('%'), 4, 1, 0], [bufnr('%'), 4, 10, 0]], + \ ] + let g:checked = 0 + call feedkeys('2gg02lv2j2ly', 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['asdf', '«口=', 'qwer', '口口', 'zxcv'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 6, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 4, 0]], + \ [[bufnr('%'), 4, 1, 0], [bufnr('%'), 4, 6, 0]], + \ [[bufnr('%'), 5, 1, 0], [bufnr('%'), 5, 4, 0]], + \ ] + let g:checked = 0 + call feedkeys("G0\<C-V>3l4ky", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['ghjkl', '口»', 'tyuiop', '=口口', 'bnm'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 9, 0]], + \ [[bufnr('%'), 2, 7, 0], [bufnr('%'), 2, 11, 0]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 10, 0]], + \ [[bufnr('%'), 4, 7, 0], [bufnr('%'), 4, 13, 0]], + \ [[bufnr('%'), 5, 5, 0], [bufnr('%'), 5, 7, 0]], + \ ] + let g:checked = 0 + call feedkeys("G04l\<C-V>$4ky", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + bwipe! + + unlet g:expected_region + unlet g:expected_regionpos + unlet g:checked + autocmd! TextYankPost + delfunc Check_Results +endfunc + +func Test_visual_block_cursor_delete() + new + call setline(1, 'ab') + exe ":norm! $\<c-v>hI\<Del>\<ESC>" + call assert_equal(['b'], getline(1, 1)) + bwipe! +endfunc + +func Test_visual_block_cursor_insert_enter() + new + call setline(1, ['asdf asdf', 'asdf asdf', 'asdf asdf', 'asdf asdf']) + call cursor(1, 5) + exe ":norm! \<c-v>3jcw\<cr>" + call assert_equal(['asdfw', 'asdf', 'asdfasdf', 'asdfasdf', 'asdfasdf'], getline(1, '$')) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_xdg.vim b/src/testdir/test_xdg.vim index 6f35b72..b9ec3c7 100644 --- a/src/testdir/test_xdg.vim +++ b/src/testdir/test_xdg.vim @@ -80,6 +80,7 @@ func Test_xdg_runtime_files() call assert_match('XfakeHOME/\.vimrc', $MYVIMRC) call filter(g:, {idx, _ -> idx =~ '^rc'}) call assert_equal(#{rc_one: 'one', rc: '.vimrc'}, g:) + call assert_match('XfakeHOME/\.vim/view', &viewdir) call writefile(v:errors, 'Xresult') quit END @@ -94,6 +95,7 @@ func Test_xdg_runtime_files() call assert_match('XfakeHOME/\.vim/vimrc', $MYVIMRC) call filter(g:, {idx, _ -> idx =~ '^rc'}) call assert_equal(#{rc_two: 'two', rc: '.vim/vimrc'}, g:) + call assert_match('XfakeHOME/\.vim/view', &viewdir) call writefile(v:errors, 'Xresult') quit END @@ -112,6 +114,7 @@ func Test_xdg_runtime_files() call assert_match('XfakeHOME/\.config/vim/vimrc', $MYVIMRC, msg) call filter(g:, {idx, _ -> idx =~ '^rc'}) call assert_equal(#{rc_three: 'three', rc: '.config/vim/vimrc'}, g:) + call assert_match('XfakeHOME/\.config/vim/view', &viewdir) call writefile(v:errors, 'Xresult') quit END @@ -128,6 +131,7 @@ func Test_xdg_runtime_files() call assert_match('XfakeHOME/xdg/vim/vimrc', $MYVIMRC, msg) call filter(g:, {idx, _ -> idx =~ '^rc'}) call assert_equal(#{rc_four: 'four', rc: 'xdg/vim/vimrc'}, g:) + call assert_match('XfakeHOME/xdg/vim/view, &viewdir) call writefile(v:errors, 'Xresult') quit END diff --git a/src/testdir/test_xxd.vim b/src/testdir/test_xxd.vim index 7a2771e..a91a1fc 100644 --- a/src/testdir/test_xxd.vim +++ b/src/testdir/test_xxd.vim @@ -411,6 +411,22 @@ func Test_xxd_max_cols() endfor endfunc + +" This used to trigger a buffer overflow (#14738) +func Test_xxd_buffer_overflow() + CheckUnix + if system('file ' .. s:xxd_cmd) =~ '32-bit' + throw 'Skipped: test only works on 64-bit architecture' + endif + new + let input = repeat('A', 256) + call writefile(['-9223372036854775808: ' . repeat("\e[1;32m41\e[0m ", 256) . ' ' . repeat("\e[1;32mA\e[0m", 256)], 'Xxdexpected', 'D') + exe 'r! printf ' . input . '| ' . s:xxd_cmd . ' -Ralways -g1 -c256 -d -o 9223372036854775808 > Xxdout' + call assert_equalfile('Xxdexpected', 'Xxdout') + call delete('Xxdout') + bwipe! +endfunc + " -c0 selects the format specific default column value, as if no -c was given " except for -ps, where it disables extra newlines func Test_xxd_c0_is_def_cols() diff --git a/src/testing.c b/src/testing.c index 33de3a5..3e9e077 100644 --- a/src/testing.c +++ b/src/testing.c @@ -1051,6 +1051,8 @@ f_test_override(typval_T *argvars, typval_T *rettv UNUSED) ml_get_alloc_lines = val; else if (STRCMP(name, (char_u *)"autoload") == 0) override_autoload = val; + else if (STRCMP(name, (char_u *)"defcompile") == 0) + override_defcompile = val; else if (STRCMP(name, (char_u *)"ALL") == 0) { disable_char_avail_for_testing = FALSE; diff --git a/src/userfunc.c b/src/userfunc.c index 71b3983..7536234 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -5452,6 +5452,10 @@ define_function( // :func does not use Vim9 script syntax, even in a Vim9 script file fp->uf_script_ctx.sc_version = SCRIPT_VERSION_MAX; + // If test_override('defcompile' 1) is used, then compile the function now + if (eap->cmdidx == CMD_def && override_defcompile) + defcompile_function(fp, NULL); + goto ret_free; erret: diff --git a/src/version.c b/src/version.c index 71e56a2..14a5922 100644 --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,244 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 496, +/**/ + 495, +/**/ + 494, +/**/ + 493, +/**/ + 492, +/**/ + 491, +/**/ + 490, +/**/ + 489, +/**/ + 488, +/**/ + 487, +/**/ + 486, +/**/ + 485, +/**/ + 484, +/**/ + 483, +/**/ + 482, +/**/ + 481, +/**/ + 480, +/**/ + 479, +/**/ + 478, +/**/ + 477, +/**/ + 476, +/**/ + 475, +/**/ + 474, +/**/ + 473, +/**/ + 472, +/**/ + 471, +/**/ + 470, +/**/ + 469, +/**/ + 468, +/**/ + 467, +/**/ + 466, +/**/ + 465, +/**/ + 464, +/**/ + 463, +/**/ + 462, +/**/ + 461, +/**/ + 460, +/**/ + 459, +/**/ + 458, +/**/ + 457, +/**/ + 456, +/**/ + 455, +/**/ + 454, +/**/ + 453, +/**/ + 452, +/**/ + 451, +/**/ + 450, +/**/ + 449, +/**/ + 448, +/**/ + 447, +/**/ + 446, +/**/ + 445, +/**/ + 444, +/**/ + 443, +/**/ + 442, +/**/ + 441, +/**/ + 440, +/**/ + 439, +/**/ + 438, +/**/ + 437, +/**/ + 436, +/**/ + 435, +/**/ + 434, +/**/ + 433, +/**/ + 432, +/**/ + 431, +/**/ + 430, +/**/ + 429, +/**/ + 428, +/**/ + 427, +/**/ + 426, +/**/ + 425, +/**/ + 424, +/**/ + 423, +/**/ + 422, +/**/ + 421, +/**/ + 420, +/**/ + 419, +/**/ + 418, +/**/ + 417, +/**/ + 416, +/**/ + 415, +/**/ + 414, +/**/ + 413, +/**/ + 412, +/**/ + 411, +/**/ + 410, +/**/ + 409, +/**/ + 408, +/**/ + 407, +/**/ + 406, +/**/ + 405, +/**/ + 404, +/**/ + 403, +/**/ + 402, +/**/ + 401, +/**/ + 400, +/**/ + 399, +/**/ + 398, +/**/ + 397, +/**/ + 396, +/**/ + 395, +/**/ + 394, +/**/ + 393, +/**/ + 392, +/**/ + 391, +/**/ + 390, +/**/ + 389, +/**/ + 388, +/**/ + 387, +/**/ + 386, +/**/ + 385, +/**/ + 384, +/**/ + 383, +/**/ + 382, +/**/ + 381, +/**/ + 380, +/**/ + 379, +/**/ + 378, +/**/ 377, /**/ 376, @@ -1500,6 +1500,8 @@ typedef enum , HLF_SPL // SpellLocal , HLF_PNI // popup menu normal item , HLF_PSI // popup menu selected item + , HLF_PMNI // popup menu matched text in normal item + , HLF_PMSI // popup menu matched text in selected item , HLF_PNK // popup menu normal item "kind" , HLF_PSK // popup menu selected item "kind" , HLF_PNX // popup menu normal item "menu" (extra text) @@ -1525,11 +1527,20 @@ typedef enum 'n', 'a', 'b', 'N', 'G', 'O', 'r', 's', 'S', 'c', 't', 'v', 'V', \ 'w', 'W', 'f', 'F', 'A', 'C', 'D', 'T', '-', '>', \ 'B', 'P', 'R', 'L', \ - '+', '=', '[', ']', '{', '}', 'x', 'X', \ + '+', '=', 'k', '<','[', ']', '{', '}', 'x', 'X', \ '*', '#', '_', '!', '.', 'o', 'q', \ 'z', 'Z', 'g'} /* + * Values for behaviour in spell_move_to + */ +typedef enum +{ + SMT_ALL = 0 // Move to "all" words + , SMT_BAD // Move to "bad" words only + , SMT_RARE // Move to "rare" words only +} smt_T; +/* * Boolean constants */ #ifndef TRUE diff --git a/src/vim9class.c b/src/vim9class.c index 4314b52..f66aa68 100644 --- a/src/vim9class.c +++ b/src/vim9class.c @@ -137,7 +137,7 @@ parse_member( (void)skip_expr_concatenate(&init_arg, &expr_start, &expr_end, &evalarg); init_arg = skipwhite(init_arg); - if (*init_arg != NUL) + if (*init_arg != NUL && !vim9_comment_start(init_arg)) { semsg(_(e_trailing_characters_str), init_arg); return FAIL; @@ -3845,7 +3845,7 @@ object_len(object_T *obj) * Return a textual representation of object "obj" */ char_u * -object_string( +object2string( object_T *obj, char_u *numbuf, int copyID, diff --git a/src/vim9type.c b/src/vim9type.c index 775291e..ffdf7fa 100644 --- a/src/vim9type.c +++ b/src/vim9type.c @@ -1466,7 +1466,14 @@ parse_type_user_defined( } if (give_error && (did_emsg == did_emsg_before)) + { + char_u *p = skip_type(*arg, FALSE); + char cc = *p; + + *p = NUL; semsg(_(e_type_not_recognized_str), *arg); + *p = cc; + } return NULL; } @@ -2091,10 +2098,12 @@ check_typval_is_value(typval_T *tv) case VAR_CLASS: { class_T *cl = tv->vval.v_class; - if (IS_ENUM(cl)) - semsg(_(e_using_enum_as_value_str), cl->class_name); + char_u *class_name = (cl == NULL) ? (char_u *)"" + : cl->class_name; + if (cl != NULL && IS_ENUM(cl)) + semsg(_(e_using_enum_as_value_str), class_name); else - semsg(_(e_using_class_as_value_str), cl->class_name); + semsg(_(e_using_class_as_value_str), class_name); } return FAIL; diff --git a/src/xxd/xxd.c b/src/xxd/xxd.c index cf8b4ea..7a3d36a 100644 --- a/src/xxd/xxd.c +++ b/src/xxd/xxd.c @@ -62,6 +62,7 @@ * 17.01.2024 use size_t instead of usigned int for code-generation (-i), #13876 * 25.01.2024 revert the previous patch (size_t instead of unsigned int) * 10.02.2024 fix buffer-overflow when writing color output to buffer, #14003 + * 10.05.2024 fix another buffer-overflow when writing colored output to buffer, #14738 * * (c) 1990-1998 by Juergen Weigert (jnweiger@gmail.com) * @@ -142,7 +143,7 @@ extern void perror __P((char *)); # endif #endif -char version[] = "xxd 2024-02-10 by Juergen Weigert et al."; +char version[] = "xxd 2024-05-10 by Juergen Weigert et al."; #ifdef WIN32 char osver[] = " (Win32)"; #else @@ -205,29 +206,16 @@ char osver[] = ""; /* * LLEN is the maximum length of a line; other than the visible characters * we need to consider also the escape color sequence prologue/epilogue , - * (11 bytes for each character). The most larger format is the default one: - * addr + 1 word for each col/2 + 1 char for each col - * - * addr 1st group 2nd group - * +-------+ +-----------------+ +------+ - * 01234567: 1234 5678 9abc def0 12345678 - * - * - addr: typically 012345678: -> from 10 up to 18 bytes (including trailing - * space) - * - 1st group: 1234 5678 9abc ... -> each byte may be colored, so add 11 - * for each byte - * - space -> 1 byte - * - 2nd group: 12345678 -> each char may be colore so add 11 - * for each byte - * - new line -> 1 byte - * - zero (end line) -> 1 byte + * (11 bytes for each character). */ -#define LLEN (2*(int)sizeof(unsigned long) + 2 + /* addr + ": " */ \ - (11 * 2 + 4 + 1) * (COLS / 2) + /* 1st group */ \ - 1 + /* space */ \ - (1 + 11) * COLS + /* 2nd group */ \ - 1 + /* new line */ \ - 1) /* zero */ +#define LLEN \ + (39 /* addr: ⌈log10(ULONG_MAX)⌉ if "-d" flag given. We assume ULONG_MAX = 2**128 */ \ + + 2 /* ": " */ \ + + 13 * COLS /* hex dump with colors */ \ + + (COLS - 1) /* whitespace between groups if "-g1" option given and "-c" maxed out */ \ + + 2 /* whitespace */ \ + + 12 * COLS /* ASCII dump with colors */ \ + + 2) /* "\n\0" */ char hexxa[] = "0123456789abcdef0123456789ABCDEF", *hexx = hexxa; |