diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 34712cbce7..3b6be28bf8 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2848,7 +2848,8 @@ str2list({expr} [, {utf8}]) List convert each character of {expr} to str2nr({expr} [, {base} [, {quoted}]]) Number convert String to Number strcharpart({str}, {start} [, {len}]) - String {len} characters of {str} at {start} + String {len} characters of {str} at + character {start} strchars({expr} [, {skipcc}]) Number character length of the String {expr} strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} strftime({format} [, {time}]) String format time with a specified format @@ -2857,8 +2858,9 @@ stridx({haystack}, {needle} [, {start}]) Number index of {needle} in {haystack} string({expr}) String String representation of {expr} value strlen({expr}) Number length of the String {expr} -strpart({str}, {start} [, {len}]) - String {len} bytes of {str} at byte {start} +strpart({str}, {start} [, {len} [, {chars}]]) + String {len} bytes/chars of {str} at + byte {start} strptime({format}, {timestring}) Number Convert {timestring} to unix timestamp strridx({haystack}, {needle} [, {start}]) @@ -3430,7 +3432,8 @@ byte2line({byte}) *byte2line()* byteidx({expr}, {nr}) *byteidx()* Return byte index of the {nr}'th character in the string - {expr}. Use zero for the first character, it returns zero. + {expr}. Use zero for the first character, it then returns + zero. This function is only useful when there are multibyte characters, otherwise the returned value is equal to {nr}. Composing characters are not counted separately, their byte @@ -9960,17 +9963,22 @@ strlen({expr}) The result is a Number, which is the length of the String {expr} in bytes. If the argument is a Number it is first converted to a String. For other types an error is given. - If you want to count the number of multi-byte characters use + If you want to count the number of multibyte characters use |strchars()|. Also see |len()|, |strdisplaywidth()| and |strwidth()|. Can also be used as a |method|: > GetString()->strlen() -strpart({src}, {start} [, {len}]) *strpart()* +strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* The result is a String, which is part of {src}, starting from byte {start}, with the byte length {len}. - To count characters instead of bytes use |strcharpart()|. + When {chars} is present and TRUE then {len} is the number of + characters positions (composing characters are not counted + separately, thus "1" means one base character and any + following composing characters). + To count {start} as characters instead of bytes use + |strcharpart()|. When bytes are selected which do not exist, this doesn't result in an error, the bytes are simply omitted. @@ -9982,8 +9990,8 @@ strpart({src}, {start} [, {len}]) *strpart()* strpart("abcdefg", 3) == "defg" < Note: To get the first character, {start} must be 0. For - example, to get three bytes under and after the cursor: > - strpart(getline("."), col(".") - 1, 3) + example, to get the character under the cursor: > + strpart(getline("."), col(".") - 1, 1, v:true) < Can also be used as a |method|: > GetText()->strpart(5) diff --git a/src/channel.c b/src/channel.c index b9c8566e52..8ded2ced0c 100644 --- a/src/channel.c +++ b/src/channel.c @@ -923,7 +923,7 @@ channel_connect( *waittime -= elapsed_msec; if (waitnow > 0) { - mch_delay((long)waitnow, TRUE); + mch_delay((long)waitnow, MCH_DELAY_IGNOREINPUT); ui_breakcheck(); *waittime -= waitnow; } diff --git a/src/eval.c b/src/eval.c index 056ac76c2a..2d2ad1b5fa 100644 --- a/src/eval.c +++ b/src/eval.c @@ -2675,6 +2675,7 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) int oplen; int concat; typval_T var2; + int vim9script = in_vim9script(); // "." is only string concatenation when scriptversion is 1 p = eval_next_non_blank(*arg, evalarg, &getnext); @@ -2689,7 +2690,7 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) *arg = eval_next_line(evalarg); else { - if (evaluate && in_vim9script() && !VIM_ISWHITE(**arg)) + if (evaluate && vim9script && !VIM_ISWHITE(**arg)) { error_white_both(p, oplen); clear_tv(rettv); @@ -2721,14 +2722,14 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) /* * Get the second variable. */ - if (evaluate && in_vim9script() && !IS_WHITE_OR_NUL((*arg)[oplen])) + if (evaluate && vim9script && !IS_WHITE_OR_NUL((*arg)[oplen])) { error_white_both(p, oplen); clear_tv(rettv); return FAIL; } *arg = skipwhite_and_linebreak(*arg + oplen, evalarg); - if (eval6(arg, &var2, evalarg, !in_vim9script() && op == '.') == FAIL) + if (eval6(arg, &var2, evalarg, !vim9script && op == '.') == FAIL) { clear_tv(rettv); return FAIL; @@ -2745,12 +2746,12 @@ eval5(char_u **arg, typval_T *rettv, evalarg_T *evalarg) char_u *s1 = tv_get_string_buf(rettv, buf1); char_u *s2 = NULL; - if (in_vim9script() && (var2.v_type == VAR_VOID + if (vim9script && (var2.v_type == VAR_VOID || var2.v_type == VAR_CHANNEL || var2.v_type == VAR_JOB)) emsg(_(e_inval_string)); #ifdef FEAT_FLOAT - else if (var2.v_type == VAR_FLOAT) + else if (vim9script && var2.v_type == VAR_FLOAT) { vim_snprintf((char *)buf2, NUMBUFLEN, "%g", var2.vval.v_float); diff --git a/src/evalfunc.c b/src/evalfunc.c index f85c05ee8f..eeeb180fdc 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -950,7 +950,7 @@ static funcentry_T global_functions[] = {"stridx", 2, 3, FEARG_1, ret_number, f_stridx}, {"string", 1, 1, FEARG_1, ret_string, f_string}, {"strlen", 1, 1, FEARG_1, ret_number, f_strlen}, - {"strpart", 2, 3, FEARG_1, ret_string, f_strpart}, + {"strpart", 2, 4, FEARG_1, ret_string, f_strpart}, {"strptime", 2, 2, FEARG_1, ret_number, #ifdef HAVE_STRPTIME f_strptime @@ -8305,10 +8305,8 @@ f_strpart(typval_T *argvars, typval_T *rettv) else len = slen - n; // default len: all bytes that are available. - /* - * Only return the overlap between the specified part and the actual - * string. - */ + // Only return the overlap between the specified part and the actual + // string. if (n < 0) { len += n; @@ -8321,6 +8319,16 @@ f_strpart(typval_T *argvars, typval_T *rettv) else if (n + len > slen) len = slen - n; + if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) + { + int off; + + // length in characters + for (off = n; off < slen && len > 0; --len) + off += mb_ptr2len(p + off); + len = off - n; + } + rettv->v_type = VAR_STRING; rettv->vval.v_string = vim_strnsave(p + n, len); } diff --git a/src/evalvars.c b/src/evalvars.c index c43bf585a2..26e15ddfb9 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2463,6 +2463,20 @@ eval_variable( tv = sv->sv_tv; } } + else if (in_vim9script()) + { + ufunc_T *ufunc = find_func(name, FALSE, NULL); + + if (ufunc != NULL) + { + foundFunc = TRUE; + if (rettv != NULL) + { + rettv->v_type = VAR_FUNC; + rettv->vval.v_string = vim_strsave(ufunc->uf_name); + } + } + } } if (!foundFunc) diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 4951724817..7c7d9e7a46 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -66,7 +66,9 @@ static int getargopt(exarg_T *eap); # define ex_cexpr ex_ni #endif +static linenr_T default_address(exarg_T *eap); static linenr_T get_address(exarg_T *, char_u **, cmd_addr_T addr_type, int skip, int silent, int to_other_file, int address_count); +static void address_default_all(exarg_T *eap); static void get_flags(exarg_T *eap); #if !defined(FEAT_PERL) \ || !defined(FEAT_PYTHON) || !defined(FEAT_PYTHON3) \ @@ -1886,7 +1888,9 @@ do_one_cmd( ea.cmd = cmd; #ifdef FEAT_EVAL - if (may_have_range) + if (!may_have_range) + ea.line1 = ea.line2 = default_address(&ea); + else #endif if (parse_cmd_address(&ea, &errormsg, FALSE) == FAIL) goto doend; @@ -2288,59 +2292,7 @@ do_one_cmd( } if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) - { - buf_T *buf; - - ea.line1 = 1; - switch (ea.addr_type) - { - case ADDR_LINES: - case ADDR_OTHER: - ea.line2 = curbuf->b_ml.ml_line_count; - break; - case ADDR_LOADED_BUFFERS: - buf = firstbuf; - while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) - buf = buf->b_next; - ea.line1 = buf->b_fnum; - buf = lastbuf; - while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) - buf = buf->b_prev; - ea.line2 = buf->b_fnum; - break; - case ADDR_BUFFERS: - ea.line1 = firstbuf->b_fnum; - ea.line2 = lastbuf->b_fnum; - break; - case ADDR_WINDOWS: - ea.line2 = LAST_WIN_NR; - break; - case ADDR_TABS: - ea.line2 = LAST_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - ea.line2 = 1; - break; - case ADDR_ARGUMENTS: - if (ARGCOUNT == 0) - ea.line1 = ea.line2 = 0; - else - ea.line2 = ARGCOUNT; - break; - case ADDR_QUICKFIX_VALID: -#ifdef FEAT_QUICKFIX - ea.line2 = qf_get_valid_size(&ea); - if (ea.line2 == 0) - ea.line2 = 1; -#endif - break; - case ADDR_NONE: - case ADDR_UNSIGNED: - case ADDR_QUICKFIX: - iemsg(_("INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX")); - break; - } - } + address_default_all(&ea); // accept numbered register only when no count allowed (:put) if ( (ea.argt & EX_REGSTR) @@ -3017,50 +2969,7 @@ parse_cmd_address(exarg_T *eap, char **errormsg, int silent) for (;;) { eap->line1 = eap->line2; - switch (eap->addr_type) - { - case ADDR_LINES: - case ADDR_OTHER: - // Default is the cursor line number. Avoid using an invalid - // line number though. - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) - eap->line2 = curbuf->b_ml.ml_line_count; - else - eap->line2 = curwin->w_cursor.lnum; - break; - case ADDR_WINDOWS: - eap->line2 = CURRENT_WIN_NR; - break; - case ADDR_ARGUMENTS: - eap->line2 = curwin->w_arg_idx + 1; - if (eap->line2 > ARGCOUNT) - eap->line2 = ARGCOUNT; - break; - case ADDR_LOADED_BUFFERS: - case ADDR_BUFFERS: - eap->line2 = curbuf->b_fnum; - break; - case ADDR_TABS: - eap->line2 = CURRENT_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - case ADDR_UNSIGNED: - eap->line2 = 1; - break; - case ADDR_QUICKFIX: -#ifdef FEAT_QUICKFIX - eap->line2 = qf_get_cur_idx(eap); -#endif - break; - case ADDR_QUICKFIX_VALID: -#ifdef FEAT_QUICKFIX - eap->line2 = qf_get_cur_valid_idx(eap); -#endif - break; - case ADDR_NONE: - // Will give an error later if a range is found. - break; - } + eap->line2 = default_address(eap); eap->cmd = skipwhite(eap->cmd); lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent, eap->addr_count == 0, address_count++); @@ -3248,6 +3157,27 @@ append_command(char_u *cmd) *d = NUL; } +/* + * If "start" points "&opt", "&l:opt", "&g:opt" or "$ENV" return a pointer to + * the name. Otherwise just return "start". + */ + char_u * +skip_option_env_lead(char_u *start) +{ + char_u *name = start; + + if (*start == '&') + { + if ((start[1] == 'l' || start[1] == 'g') && start[2] == ':') + name += 3; + else + name += 1; + } + else if (*start == '$') + name += 1; + return name; +} + /* * Find an Ex command by its name, either built-in or user. * Start of the name can be found at eap->cmd. @@ -3279,9 +3209,7 @@ find_ex_command( p = eap->cmd; if (lookup != NULL) { - // Skip over first char for "&opt = val", "$ENV = val" and "@r = val". - char_u *pskip = (*eap->cmd == '&' || *eap->cmd == '$') - ? eap->cmd + 1 : eap->cmd; + char_u *pskip = skip_option_env_lead(eap->cmd); if (vim_strchr((char_u *)"{('[\"@", *p) != NULL || ((p = to_name_const_end(pskip)) > eap->cmd && *p != NUL)) @@ -3659,6 +3587,61 @@ addr_error(cmd_addr_T addr_type) emsg(_(e_invrange)); } +/* + * Return the default address for an address type. + */ + static linenr_T +default_address(exarg_T *eap) +{ + linenr_T lnum = 0; + + switch (eap->addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + // Default is the cursor line number. Avoid using an invalid + // line number though. + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + else + lnum = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + lnum = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + lnum = curwin->w_arg_idx + 1; + if (lnum > ARGCOUNT) + lnum = ARGCOUNT; + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + lnum = curbuf->b_fnum; + break; + case ADDR_TABS: + lnum = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + case ADDR_UNSIGNED: + lnum = 1; + break; + case ADDR_QUICKFIX: +#ifdef FEAT_QUICKFIX + lnum = qf_get_cur_idx(eap); +#endif + break; + case ADDR_QUICKFIX_VALID: +#ifdef FEAT_QUICKFIX + lnum = qf_get_cur_valid_idx(eap); +#endif + break; + case ADDR_NONE: + // Will give an error later if a range is found. + break; + } + return lnum; +} + /* * Get a single EX address. * @@ -4020,6 +4003,68 @@ error: return lnum; } +/* + * Set eap->line1 and eap->line2 to the whole range. + * Used for commands with the EX_DFLALL flag and no range given. + */ + static void +address_default_all(exarg_T *eap) +{ + eap->line1 = 1; + switch (eap->addr_type) + { + case ADDR_LINES: + case ADDR_OTHER: + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: + { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) + buf = buf->b_next; + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) + buf = buf->b_prev; + eap->line2 = buf->b_fnum; + } + break; + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + eap->line2 = LAST_WIN_NR; + break; + case ADDR_TABS: + eap->line2 = LAST_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + eap->line2 = 1; + break; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) + eap->line1 = eap->line2 = 0; + else + eap->line2 = ARGCOUNT; + break; + case ADDR_QUICKFIX_VALID: +#ifdef FEAT_QUICKFIX + eap->line2 = qf_get_valid_size(eap); + if (eap->line2 == 0) + eap->line2 = 1; +#endif + break; + case ADDR_NONE: + case ADDR_UNSIGNED: + case ADDR_QUICKFIX: + iemsg(_("INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX")); + break; + } +} + + /* * Get flags from an Ex command argument. */ diff --git a/src/if_cscope.c b/src/if_cscope.c index 0e6f5ae30f..c027c71b48 100644 --- a/src/if_cscope.c +++ b/src/if_cscope.c @@ -2243,7 +2243,7 @@ cs_release_csp(int i, int freefnpp) waitpid_errno = errno; if (pid != 0) break; // break unless the process is still running - mch_delay(50L, FALSE); // sleep 50 ms + mch_delay(50L, 0); // sleep 50 ms } # endif /* @@ -2278,7 +2278,7 @@ cs_release_csp(int i, int freefnpp) alive = FALSE; // cscope process no longer exists break; } - mch_delay(50L, FALSE); // sleep 50ms + mch_delay(50L, 0); // sleep 50 ms } } if (alive) diff --git a/src/normal.c b/src/normal.c index d006642164..ca863675e5 100644 --- a/src/normal.c +++ b/src/normal.c @@ -3656,8 +3656,10 @@ nv_ident(cmdarg_T *cap) { if (g_cmd) STRCPY(buf, "tj "); + else if (cap->count0 == 0) + STRCPY(buf, "ta "); else - sprintf((char *)buf, "%ldta ", cap->count0); + sprintf((char *)buf, ":%ldta ", cap->count0); } } diff --git a/src/os_amiga.c b/src/os_amiga.c index 850c26acda..91c13e7d29 100644 --- a/src/os_amiga.c +++ b/src/os_amiga.c @@ -222,10 +222,10 @@ mch_avail_mem(int special) /* * Waits a specified amount of time, or until input arrives if - * ignoreinput is FALSE. + * flags does not have MCH_DELAY_IGNOREINPUT. */ void -mch_delay(long msec, int ignoreinput) +mch_delay(long msec, int flags) { #ifndef LATTICE // SAS declares void Delay(ULONG) void Delay(long); @@ -233,7 +233,7 @@ mch_delay(long msec, int ignoreinput) if (msec > 0) { - if (ignoreinput) + if (flags & MCH_DELAY_IGNOREINPUT) Delay(msec / 20L); // Delay works with 20 msec intervals else WaitForChar(raw_in, msec * 1000L); diff --git a/src/os_unix.c b/src/os_unix.c index 4ceab10d99..e84f53920a 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -577,15 +577,19 @@ mch_total_mem(int special UNUSED) } #endif +/* + * "flags": MCH_DELAY_IGNOREINPUT - don't read input + * MCH_DELAY_SETTMODE - use settmode() even for short delays + */ void -mch_delay(long msec, int ignoreinput) +mch_delay(long msec, int flags) { tmode_T old_tmode; #ifdef FEAT_MZSCHEME long total = msec; // remember original value #endif - if (ignoreinput) + if (flags & MCH_DELAY_IGNOREINPUT) { // Go to cooked mode without echo, to allow SIGINT interrupting us // here. But we don't want QUIT to kill us (CTRL-\ used in a @@ -593,7 +597,8 @@ mch_delay(long msec, int ignoreinput) // Only do this if sleeping for more than half a second. in_mch_delay = TRUE; old_tmode = mch_cur_tmode; - if (mch_cur_tmode == TMODE_RAW && msec > 500) + if (mch_cur_tmode == TMODE_RAW + && (msec > 500 || (flags & MCH_DELAY_SETTMODE))) settmode(TMODE_SLEEP); /* @@ -636,10 +641,8 @@ mch_delay(long msec, int ignoreinput) tv.tv_sec = msec / 1000; tv.tv_usec = (msec % 1000) * 1000; - /* - * NOTE: Solaris 2.6 has a bug that makes select() hang here. Get - * a patch from Sun to fix this. Reported by Gunnar Pedersen. - */ + // NOTE: Solaris 2.6 has a bug that makes select() hang here. Get + // a patch from Sun to fix this. Reported by Gunnar Pedersen. select(0, NULL, NULL, NULL, &tv); } # endif // HAVE_SELECT @@ -650,7 +653,7 @@ mch_delay(long msec, int ignoreinput) while (total > 0); #endif - if (msec > 500) + if (msec > 500 || (flags & MCH_DELAY_SETTMODE)) settmode(old_tmode); in_mch_delay = FALSE; } @@ -1284,7 +1287,7 @@ mch_suspend(void) long wait_time; for (wait_time = 0; !sigcont_received && wait_time <= 3L; wait_time++) - mch_delay(wait_time, FALSE); + mch_delay(wait_time, 0); } # endif in_mch_suspend = FALSE; @@ -4176,7 +4179,7 @@ wait4pid(pid_t child, waitstatus *status) if (wait_pid == 0) { // Wait for 1 to 10 msec before trying again. - mch_delay(delay_msec, TRUE); + mch_delay(delay_msec, MCH_DELAY_IGNOREINPUT | MCH_DELAY_SETTMODE); if (++delay_msec > 10) delay_msec = 10; continue; @@ -5284,6 +5287,9 @@ finished: { long delay_msec = 1; + out_str(T_CTE); // possibly disables modifyOtherKeys, so that + // the system can recognize CTRL-C + /* * Similar to the loop above, but only handle X events, no * I/O. @@ -5317,11 +5323,14 @@ finished: clip_update(); // Wait for 1 to 10 msec. 1 is faster but gives the child - // less time. - mch_delay(delay_msec, TRUE); + // less time, gradually wait longer. + mch_delay(delay_msec, + MCH_DELAY_IGNOREINPUT | MCH_DELAY_SETTMODE); if (++delay_msec > 10) delay_msec = 10; } + + out_str(T_CTI); // possibly enables modifyOtherKeys again } # endif @@ -6732,7 +6741,7 @@ mch_expand_wildcards( // When running in the background, give it some time to create the temp // file, but don't wait for it to finish. if (ampersand) - mch_delay(10L, TRUE); + mch_delay(10L, MCH_DELAY_IGNOREINPUT); extra_shell_arg = NULL; // cleanup show_shell_mess = TRUE; diff --git a/src/os_win32.c b/src/os_win32.c index 43ad1026d5..96af44364b 100644 --- a/src/os_win32.c +++ b/src/os_win32.c @@ -6739,7 +6739,7 @@ notsgr: void mch_delay( long msec, - int ignoreinput UNUSED) + int flags UNUSED) { #if defined(FEAT_GUI_MSWIN) && !defined(VIMDLL) Sleep((int)msec); // never wait for input @@ -6751,7 +6751,7 @@ mch_delay( return; } # endif - if (ignoreinput) + if (flags & MCH_DELAY_IGNOREINPUT) # ifdef FEAT_MZSCHEME if (mzthreads_allowed() && p_mzq > 0 && msec > p_mzq) { diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro index 20bb85a40f..05c1a85b59 100644 --- a/src/proto/ex_docmd.pro +++ b/src/proto/ex_docmd.pro @@ -10,6 +10,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, int skip_only); void undo_cmdmod(exarg_T *eap, int save_msg_scroll); int parse_cmd_address(exarg_T *eap, char **errormsg, int silent); int checkforcmd(char_u **pp, char *cmd, int len); +char_u *skip_option_env_lead(char_u *start); char_u *find_ex_command(exarg_T *eap, int *full, void *(*lookup)(char_u *, size_t, cctx_T *), cctx_T *cctx); int modifier_len(char_u *cmd); int cmd_exists(char_u *name); diff --git a/src/proto/os_amiga.pro b/src/proto/os_amiga.pro index b2ad9c3b55..abebae154f 100644 --- a/src/proto/os_amiga.pro +++ b/src/proto/os_amiga.pro @@ -5,7 +5,7 @@ void mch_write(char_u *p, int len); int mch_inchar(char_u *buf, int maxlen, long time, int tb_change_cnt); int mch_char_avail(void); long_u mch_avail_mem(int special); -void mch_delay(long msec, int ignoreinput); +void mch_delay(long msec, int flags); void mch_suspend(void); void mch_init(void); int mch_check_win(int argc, char **argv); diff --git a/src/proto/os_unix.pro b/src/proto/os_unix.pro index cb84994429..ee22b4a422 100644 --- a/src/proto/os_unix.pro +++ b/src/proto/os_unix.pro @@ -5,7 +5,7 @@ int mch_inchar(char_u *buf, int maxlen, long wtime, int tb_change_cnt); int mch_char_avail(void); int mch_check_messages(void); long_u mch_total_mem(int special); -void mch_delay(long msec, int ignoreinput); +void mch_delay(long msec, int flags); int mch_stackcheck(char *p); void mch_suspend(void); void mch_init(void); diff --git a/src/proto/os_win32.pro b/src/proto/os_win32.pro index 85c09c0ba1..18c81273e1 100644 --- a/src/proto/os_win32.pro +++ b/src/proto/os_win32.pro @@ -53,7 +53,7 @@ int mch_signal_job(job_T *job, char_u *how); void mch_clear_job(job_T *job); void mch_set_normal_colors(void); void mch_write(char_u *s, int len); -void mch_delay(long msec, int ignoreinput); +void mch_delay(long msec, int flags); int mch_remove(char_u *name); void mch_breakcheck(int force); long_u mch_total_mem(int special); diff --git a/src/screen.c b/src/screen.c index 88cdf8cf97..c1f76a3275 100644 --- a/src/screen.c +++ b/src/screen.c @@ -465,7 +465,8 @@ screen_line( // double-wide character. Clear the left half to avoid it getting the popup // window background color. if (coloff > 0 && ScreenLines[off_to] == 0 - && ScreenLinesUC[off_to - 1] != 0) + && ScreenLinesUC[off_to - 1] != 0 + && (*mb_char2cells)(ScreenLinesUC[off_to - 1]) > 1) { ScreenLines[off_to - 1] = ' '; ScreenLinesUC[off_to - 1] = 0; diff --git a/src/spell.h b/src/spell.h index b444145be3..7e4f813b46 100644 --- a/src/spell.h +++ b/src/spell.h @@ -66,6 +66,7 @@ struct slang_S int sl_add; // TRUE if it's a .add file. char_u *sl_fbyts; // case-folded word bytes + long sl_fbyts_len; // length of sl_fbyts idx_T *sl_fidxs; // case-folded word indexes char_u *sl_kbyts; // keep-case word bytes idx_T *sl_kidxs; // keep-case word indexes diff --git a/src/spellfile.c b/src/spellfile.c index 957be097a6..6aeac86b85 100644 --- a/src/spellfile.c +++ b/src/spellfile.c @@ -315,7 +315,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len); static int set_sofo(slang_T *lp, char_u *from, char_u *to); static void set_sal_first(slang_T *lp); static int *mb_str2wide(char_u *s); -static int spell_read_tree(FILE *fd, char_u **bytsp, idx_T **idxsp, int prefixtree, int prefixcnt); +static int spell_read_tree(FILE *fd, char_u **bytsp, long *bytsp_len, idx_T **idxsp, int prefixtree, int prefixcnt); static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr); static void set_spell_charflags(char_u *flags, int cnt, char_u *upp); static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp); @@ -553,17 +553,18 @@ truncerr: } // - res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fidxs, FALSE, 0); + res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fbyts_len, + &lp->sl_fidxs, FALSE, 0); if (res != 0) goto someerror; // - res = spell_read_tree(fd, &lp->sl_kbyts, &lp->sl_kidxs, FALSE, 0); + res = spell_read_tree(fd, &lp->sl_kbyts, NULL, &lp->sl_kidxs, FALSE, 0); if (res != 0) goto someerror; // - res = spell_read_tree(fd, &lp->sl_pbyts, &lp->sl_pidxs, TRUE, + res = spell_read_tree(fd, &lp->sl_pbyts, NULL, &lp->sl_pidxs, TRUE, lp->sl_prefixcnt); if (res != 0) goto someerror; @@ -737,7 +738,7 @@ suggest_load_files(void) * : * Read the trie with the soundfolded words. */ - if (spell_read_tree(fd, &slang->sl_sbyts, &slang->sl_sidxs, + if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs, FALSE, 0) != 0) { someerror: @@ -1572,6 +1573,7 @@ mb_str2wide(char_u *s) spell_read_tree( FILE *fd, char_u **bytsp, + long *bytsp_len, idx_T **idxsp, int prefixtree, // TRUE for the prefix tree int prefixcnt) // when "prefixtree" is TRUE: prefix count @@ -1596,6 +1598,8 @@ spell_read_tree( if (bp == NULL) return SP_OTHERERROR; *bytsp = bp; + if (bytsp_len != NULL) + *bytsp_len = len; // Allocate the index array. ip = lalloc_clear(len * sizeof(int), TRUE); @@ -5609,8 +5613,8 @@ sug_filltree(spellinfo_T *spin, slang_T *slang) spin->si_blocks_cnt = 0; // Skip over any other NUL bytes (same word with different - // flags). - while (byts[n + 1] == 0) + // flags). But don't go over the end. + while (n + 1 < slang->sl_fbyts_len && byts[n + 1] == 0) { ++n; ++curi[depth]; diff --git a/src/term.c b/src/term.c index 1a505794ee..b99c69c946 100644 --- a/src/term.c +++ b/src/term.c @@ -3604,7 +3604,7 @@ stoptermcap(void) { # ifdef UNIX // Give the terminal a chance to respond. - mch_delay(100L, FALSE); + mch_delay(100L, 0); # endif # ifdef TCIFLUSH // Discard data received but not read. diff --git a/src/testdir/Make_dos.mak b/src/testdir/Make_dos.mak index ba728df9eb..89563848ec 100644 --- a/src/testdir/Make_dos.mak +++ b/src/testdir/Make_dos.mak @@ -117,7 +117,7 @@ $(TEST_OUTFILES): $(DOSTMP)\$(*B).in # Limitation: Only works with the +eval feature. newtests: newtestssilent - @if exist messages (findstr "SKIPPED FAILED" messages > nul) && type messages + @if exist messages type messages newtestssilent: $(NEW_TESTS_RES) diff --git a/src/testdir/Make_ming.mak b/src/testdir/Make_ming.mak index 00c9e1087a..f9dcfc86e0 100644 --- a/src/testdir/Make_ming.mak +++ b/src/testdir/Make_ming.mak @@ -129,7 +129,7 @@ $(DOSTMP)/%.in : %.in # Limitation: Only works with the +eval feature. newtests: newtestssilent - @if exist messages (findstr "SKIPPED FAILED" messages > nul) && type messages + @if exist messages type messages newtestssilent: $(NEW_TESTS_RES) diff --git a/src/testdir/Makefile b/src/testdir/Makefile index 7033d1e46e..2fb329aa1c 100644 --- a/src/testdir/Makefile +++ b/src/testdir/Makefile @@ -126,7 +126,7 @@ tinytests: $(SCRIPTS_TINY_OUT) RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE) $(VALGRIND) $(VIMPROG) -f $(GUI_FLAG) -u unix.vim newtests: newtestssilent - @/bin/sh -c "if test -f messages && grep -q 'SKIPPED\|FAILED' messages; then cat messages; fi" + @/bin/sh -c "if test -f messages; then cat messages; fi" newtestssilent: $(NEW_TESTS_RES) diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim index 02e7e14fd7..1490ffbc57 100644 --- a/src/testdir/test_eval_stuff.vim +++ b/src/testdir/test_eval_stuff.vim @@ -135,6 +135,12 @@ func Test_string_concatenation() let a = 'a' let a..=b call assert_equal('ab', a) + + if has('float') + let a = 'A' + let b = 1.234 + call assert_fails('echo a .. b', 'E806:') + endif endfunc " Test fix for issue #4507 diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index dc06bd72ec..e15199b786 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -513,6 +513,10 @@ func Test_strpart() call assert_equal('lép', strpart('éléphant', 2, 4)) call assert_equal('léphant', strpart('éléphant', 2)) + + call assert_equal('é', strpart('éléphant', 0, 1, 1)) + call assert_equal('ép', strpart('éléphant', 3, 2, v:true)) + call assert_equal('ó', strpart('cómposed', 1, 1, 1)) endfunc func Test_tolower() diff --git a/src/testdir/test_spellfile.vim b/src/testdir/test_spellfile.vim index f3578bce2f..ed11ce0af1 100644 --- a/src/testdir/test_spellfile.vim +++ b/src/testdir/test_spellfile.vim @@ -410,6 +410,24 @@ func Test_sugfile_format_error() call assert_fails("let s = spellsuggest('abc')", 'E782:') set nospell spelllang& + " invalid suggest word count in SUGTABLE + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000440000000022, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + " missing sugline in SUGTABLE + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000440000000000000005, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + let &rtp = save_rtp call delete('Xtest', 'rf') endfunc @@ -510,6 +528,197 @@ func Test_mkspell() call assert_fails('mkspell en en_US abc_xyz', 'E755:') endfunc +" Tests for :mkspell with a .dic and .aff file +func Test_aff_file_format_error() + " FIXME: For some reason, the :mkspell command below doesn't fail on the + " MS-Windows CI build. Disable this test on MS-Windows for now. + CheckNotMSWindows + + " No word count in .dic file + call writefile([], 'Xtest.dic') + call writefile([], 'Xtest.aff') + call assert_fails('mkspell! Xtest.spl Xtest', 'E760:') + + " create a .dic file for the tests below + call writefile(['1', 'work'], 'Xtest.dic') + + " Invalid encoding in .aff file + call writefile(['# comment', 'SET Xinvalidencoding'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Conversion in Xtest.aff not supported: from xinvalidencoding', output) + + " Invalid flag in .aff file + call writefile(['FLAG xxx'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Invalid value for FLAG in Xtest.aff line 1: xxx', output) + + " set FLAGS after using flag for an affix + call writefile(['SFX L Y 1', 'SFX L 0 re [^x]', 'FLAG long'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('FLAG after using flags in Xtest.aff line 3: long', output) + + " INFO in affix file + let save_encoding = &encoding + call mkdir('Xrtp/spell', 'p') + call writefile(['1', 'work'], 'Xrtp/spell/Xtest.dic') + call writefile(['NAME klingon', 'VERSION 1.4', 'AUTHOR Spock'], + \ 'Xrtp/spell/Xtest.aff') + silent mkspell! Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest + let save_rtp = &rtp + set runtimepath=./Xrtp + set spelllang=Xtest + set spell + let output = split(execute('spellinfo'), "\n") + call assert_equal("NAME klingon", output[1]) + call assert_equal("VERSION 1.4", output[2]) + call assert_equal("AUTHOR Spock", output[3]) + let &rtp = save_rtp + call delete('Xrtp', 'rf') + set spell& spelllang& spellfile& + %bw! + " 'encoding' must be set again to clear the spell file in memory + let &encoding = save_encoding + + " COMPOUNDFORBIDFLAG flag after PFX in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDFLAG c', 'COMPOUNDFORBIDFLAG x'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in Xtest.aff line 4', output) + + " COMPOUNDPERMITFLAG flag after PFX in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDPERMITFLAG c'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in Xtest.aff line 3', output) + + " Wrong COMPOUNDRULES flag value in an affix file + call writefile(['COMPOUNDRULES a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDRULES value in Xtest.aff line 1: a', output) + + " Wrong COMPOUNDWORDMAX flag value in an affix file + call writefile(['COMPOUNDWORDMAX 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDWORDMAX value in Xtest.aff line 1: 0', output) + + " Wrong COMPOUNDMIN flag value in an affix file + call writefile(['COMPOUNDMIN 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDMIN value in Xtest.aff line 1: 0', output) + + " Wrong COMPOUNDSYLMAX flag value in an affix file + call writefile(['COMPOUNDSYLMAX 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDSYLMAX value in Xtest.aff line 1: 0', output) + + " Wrong CHECKCOMPOUNDPATTERN flag value in an affix file + call writefile(['CHECKCOMPOUNDPATTERN 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong CHECKCOMPOUNDPATTERN value in Xtest.aff line 1: 0', output) + + " Duplicate affix entry in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L Y 1', 'PFX L 0 re x'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Duplicate affix in Xtest.aff line 3: L', output) + + " Duplicate affix entry in an affix file + call writefile(['PFX L Y 1', 'PFX L Y 1'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Unrecognized or duplicate item in Xtest.aff line 2: PFX', output) + + " Different combining flags in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L N 1'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Different combining flag in continued affix block in Xtest.aff line 3', output) + + " Try to reuse a affix used for BAD flag + call writefile(['BAD x', 'PFX x Y 1', 'PFX x 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in Xtest.aff line 2: x', output) + + " Trailing characters in an affix entry + call writefile(['PFX L Y 1 Test', 'PFX L 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 1: Test', output) + + " Trailing characters in an affix entry + call writefile(['PFX L Y 1', 'PFX L 0 re x Test'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 2: Test', output) + + " Incorrect combine flag in an affix entry + call writefile(['PFX L X 1', 'PFX L 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected Y or N in Xtest.aff line 1: X', output) + + " Invalid count for REP item + call writefile(['REP a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected REP(SAL) count in Xtest.aff line 1', output) + + " Trailing characters in REP item + call writefile(['REP 1', 'REP f ph test'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 2: test', output) + + " Invalid count for MAP item + call writefile(['MAP a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected MAP count in Xtest.aff line 1', output) + + " Duplicate character in a MAP item + call writefile(['MAP 2', 'MAP xx', 'MAP yy'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Duplicate character in MAP in Xtest.aff line 2', output) + + " Use COMPOUNDSYLMAX without SYLLABLE + call writefile(['COMPOUNDSYLMAX 2'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('COMPOUNDSYLMAX used without SYLLABLE', output) + + " Missing SOFOTO + call writefile(['SOFOFROM abcdef'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Missing SOFOTO line in Xtest.aff', output) + + " Length of SOFOFROM and SOFOTO differ + call writefile(['SOFOFROM abcde', 'SOFOTO ABCD'], 'Xtest.aff') + call assert_fails('mkspell! Xtest.spl Xtest', 'E759:') + + " Both SAL and SOFOFROM/SOFOTO items + call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Both SAL and SOFO lines in Xtest.aff', output) + + " use an alphabet flag when FLAG is num + call writefile(['FLAG num', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Flag is not a number in Xtest.aff line 2: L', output) + + " use number and alphabet flag when FLAG is num + call writefile(['FLAG num', 'SFX 4f Y 1', 'SFX 4f 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Affix name too long in Xtest.aff line 2: 4f', output) + + " use a single character flag when FLAG is long + call writefile(['FLAG long', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Illegal flag in Xtest.aff line 2: L', output) + + " duplicate word in the .dic file + call writefile(['2', 'good', 'good', 'good'], 'Xtest.dic') + call writefile(['NAME vim'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('First duplicate word in Xtest.dic line 3: good', output) + call assert_match('2 duplicate word(s) in Xtest.dic', output) + + call delete('Xtest.dic') + call delete('Xtest.aff') + call delete('Xtest.spl') + call delete('Xtest.sug') +endfunc + func Test_spell_add_word() set spellfile= call assert_fails('spellgood abc', 'E764:') @@ -524,4 +733,36 @@ func Test_spell_add_word() %bw! endfunc +" When 'spellfile' is not set, adding a new good word will automatically set +" the 'spellfile' +func Test_init_spellfile() + let save_rtp = &rtp + let save_encoding = &encoding + call mkdir('Xrtp/spell', 'p') + call writefile(['vim'], 'Xrtp/spell/Xtest.dic') + silent mkspell Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest.dic + set runtimepath=./Xrtp + set spelllang=Xtest + set spell + silent spellgood abc + call assert_equal('./Xrtp/spell/Xtest.utf-8.add', &spellfile) + call assert_equal(['abc'], readfile('Xrtp/spell/Xtest.utf-8.add')) + call assert_true(filereadable('Xrtp/spell/Xtest.utf-8.spl')) + set spell& spelllang& spellfile& + call delete('Xrtp', 'rf') + let &encoding = save_encoding + let &rtp = save_rtp + %bw! +endfunc + +" Test for the 'mkspellmem' option +func Test_mkspellmem_opt() + call assert_fails('set mkspellmem=1000', 'E474:') + call assert_fails('set mkspellmem=1000,', 'E474:') + call assert_fails('set mkspellmem=1000,50', 'E474:') + call assert_fails('set mkspellmem=1000,50,', 'E474:') + call assert_fails('set mkspellmem=1000,50,10,', 'E474:') + call assert_fails('set mkspellmem=1000,50,0', 'E474:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_vim9_cmd.vim b/src/testdir/test_vim9_cmd.vim index 9f4231a9a3..9372d90952 100644 --- a/src/testdir/test_vim9_cmd.vim +++ b/src/testdir/test_vim9_cmd.vim @@ -295,5 +295,25 @@ def Test_map_command() CheckScriptSuccess(['vim9script'] + lines) enddef +def Test_normal_command() + new + setline(1, 'doesnotexist') + let caught = 0 + try + exe "norm! \" + catch /E433/ + caught = 2 + endtry + assert_equal(2, caught) + + try + exe "norm! 3\" + catch /E433/ + caught = 3 + endtry + assert_equal(3, caught) + bwipe! +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 4ff0fd7e25..a7d8d87f3c 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -43,6 +43,9 @@ def Test_expr1() var = 0 assert_equal('two', var ? 'one' : 'two') + # with constant condition expression is not evaluated + assert_equal('one', 1 ? 'one' : xxx) + let Some: func = function('len') let Other: func = function('winnr') let Res: func = g:atrue ? Some : Other @@ -139,7 +142,6 @@ enddef func Test_expr1_fails() call CheckDefFailure(["let x = 1 ? 'one'"], "Missing ':' after '?'", 1) - call CheckDefFailure(["let x = 1 ? 'one' : xxx"], "E1001:", 1) let msg = "white space required before and after '?'" call CheckDefFailure(["let x = 1? 'one' : 'two'"], msg, 1) @@ -1668,6 +1670,17 @@ def Test_expr7_lambda_vim9script() CheckScriptSuccess(lines) enddef +def Test_epxr7_funcref() + let lines =<< trim END + def RetNumber(): number + return 123 + enddef + let FuncRef = RetNumber + assert_equal(123, FuncRef()) + END + CheckDefAndScriptSuccess(lines) +enddef + def Test_expr7_dict() # dictionary assert_equal(g:dict_empty, {}) diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim index 5a251473cd..054fe4cbfc 100644 --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -15,6 +15,18 @@ def Test_range_only() setline(1, ['blah', 'Blah']) :/Blah/ assert_equal(2, getcurpos()[1]) + bwipe! + + # without range commands use current line + new + setline(1, ['one', 'two', 'three']) + :2 + print + assert_equal('two', Screenline(&lines)) + :3 + list + assert_equal('three$', Screenline(&lines)) + bwipe! enddef let s:appendToMe = 'xxx' @@ -110,12 +122,21 @@ def Test_assignment() endif lines =<< trim END - vim9script &ts = 6 &ts += 3 assert_equal(9, &ts) + + &l:ts = 6 + assert_equal(6, &ts) + &l:ts += 2 + assert_equal(8, &ts) + + &g:ts = 6 + assert_equal(6, &g:ts) + &g:ts += 2 + assert_equal(8, &g:ts) END - CheckScriptSuccess(lines) + CheckDefAndScriptSuccess(lines) CheckDefFailure(['¬ex += 3'], 'E113:') CheckDefFailure(['&ts ..= "xxx"'], 'E1019:') @@ -163,19 +184,15 @@ def Test_assignment() call CheckDefFailure(['$SOME_ENV_VAR += "more"'], 'E1051:') call CheckDefFailure(['$SOME_ENV_VAR += 123'], 'E1012:') - @a = 'areg' - @a ..= 'add' - assert_equal('aregadd', @a) - call CheckDefFailure(['@a += "more"'], 'E1051:') - call CheckDefFailure(['@a += 123'], 'E1012:') - lines =<< trim END - vim9script @c = 'areg' @c ..= 'add' assert_equal('aregadd', @c) END - call CheckScriptSuccess(lines) + CheckDefAndScriptSuccess(lines) + + call CheckDefFailure(['@a += "more"'], 'E1051:') + call CheckDefFailure(['@a += 123'], 'E1012:') v:errmsg = 'none' v:errmsg ..= 'again' @@ -608,6 +625,13 @@ def Test_unlet() assert_false(exists('g:somevar')) unlet! g:somevar + # also works for script-local variable in legacy Vim script + s:somevar = 'legacy' + assert_true(exists('s:somevar')) + unlet s:somevar + assert_false(exists('s:somevar')) + unlet! s:somevar + call CheckScriptFailure([ 'vim9script', 'let svar = 123', @@ -1660,8 +1684,9 @@ def Test_vim9script_funcref() delete('Xscript.vim') enddef -" Check that when searcing for "FilterFunc" it doesn't find the import in the -" script where FastFilter() is called from. +" Check that when searching for "FilterFunc" it finds the import in the +" script where FastFilter() is called from, both as a string and as a direct +" function reference. def Test_vim9script_funcref_other_script() let filterLines =<< trim END vim9script @@ -1671,22 +1696,26 @@ def Test_vim9script_funcref_other_script() export def FastFilter(): list return range(10)->filter('FilterFunc') enddef + export def FastFilterDirect(): list + return range(10)->filter(FilterFunc) + enddef END writefile(filterLines, 'Xfilter.vim') let lines =<< trim END vim9script - import {FilterFunc, FastFilter} from './Xfilter.vim' + import {FilterFunc, FastFilter, FastFilterDirect} from './Xfilter.vim' def Test() let x: list = FastFilter() enddef Test() + def TestDirect() + let x: list = FastFilterDirect() + enddef + TestDirect() END - writefile(lines, 'Ximport.vim') - assert_fails('source Ximport.vim', 'E121:') - + CheckScriptSuccess(lines) delete('Xfilter.vim') - delete('Ximport.vim') enddef def Test_vim9script_reload_delfunc() @@ -2093,6 +2122,14 @@ def Test_if_const_expr() res = true endif assert_equal(false, res) + + # with constant "false" expression may be invalid so long as the syntax is OK + if false | eval 0 | endif + if false | eval burp + 234 | endif + if false | echo burp 234 'asd' | endif + if false + burp + endif enddef def Test_if_const_expr_fails() @@ -2152,8 +2189,9 @@ def Test_execute_cmd() echomsg [1, 2, 3] #{a: 1, b: 2} assert_match('^\[1, 2, 3\] {''a'': 1, ''b'': 2}$', Screenline(&lines)) - call CheckDefFailure(['execute xxx'], 'E1001:') - call CheckDefFailure(['execute "cmd"# comment'], 'E488:') + call CheckDefFailure(['execute xxx'], 'E1001:', 1) + call CheckDefExecFailure(['execute "tabnext " .. 8'], 'E475:', 1) + call CheckDefFailure(['execute "cmd"# comment'], 'E488:', 1) enddef def Test_execute_cmd_vimscript() diff --git a/src/testdir/vim9.vim b/src/testdir/vim9.vim index 2f92cf954e..7fbe4a5ed3 100644 --- a/src/testdir/vim9.vim +++ b/src/testdir/vim9.vim @@ -41,6 +41,11 @@ def CheckScriptSuccess(lines: list) delete('Xdef') enddef +def CheckDefAndScriptSuccess(lines: list) + CheckDefSuccess(lines) + CheckScriptSuccess(['vim9script'] + lines) +enddef + " Check that a command fails both when used in a :def function and when used " in Vim9 script. def CheckScriptAndDefFailure(lines: list, error: string, lnum = -3) diff --git a/src/ui.c b/src/ui.c index 68c59c6eaf..6efb1a2f33 100644 --- a/src/ui.c +++ b/src/ui.c @@ -548,7 +548,7 @@ ui_delay(long msec_arg, int ignoreinput) if (gui.in_use) gui_macvim_force_flush(); #endif - mch_delay(msec, ignoreinput); + mch_delay(msec, ignoreinput ? MCH_DELAY_IGNOREINPUT : 0); } } diff --git a/src/version.c b/src/version.c index c7206faef4..918b6f6554 100644 --- a/src/version.c +++ b/src/version.c @@ -769,6 +769,44 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1531, +/**/ + 1530, +/**/ + 1529, +/**/ + 1528, +/**/ + 1527, +/**/ + 1526, +/**/ + 1525, +/**/ + 1524, +/**/ + 1523, +/**/ + 1522, +/**/ + 1521, +/**/ + 1520, +/**/ + 1519, +/**/ + 1518, +/**/ + 1517, +/**/ + 1516, +/**/ + 1515, +/**/ + 1514, +/**/ + 1513, /**/ 1512, /**/ diff --git a/src/vim.h b/src/vim.h index edccc9c1d4..e260673552 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2679,4 +2679,8 @@ long elapsed(DWORD start_tick); #define READDIR_SORT_IC 2 // sort ignoring case (strcasecmp) #define READDIR_SORT_COLLATE 3 // sort according to collation (strcoll) +// Flags for mch_delay. +#define MCH_DELAY_IGNOREINPUT 1 +#define MCH_DELAY_SETTMODE 2 + #endif // VIM__H diff --git a/src/vim9compile.c b/src/vim9compile.c index e4b7d44adb..5e4bfdb76c 100644 --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -258,6 +258,15 @@ lookup_arg( return FAIL; } +/* + * Returnd TRUE if the script context is Vim9 script. + */ + static int +script_is_vim9() +{ + return SCRIPT_ITEM(current_sctx.sc_sid)->sn_version == SCRIPT_VERSION_VIM9; +} + /* * Lookup a variable in the current script. * If "vim9script" is TRUE the script must be Vim9 script. Used for "var" @@ -271,8 +280,7 @@ lookup_script(char_u *name, size_t len, int vim9script) hashtab_T *ht = &SCRIPT_VARS(current_sctx.sc_sid); dictitem_T *di; - if (vim9script && SCRIPT_ITEM(current_sctx.sc_sid)->sn_version - != SCRIPT_VERSION_VIM9) + if (vim9script && !script_is_vim9()) return FAIL; cc = name[len]; name[len] = NUL; @@ -4006,6 +4014,13 @@ compile_expr1(char_u **arg, cctx_T *cctx, ppconst_T *ppconst) int ppconst_used = ppconst->pp_used; char_u *next; + // Ignore all kinds of errors when not producing code. + if (cctx->ctx_skip == SKIP_YES) + { + skip_expr(arg); + return OK; + } + // Evaluate the first expression. if (compile_expr2(arg, cctx, ppconst) == FAIL) return FAIL; @@ -4256,7 +4271,7 @@ compile_nested_function(exarg_T *eap, cctx_T *cctx) ufunc = def_function(eap, lambda_name); if (ufunc == NULL) - return NULL; + return eap->skip ? (char_u *)"" : NULL; if (ufunc->uf_def_status == UF_TO_BE_COMPILED && compile_def_function(ufunc, TRUE, cctx) == FAIL) return NULL; @@ -4542,8 +4557,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) p = var_start + 2; else { - p = (*var_start == '&' || *var_start == '$') - ? var_start + 1 : var_start; + // skip over the leading "&", "&l:", "&g:" and "$" + p = skip_option_env_lead(var_start); p = to_name_end(p, TRUE); } @@ -4587,8 +4602,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) } cc = *p; *p = NUL; - opt_type = get_option_value(var_start + 1, &numval, - NULL, opt_flags); + opt_type = get_option_value(skip_option_env_lead(var_start), + &numval, NULL, opt_flags); *p = cc; if (opt_type == -3) { @@ -5123,7 +5138,8 @@ compile_assignment(char_u *arg, exarg_T *eap, cmdidx_T cmdidx, cctx_T *cctx) switch (dest) { case dest_option: - generate_STOREOPT(cctx, name + 1, opt_flags); + generate_STOREOPT(cctx, skip_option_env_lead(name), + opt_flags); break; case dest_global: // include g: with the name, easier to execute that way @@ -5234,6 +5250,9 @@ check_vim9_unlet(char_u *name) { if (name[1] != ':' || vim_strchr((char_u *)"gwtb", *name) == NULL) { + // "unlet s:var" is allowed in legacy script. + if (*name == 's' && !script_is_vim9()) + return OK; semsg(_(e_cannot_unlet_str), name); return FAIL; } @@ -5500,6 +5519,7 @@ compile_elseif(char_u *arg, cctx_T *cctx) isn_T *isn; scope_T *scope = cctx->ctx_scope; ppconst_T ppconst; + skip_T save_skip = cctx->ctx_skip; if (scope == NULL || scope->se_type != IF_SCOPE) { @@ -5522,11 +5542,14 @@ compile_elseif(char_u *arg, cctx_T *cctx) // compile "expr"; if we know it evaluates to FALSE skip the block CLEAR_FIELD(ppconst); + if (cctx->ctx_skip == SKIP_YES) + cctx->ctx_skip = SKIP_UNKNOWN; if (compile_expr1(&p, cctx, &ppconst) == FAIL) { clear_ppconst(&ppconst); return NULL; } + cctx->ctx_skip = save_skip; if (scope->se_skip_save == SKIP_YES) clear_ppconst(&ppconst); else if (instr->ga_len == instr_count && ppconst.pp_used == 1) @@ -6712,17 +6735,8 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx) p = skipwhite(p); - if (cctx.ctx_skip == SKIP_YES - && ea.cmdidx != CMD_if + if (cctx.ctx_had_return && ea.cmdidx != CMD_elseif - && ea.cmdidx != CMD_else - && ea.cmdidx != CMD_endif) - { - line = (char_u *)""; - continue; - } - - if (ea.cmdidx != CMD_elseif && ea.cmdidx != CMD_else && ea.cmdidx != CMD_endif && ea.cmdidx != CMD_endfor @@ -6731,11 +6745,8 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx) && ea.cmdidx != CMD_finally && ea.cmdidx != CMD_endtry) { - if (cctx.ctx_had_return) - { - emsg(_(e_unreachable_code_after_return)); - goto erret; - } + emsg(_(e_unreachable_code_after_return)); + goto erret; } switch (ea.cmdidx) @@ -6833,7 +6844,7 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx) if (compile_expr0(&p, &cctx) == FAIL) goto erret; - // drop the return value + // drop the result generate_instr_drop(&cctx, ISN_DROP, 1); line = skipwhite(p); @@ -6847,7 +6858,7 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx) line = compile_mult_expr(p, ea.cmdidx, &cctx); break; - // TODO: other commands with an expression argument + // TODO: any other commands with an expression argument? case CMD_append: case CMD_change: @@ -6858,13 +6869,27 @@ compile_def_function(ufunc_T *ufunc, int set_return_type, cctx_T *outer_cctx) goto erret; case CMD_SIZE: - semsg(_(e_invalid_command_str), ea.cmd); - goto erret; + if (cctx.ctx_skip != SKIP_YES) + { + semsg(_(e_invalid_command_str), ea.cmd); + goto erret; + } + // We don't check for a next command here. + line = (char_u *)""; + break; default: - // Not recognized, execute with do_cmdline_cmd(). - ea.arg = p; - line = compile_exec(line, &ea, &cctx); + if (cctx.ctx_skip == SKIP_YES) + { + // We don't check for a next command here. + line = (char_u *)""; + } + else + { + // Not recognized, execute with do_cmdline_cmd(). + ea.arg = p; + line = compile_exec(line, &ea, &cctx); + } break; } nextline: diff --git a/src/vim9execute.c b/src/vim9execute.c index a49f3d7c6f..53a748e971 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1062,7 +1062,10 @@ call_def_function( if (ga.ga_data != NULL) { if (iptr->isn_type == ISN_EXECUTE) + { + SOURCING_LNUM = iptr->isn_lnum; do_cmdline_cmd((char_u *)ga.ga_data); + } else { msg_sb_eol();