diff options
31 files changed, 610 insertions, 149 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 7ab06c3820..9a04649bfa 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -37,7 +37,12 @@ endfunction " Handler for s:system() function. function! s:system_handler(jobid, data, event) dict abort - if a:event ==# 'stdout' || a:event ==# 'stderr' + if a:event ==# 'stderr' + let self.stderr .= join(a:data, '') + if !self.ignore_stderr + let self.output .= join(a:data, '') + endif + elseif a:event ==# 'stdout' let self.output .= join(a:data, '') elseif a:event ==# 'exit' let s:shell_error = a:data @@ -57,16 +62,15 @@ endfunction " Run a system command and timeout after 30 seconds. function! s:system(cmd, ...) abort let stdin = a:0 ? a:1 : '' - let ignore_stderr = a:0 > 1 ? a:2 : 0 let ignore_error = a:0 > 2 ? a:3 : 0 let opts = { + \ 'ignore_stderr': a:0 > 1 ? a:2 : 0, \ 'output': '', + \ 'stderr': '', \ 'on_stdout': function('s:system_handler'), + \ 'on_stderr': function('s:system_handler'), \ 'on_exit': function('s:system_handler'), \ } - if !ignore_stderr - let opts.on_stderr = function('s:system_handler') - endif let jobid = jobstart(a:cmd, opts) if jobid < 1 @@ -85,8 +89,8 @@ function! s:system(cmd, ...) abort call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) call jobstop(jobid) elseif s:shell_error != 0 && !ignore_error - call health#report_error(printf("Command error (job=%d): `%s` (in %s)\nOutput: %s", - \ jobid, s:shellify(a:cmd), string(getcwd()), opts.output)) + call health#report_error(printf("Command error (job=%d, exit code %d): `%s` (in %s)\nOutput: %s\nStderr: %s", + \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd()), opts.output, opts.stderr)) endif return opts.output @@ -291,20 +295,14 @@ function! s:check_python(version) abort if empty(pyname) call health#report_warn('No Python interpreter was found with the neovim ' \ . 'module. Using the first available for diagnostics.') - endif - - if !empty(pyname) - if exists('g:'.host_prog_var) - let python_bin = exepath(pyname) - endif - let pyname = fnamemodify(pyname, ':t') + elseif exists('g:'.host_prog_var) + let python_bin = pyname endif if !empty(pythonx_errs) call health#report_error('Python provider error', pythonx_errs) - endif - if !empty(pyname) && empty(python_bin) && empty(pythonx_errs) + elseif !empty(pyname) && empty(python_bin) if !exists('g:'.host_prog_var) call health#report_info(printf('`g:%s` is not set. Searching for ' \ . '%s in the environment.', host_prog_var, pyname)) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index dce531b663..7b5fc191e9 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2990,11 +2990,16 @@ cosh({expr}) *cosh()* count({comp}, {expr} [, {ic} [, {start}]]) *count()* Return the number of times an item with value {expr} appears - in |List| or |Dictionary| {comp}. + in |String|, |List| or |Dictionary| {comp}. + If {start} is given then start with the item with this index. {start} can only be used with a |List|. + When {ic} is given and it's |TRUE| then case is ignored. + When {comp} is a string then the number of not overlapping + occurences of {expr} is returned. + *cscope_connection()* cscope_connection([{num} , {dbpath} [, {prepend}]]) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 4152c16588..a8d29a3a53 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -4420,7 +4420,7 @@ do_arg_all ( if (i < alist->al_ga.ga_len && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum || path_full_compare(alist_name(&AARGLIST(alist)[i]), - buf->b_ffname, TRUE) & kEqualFiles)) { + buf->b_ffname, true) & kEqualFiles)) { int weight = 1; if (old_curtab == curtab) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 36662b2a39..3843245070 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7592,9 +7592,38 @@ static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long n = 0; - int ic = FALSE; + int ic = 0; + bool error = false; - if (argvars[0].v_type == VAR_LIST) { + if (argvars[2].v_type != VAR_UNKNOWN) { + ic = tv_get_number_chk(&argvars[2], &error); + } + + if (argvars[0].v_type == VAR_STRING) { + const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); + const char_u *p = argvars[0].vval.v_string; + + if (!error && expr != NULL && p != NULL) { + if (ic) { + const size_t len = STRLEN(expr); + + while (*p != NUL) { + if (mb_strnicmp(p, expr, len) == 0) { + n++; + p += len; + } else { + MB_PTR_ADV(p); + } + } + } else { + char_u *next; + while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { + n++; + p = next + STRLEN(expr); + } + } + } + } else if (argvars[0].v_type == VAR_LIST) { listitem_T *li; list_T *l; long idx; @@ -7602,9 +7631,6 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) if ((l = argvars[0].vval.v_list) != NULL) { li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; - - ic = tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { idx = tv_get_number_chk(&argvars[3], &error); if (!error) { @@ -7630,10 +7656,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) hashitem_T *hi; if ((d = argvars[0].vval.v_dict) != NULL) { - bool error = false; - if (argvars[2].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { EMSG(_(e_invarg)); } @@ -7649,8 +7672,9 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - } else + } else { EMSG2(_(e_listdictarg), "count()"); + } rettv->vval.v_number = n; } @@ -16377,9 +16401,12 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) return list; } +// os_system wrapper. Handles 'verbose', :profile, and v:shell_error. static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist) { + proftime_T wait_time; + rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -16406,11 +16433,29 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, return; // Already did emsg. } + if (p_verbose > 3) { + char buf[NUMBUFLEN]; + const char * cmd = tv_get_string_buf(argvars, buf); + + verbose_enter_scroll(); + smsg(_("Calling shell to execute: \"%s\""), cmd); + msg_puts("\n\n"); + verbose_leave_scroll(); + } + + if (do_profiling == PROF_YES) { + prof_child_enter(&wait_time); + } + // execute the command size_t nread = 0; char *res = NULL; int status = os_system(argv, input, input_len, &res, &nread); + if (do_profiling == PROF_YES) { + prof_child_exit(&wait_time); + } + xfree(input); set_vim_var_nr(VV_SHELL_ERROR, (long) status); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 3c6a8b1074..176df58fb9 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4977,7 +4977,8 @@ void fix_help_buffer(void) while (*p != NUL) { copy_option_part(&p, NameBuff, MAXPATHL, ","); rt = (char_u *)vim_getenv("VIMRUNTIME"); - if (path_full_compare(rt, NameBuff, FALSE) != kEqualFiles) { + if (rt != NULL + && path_full_compare(rt, NameBuff, false) != kEqualFiles) { int fcount; char_u **fnames; FILE *fd; @@ -5197,8 +5198,9 @@ static void helptags_one(char_u *dir, char_u *ext, char_u *tagfname, * add the "help-tags" tag. */ ga_init(&ga, (int)sizeof(char_u *), 100); - if (add_help_tags || path_full_compare((char_u *)"$VIMRUNTIME/doc", - dir, FALSE) == kEqualFiles) { + if (add_help_tags + || path_full_compare((char_u *)"$VIMRUNTIME/doc", + dir, false) == kEqualFiles) { s = xmalloc(18 + STRLEN(tagfname)); sprintf((char *)s, "help-tags\t%s\t1\n", tagfname); GA_APPEND(char_u *, &ga, s); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 775d002e58..fd11acff84 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5062,38 +5062,33 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func, */ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file) { - char_u *retstr; - char_u *s; char_u *e; - char_u keep; garray_T ga; - retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, - num_file, file); + char_u *const retstr = call_user_expand_func( + (user_expand_func_T)call_func_retstr, xp, num_file, file); if (retstr == NULL) { return FAIL; } ga_init(&ga, (int)sizeof(char *), 3); - for (s = retstr; *s != NUL; s = e) { + for (char_u *s = retstr; *s != NUL; s = e) { e = vim_strchr(s, '\n'); if (e == NULL) e = s + STRLEN(s); - keep = *e; - *e = 0; + const int keep = *e; + *e = NUL; - if (xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0) { - *e = keep; - if (*e != NUL) - ++e; - continue; + const bool skip = xp->xp_pattern[0] + && vim_regexec(regmatch, s, (colnr_T)0) == 0; + *e = keep; + if (!skip) { + GA_APPEND(char_u *, &ga, vim_strnsave(s, (int)(e - s))); } - GA_APPEND(char_u *, &ga, vim_strnsave(s, (int)(e - s))); - - *e = keep; - if (*e != NUL) - ++e; + if (*e != NUL) { + e++; + } } xfree(retstr); *file = ga.ga_data; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 5b17b58781..ee775bab4a 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -577,7 +577,7 @@ char_u *vim_findfile(void *search_ctx_arg) char_u *file_path; char_u *rest_of_wildcards; char_u *path_end = NULL; - ff_stack_T *stackp; + ff_stack_T *stackp = NULL; size_t len; char_u *p; char_u *suf; @@ -683,28 +683,40 @@ char_u *vim_findfile(void *search_ctx_arg) dirptrs[0] = file_path; dirptrs[1] = NULL; - /* if we have a start dir copy it in */ + // if we have a start dir copy it in if (!vim_isAbsName(stackp->ffs_fix_path) && search_ctx->ffsc_start_dir) { + if (STRLEN(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) { + goto fail; + } STRCPY(file_path, search_ctx->ffsc_start_dir); - add_pathsep((char *)file_path); + if (!add_pathsep((char *)file_path)) { + goto fail; + } } - /* append the fix part of the search path */ + // append the fix part of the search path + if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 >= MAXPATHL) { + goto fail; + } STRCAT(file_path, stackp->ffs_fix_path); - add_pathsep((char *)file_path); + if (!add_pathsep((char *)file_path)) { + goto fail; + } rest_of_wildcards = stackp->ffs_wc_path; if (*rest_of_wildcards != NUL) { len = STRLEN(file_path); if (STRNCMP(rest_of_wildcards, "**", 2) == 0) { - /* pointer to the restrict byte - * The restrict byte is not a character! - */ + // pointer to the restrict byte + // The restrict byte is not a character! p = rest_of_wildcards + 2; if (*p > 0) { (*p)--; + if (len + 1 >= MAXPATHL) { + goto fail; + } file_path[len++] = '*'; } @@ -729,8 +741,12 @@ char_u *vim_findfile(void *search_ctx_arg) * on the stack again for further search. */ while (*rest_of_wildcards - && !vim_ispathsep(*rest_of_wildcards)) + && !vim_ispathsep(*rest_of_wildcards)) { + if (len + 1 >= MAXPATHL) { + goto fail; + } file_path[len++] = *rest_of_wildcards++; + } file_path[len] = NUL; if (vim_ispathsep(*rest_of_wildcards)) @@ -773,10 +789,15 @@ char_u *vim_findfile(void *search_ctx_arg) && !os_isdir(stackp->ffs_filearray[i])) continue; /* not a directory */ - /* prepare the filename to be checked for existence - * below */ + // prepare the filename to be checked for existence below + if (STRLEN(stackp->ffs_filearray[i]) + 1 + + STRLEN(search_ctx->ffsc_file_to_search) >= MAXPATHL) { + goto fail; + } STRCPY(file_path, stackp->ffs_filearray[i]); - add_pathsep((char *)file_path); + if (!add_pathsep((char *)file_path)) { + goto fail; + } STRCAT(file_path, search_ctx->ffsc_file_to_search); /* @@ -924,8 +945,14 @@ char_u *vim_findfile(void *search_ctx_arg) if (*search_ctx->ffsc_start_dir == 0) break; + if (STRLEN(search_ctx->ffsc_start_dir) + 1 + + STRLEN(search_ctx->ffsc_fix_path) >= MAXPATHL) { + goto fail; + } STRCPY(file_path, search_ctx->ffsc_start_dir); - add_pathsep((char *)file_path); + if (!add_pathsep((char *)file_path)) { + goto fail; + } STRCAT(file_path, search_ctx->ffsc_fix_path); /* create a new stack entry */ @@ -936,6 +963,8 @@ char_u *vim_findfile(void *search_ctx_arg) break; } +fail: + ff_free_stack_element(stackp); xfree(file_path); return NULL; } @@ -1192,14 +1221,19 @@ static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx) /* * free the given stack element */ -static void ff_free_stack_element(ff_stack_T *stack_ptr) +static void ff_free_stack_element(ff_stack_T *const stack_ptr) { - /* free handles possible NULL pointers */ + if (stack_ptr == NULL) { + return; + } + + // free handles possible NULL pointers xfree(stack_ptr->ffs_fix_path); xfree(stack_ptr->ffs_wc_path); - if (stack_ptr->ffs_filearray != NULL) + if (stack_ptr->ffs_filearray != NULL) { FreeWild(stack_ptr->ffs_filearray_size, stack_ptr->ffs_filearray); + } xfree(stack_ptr); } diff --git a/src/nvim/log.c b/src/nvim/log.c index 7bfe5c4089..6de231858e 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -7,6 +7,9 @@ #include <stdbool.h> #include <stdint.h> #include <stdio.h> +#if !defined(WIN32) +# include <sys/time.h> // for gettimeofday() +#endif #include <uv.h> #include "nvim/log.h" @@ -195,7 +198,7 @@ void log_callstack_to_file(FILE *log_file, const char *const func_name, } assert(24 + exepathlen < IOSIZE); // Must fit in `cmdbuf` below. - char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace))]; + char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace)) + MAXPATHL]; snprintf(cmdbuf, sizeof(cmdbuf), "addr2line -e %s -f -p", exepath); for (int i = 1; i < trace_size; i++) { char buf[20]; // 64-bit pointer 0xNNNNNNNNNNNNNNNN with leading space. @@ -260,25 +263,33 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, }; assert(log_level >= DEBUG_LOG_LEVEL && log_level <= ERROR_LOG_LEVEL); - // format current timestamp in local time + // Format the timestamp. struct tm local_time; - if (os_get_localtime(&local_time) == NULL) { + if (os_localtime(&local_time) == NULL) { return false; } char date_time[20]; - if (strftime(date_time, sizeof(date_time), "%Y/%m/%d %H:%M:%S", + if (strftime(date_time, sizeof(date_time), "%Y-%m-%dT%H:%M:%S", &local_time) == 0) { return false; } - // print the log message prefixed by the current timestamp and pid + int millis = 0; +#if !defined(WIN32) + struct timeval curtime; + if (gettimeofday(&curtime, NULL) == 0) { + millis = (int)curtime.tv_usec / 1000; + } +#endif + + // Print the log message. int64_t pid = os_get_pid(); int rv = (line_num == -1 || func_name == NULL) - ? fprintf(log_file, "%s %s %" PRId64 " %s", date_time, - log_levels[log_level], pid, + ? fprintf(log_file, "%s %s.%03d %-5" PRId64 " %s", + log_levels[log_level], date_time, millis, pid, (context == NULL ? "?:" : context)) - : fprintf(log_file, "%s %s %" PRId64 " %s%s:%d: ", date_time, - log_levels[log_level], pid, + : fprintf(log_file, "%s %s.%03d %-5" PRId64 " %s%s:%d: ", + log_levels[log_level], date_time, millis, pid, (context == NULL ? "" : context), func_name, line_num); if (rv < 0) { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index fc9a1e8271..84ceaf0973 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1365,11 +1365,11 @@ recover_names ( */ if (curbuf->b_ml.ml_mfp != NULL && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) { - for (int i = 0; i < num_files; ++i) - if (path_full_compare(p, files[i], TRUE) & kEqualFiles) { - /* Remove the name from files[i]. Move further entries - * down. When the array becomes empty free it here, since - * FreeWild() won't be called below. */ + for (int i = 0; i < num_files; i++) { + if (path_full_compare(p, files[i], true) & kEqualFiles) { + // Remove the name from files[i]. Move further entries + // down. When the array becomes empty free it here, since + // FreeWild() won't be called below. xfree(files[i]); if (--num_files == 0) xfree(files); @@ -1377,6 +1377,7 @@ recover_names ( for (; i < num_files; ++i) files[i] = files[i + 1]; } + } } if (nr > 0) { file_count += num_files; diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 0df857352b..6997156d4c 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -196,16 +196,19 @@ void init_homedir(void) const char *homedrive = os_getenv("HOMEDRIVE"); const char *homepath = os_getenv("HOMEPATH"); if (homepath == NULL) { - homepath = "\\"; + homepath = "\\"; } - if (homedrive != NULL && strlen(homedrive) + strlen(homepath) < MAXPATHL) { + if (homedrive != NULL + && strlen(homedrive) + strlen(homepath) < MAXPATHL) { snprintf(os_buf, MAXPATHL, "%s%s", homedrive, homepath); if (os_buf[0] != NUL) { var = os_buf; - vim_setenv("HOME", os_buf); } } } + if (var == NULL) { + var = os_getenv("USERPROFILE"); + } #endif if (var != NULL) { @@ -608,6 +611,12 @@ char *vim_getenv(const char *name) return xstrdup(kos_env_path); } +#ifdef WIN32 + if (strcmp(name, "HOME") == 0) { + return xstrdup(homedir); + } +#endif + bool vimruntime = (strcmp(name, "VIMRUNTIME") == 0); if (!vimruntime && strcmp(name, "VIM") != 0) { return NULL; @@ -758,7 +767,12 @@ size_t home_replace(const buf_T *const buf, const char_u *src, dirlen = strlen(homedir); } - const char *const homedir_env = os_getenv("HOME"); + const char *homedir_env = os_getenv("HOME"); +#ifdef WIN32 + if (homedir_env == NULL) { + homedir_env = os_getenv("USERPROFILE"); + } +#endif char *homedir_env_mod = (char *)homedir_env; bool must_free = false; diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 290d421acc..31ef1a0cd6 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -114,12 +114,12 @@ struct tm *os_localtime_r(const time_t *restrict clock, #endif } -/// Obtains the current Unix timestamp and adjusts it to local time. +/// Gets the current Unix timestamp and adjusts it to local time. /// /// @param result Pointer to a 'struct tm' where the result should be placed /// @return A pointer to a 'struct tm' in the current time zone (the 'result' /// argument) or NULL in case of error -struct tm *os_get_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL +struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL { time_t rawtime = time(NULL); return os_localtime_r(&rawtime, result); diff --git a/src/nvim/path.c b/src/nvim/path.c index d5c636ff08..0b90329686 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -52,7 +52,8 @@ /// @param s2 Second file name. /// @param checkname When both files don't exist, only compare their names. /// @return Enum of type FileComparison. @see FileComparison. -FileComparison path_full_compare(char_u *s1, char_u *s2, int checkname) +FileComparison path_full_compare(char_u *const s1, char_u *const s2, + const bool checkname) { assert(s1 && s2); char_u exp1[MAXPATHL]; diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim index b323174550..eae27ef74d 100644 --- a/src/nvim/po/check.vim +++ b/src/nvim/po/check.vim @@ -38,6 +38,7 @@ let s:save_wrapscan = &wrapscan set nowrapscan " Start at the first "msgid" line. +let wsv = winsaveview() 1 /^msgid\> @@ -113,9 +114,96 @@ if search('msgid "\("\n"\)\?\([EW][0-9]\+:\).*\nmsgstr "\("\n"\)\?[^"]\@=\2\@!') endif endif +func! CountNl(first, last) + let nl = 0 + for lnum in range(a:first, a:last) + let nl += count(getline(lnum), "\n") + endfor + return nl +endfunc + +" Check that the \n at the end of the msgid line is also present in the msgstr +" line. Skip over the header. +1 +/^"MIME-Version: +while 1 + let lnum = search('^msgid\>') + if lnum <= 0 + break + endif + let strlnum = search('^msgstr\>') + let end = search('^$') + if end <= 0 + let end = line('$') + 1 + endif + let origcount = CountNl(lnum, strlnum - 1) + let transcount = CountNl(strlnum, end - 1) + " Allow for a few more or less line breaks when there are 2 or more + if origcount != transcount && (origcount <= 2 || transcount <= 2) + echomsg 'Mismatching "\n" in line ' . line('.') + if error == 0 + let error = lnum + endif + endif +endwhile + +" Check that the file is well formed according to msgfmts understanding +if executable("msgfmt") + let filename = expand("%") + let a = system("msgfmt --statistics OLD_PO_FILE_INPUT=yes " . filename) + if v:shell_error != 0 + let error = matchstr(a, filename.':\zs\d\+\ze:')+0 + for line in split(a, '\n') | echomsg line | endfor + endif +endif + +" Check that the plural form is properly initialized +1 +let plural = search('^msgid_plural ', 'n') +if (plural && search('^"Plural-Forms: ', 'n') == 0) || (plural && search('^msgstr\[0\] ".\+"', 'n') != plural + 1) + if search('^"Plural-Forms: ', 'n') == 0 + echomsg "Missing Plural header" + if error == 0 + let error = search('\(^"[A-Za-z-_]\+: .*\\n"\n\)\+\zs', 'n') - 1 + endif + elseif error == 0 + let error = plural + endif +elseif !plural && search('^"Plural-Forms: ', 'n') + " We allow for a stray plural header, msginit adds one. +endif + +" Check that 8bit encoding is used instead of 8-bit +let cte = search('^"Content-Transfer-Encoding:\s\+8-bit', 'n') +let ctc = search('^"Content-Type:.*;\s\+\<charset=[iI][sS][oO]_', 'n') +let ctu = search('^"Content-Type:.*;\s\+\<charset=utf-8', 'n') +if cte + echomsg "Content-Transfer-Encoding should be 8bit instead of 8-bit" + " TODO: make this an error + " if error == 0 + " let error = cte + " endif +elseif ctc + echomsg "Content-Type charset should be 'ISO-...' instead of 'ISO_...'" + " TODO: make this an error + " if error == 0 + " let error = ct + " endif +elseif ctu + echomsg "Content-Type charset should be 'UTF-8' instead of 'utf-8'" + " TODO: make this an error + " if error == 0 + " let error = ct + " endif +endif + + if error == 0 + " If all was OK restore the view. + call winrestview(wsv) echomsg "OK" else + " Put the cursor on the line with the error. exe error endif diff --git a/src/nvim/po/cleanup.vim b/src/nvim/po/cleanup.vim index 24ae74ed38..b27d88092f 100644 --- a/src/nvim/po/cleanup.vim +++ b/src/nvim/po/cleanup.vim @@ -8,12 +8,18 @@ let s:was_diff = &diff setl nodiff -silent g/^#: /d +" untranslated message preceded by c-format or comment +silent g/^#, c-format\n#/.d +silent g/^#\..*\n#/.d + +silent g/^#[:~] /d silent g/^#, fuzzy\(, .*\)\=\nmsgid ""\@!/.+1,/^$/-1s/^/#\~ / silent g/^msgstr"/s//msgstr "/ silent g/^msgid"/s//msgid "/ silent g/^msgstr ""\(\n"\)\@!/?^msgid?,.s/^/#\~ / +silent g/^\n\n\n/.d + if s:was_diff setl diff endif diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 47760e1e70..664dd3e968 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3603,15 +3603,16 @@ void ex_vimgrep(exarg_T *eap) goto theend; } - if (s != NULL && *s == NUL) { - /* Pattern is empty, use last search pattern. */ + if (s == NULL || *s == NUL) { + // Pattern is empty, use last search pattern. if (last_search_pat() == NULL) { EMSG(_(e_noprevre)); goto theend; } regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); - } else + } else { regmatch.regprog = vim_regcomp(s, RE_MAGIC); + } if (regmatch.regprog == NULL) goto theend; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index e8dbc11710..9f0d8a5080 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -507,6 +507,7 @@ void update_single_line(win_T *wp, linenr_T lnum) init_search_hl(wp); start_search_hl(); prepare_search_hl(wp, lnum); + update_window_hl(wp, false); win_line(wp, lnum, row, row + wp->w_lines[j].wl_size, false); end_search_hl(); break; @@ -542,51 +543,57 @@ static void update_finish(void) updating_screen = FALSE; } -void update_debug_sign(buf_T *buf, linenr_T lnum) +void update_debug_sign(const buf_T *const buf, const linenr_T lnum) { - int doit = FALSE; - win_foldinfo.fi_level = 0; - - /* update/delete a specific mark */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (buf != NULL && lnum > 0) { - if (wp->w_buffer == buf && lnum >= wp->w_topline - && lnum < wp->w_botline) { - if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { - wp->w_redraw_top = lnum; - } - if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { - wp->w_redraw_bot = lnum; - } - redraw_win_later(wp, VALID); + bool doit = false; + win_foldinfo.fi_level = 0; + + // update/delete a specific mark + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (buf != NULL && lnum > 0) { + if (wp->w_buffer == buf && lnum >= wp->w_topline + && lnum < wp->w_botline) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > lnum) { + wp->w_redraw_top = lnum; + } + if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { + wp->w_redraw_bot = lnum; } - } else { redraw_win_later(wp, VALID); } - if (wp->w_redr_type != 0) { - doit = TRUE; - } + } else { + redraw_win_later(wp, VALID); } - - /* Return when there is nothing to do, screen updating is already - * happening (recursive call) or still starting up. */ - if (!doit || updating_screen || starting) { - return; + if (wp->w_redr_type != 0) { + doit = true; } + } - /* update all windows that need updating */ - update_prepare(); + // Return when there is nothing to do, screen updating is already + // happening (recursive call), messages on the screen or still starting up. + if (!doit + || updating_screen + || State == ASKMORE + || State == HITRETURN + || msg_scrolled + || starting) { + return; + } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type != 0) { - win_update(wp); - } - if (wp->w_redr_status) { - win_redr_status(wp); - } + // update all windows that need updating + update_prepare(); + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_redr_type != 0) { + update_window_hl(wp, wp->w_redr_type >= NOT_VALID); + win_update(wp); + } + if (wp->w_redr_status) { + win_redr_status(wp); } + } - update_finish(); + update_finish(); } /* diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 0714eb3137..cb03257878 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2043,9 +2043,11 @@ char_u *did_set_spelllang(win_T *wp) dont_use_region = true; // Check if we loaded this language before. - for (slang = first_lang; slang != NULL; slang = slang->sl_next) - if (path_full_compare(lang, slang->sl_fname, FALSE) == kEqualFiles) + for (slang = first_lang; slang != NULL; slang = slang->sl_next) { + if (path_full_compare(lang, slang->sl_fname, false) == kEqualFiles) { break; + } + } } else { filename = false; if (len > 3 && lang[len - 3] == '_') { @@ -2085,8 +2087,9 @@ char_u *did_set_spelllang(win_T *wp) } // Loop over the languages, there can be several files for "lang". - for (slang = first_lang; slang != NULL; slang = slang->sl_next) - if (filename ? path_full_compare(lang, slang->sl_fname, FALSE) == kEqualFiles + for (slang = first_lang; slang != NULL; slang = slang->sl_next) { + if (filename + ? path_full_compare(lang, slang->sl_fname, false) == kEqualFiles : STRICMP(lang, slang->sl_name) == 0) { region_mask = REGION_ALL; if (!filename && region != NULL) { @@ -2116,6 +2119,7 @@ char_u *did_set_spelllang(win_T *wp) nobreak = true; } } + } } // round 0: load int_wordlist, if possible. @@ -2137,17 +2141,21 @@ char_u *did_set_spelllang(win_T *wp) // If it was already found above then skip it. for (c = 0; c < ga.ga_len; ++c) { p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname; - if (p != NULL && path_full_compare(spf_name, p, FALSE) == kEqualFiles) + if (p != NULL + && path_full_compare(spf_name, p, false) == kEqualFiles) { break; + } } if (c < ga.ga_len) continue; } // Check if it was loaded already. - for (slang = first_lang; slang != NULL; slang = slang->sl_next) - if (path_full_compare(spf_name, slang->sl_fname, FALSE) == kEqualFiles) + for (slang = first_lang; slang != NULL; slang = slang->sl_next) { + if (path_full_compare(spf_name, slang->sl_fname, false) == kEqualFiles) { break; + } + } if (slang == NULL) { // Not loaded, try loading it now. The language name includes the // region name, the region is ignored otherwise. for int_wordlist diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 69fa95107e..6578c7d66c 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1786,7 +1786,7 @@ spell_reload_one ( bool didit = false; for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare(fname, slang->sl_fname, FALSE) == kEqualFiles) { + if (path_full_compare(fname, slang->sl_fname, false) == kEqualFiles) { slang_clear(slang); if (spell_load_file(fname, NULL, slang, false) == NULL) // reloading failed, clear the language @@ -4714,9 +4714,11 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // pointer-linked version of the trie. And it avoids having two versions // of the code for the soundfolding stuff. // It might have been done already by spell_reload_one(). - for (slang = first_lang; slang != NULL; slang = slang->sl_next) - if (path_full_compare(wfname, slang->sl_fname, FALSE) == kEqualFiles) + for (slang = first_lang; slang != NULL; slang = slang->sl_next) { + if (path_full_compare(wfname, slang->sl_fname, false) == kEqualFiles) { break; + } + } if (slang == NULL) { spell_message(spin, (char_u *)_("Reading back spell file...")); slang = spell_load_file(wfname, NULL, NULL, false); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index beff89d191..c09a13edb1 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2289,7 +2289,7 @@ static char_u *tag_full_fname(tagptrs_T *tagp) { int c = *tagp->fname_end; *tagp->fname_end = NUL; - char_u *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, FALSE); + char_u *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); *tagp->fname_end = c; return fullname; @@ -2363,8 +2363,8 @@ jumpto_tag ( * Expand file name, when needed (for environment variables). * If 'tagrelative' option set, may change file name. */ - fname = expand_tag_fname(fname, tagp.tag_fname, TRUE); - tofree_fname = fname; /* free() it later */ + fname = expand_tag_fname(fname, tagp.tag_fname, true); + tofree_fname = fname; // free() it later /* * Check if the file with the tag exists before abandoning the current @@ -2615,13 +2615,12 @@ erret: return retval; } -/* - * If "expand" is TRUE, expand wildcards in fname. - * If 'tagrelative' option set, change fname (name of file containing tag) - * according to tag_fname (name of tag file containing fname). - * Returns a pointer to allocated memory. - */ -static char_u *expand_tag_fname(char_u *fname, char_u *tag_fname, int expand) +// If "expand" is true, expand wildcards in fname. +// If 'tagrelative' option set, change fname (name of file containing tag) +// according to tag_fname (name of tag file containing fname). +// Returns a pointer to allocated memory. +static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, + const bool expand) { char_u *p; char_u *expanded_fname = NULL; @@ -2676,8 +2675,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, c = *fname_end; *fname_end = NUL; } - fullname = expand_tag_fname(fname, tag_fname, TRUE); - retval = (path_full_compare(fullname, buf_ffname, TRUE) & kEqualFiles); + fullname = expand_tag_fname(fname, tag_fname, true); + retval = (path_full_compare(fullname, buf_ffname, true) & kEqualFiles); xfree(fullname); *fname_end = c; } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 87d7ff5bad..0379235ec0 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -116,6 +116,7 @@ NEW_TESTS ?= \ test_visual.res \ test_winbuf_close.res \ test_window_id.res \ + test_windows_home.res \ test_wordcount.res \ test_writefile.res \ test_alot_latin.res \ diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index f2cd84f556..8014b76af7 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -173,6 +173,9 @@ func FinishTesting() " Don't write viminfo on exit. set viminfo= + " Clean up files created by setup.vim + call delete('XfakeHOME', 'rf') + if s:fail == 0 " Success, create the .res file so that make knows it's done. exe 'split ' . fnamemodify(g:testname, ':r') . '.res' diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index aac9fefef4..f3cc7da70f 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -1,5 +1,11 @@ " Common preparations for running tests. +" Only load this once. +if exists('s:did_load') + finish +endif +let s:did_load = 1 + " Align Nvim defaults to Vim. set sidescroll=0 set directory^=. @@ -16,7 +22,10 @@ set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after let &packpath = &rtp " Make sure $HOME does not get read or written. -let $HOME = '/does/not/exist' +let $HOME = expand(getcwd() . '/XfakeHOME') +if !isdirectory($HOME) + call mkdir($HOME) +endif " Use default shell on Windows to avoid segfault, caused by TUI if has('win32') diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 1a70ac152f..d026221dac 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,6 +2,7 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_cd.vim source test_changedtick.vim source test_cursor_func.vim source test_ex_undo.vim diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim new file mode 100644 index 0000000000..e573419bd0 --- /dev/null +++ b/src/nvim/testdir/test_cd.vim @@ -0,0 +1,13 @@ +" Test for :cd + +func Test_cd_large_path() + " This used to crash with a heap write overflow. + call assert_fails('cd ' . repeat('x', 5000), 'E472:') +endfunc + +func Test_cd_up_and_down() + let path = getcwd() + cd .. + exe 'cd ' . path + call assert_equal(path, getcwd()) +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index c59134908c..3b16f2ce9f 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -680,7 +680,13 @@ func Test_count() call assert_equal(0, count(d, 'c', 1)) call assert_fails('call count(d, "a", 0, 1)', 'E474:') - call assert_fails('call count("a", "a")', 'E712:') + + call assert_equal(0, count("foo", "bar")) + call assert_equal(1, count("foo", "oo")) + call assert_equal(2, count("foo", "o")) + call assert_equal(0, count("foo", "O")) + call assert_equal(2, count("foo", "O", 1)) + call assert_equal(2, count("fooooo", "oo")) endfunc func Test_changenr() diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim new file mode 100644 index 0000000000..bbcbf96050 --- /dev/null +++ b/src/nvim/testdir/test_windows_home.vim @@ -0,0 +1,121 @@ +" Test for $HOME on Windows. + +if !has('win32') + finish +endif + +let s:env = {} + +func s:restore_env() + for i in keys(s:env) + exe 'let ' . i . '=s:env["' . i . '"]' + endfor +endfunc + +func s:save_env(...) + for i in a:000 + exe 'let s:env["' . i . '"]=' . i + endfor +endfunc + +func s:unlet_env(...) + for i in a:000 + exe 'let ' . i . '=""' + endfor +endfunc + +func CheckHomeIsMissingFromSubprocessEnvironment() + silent! let out = system('set') + let env = filter(split(out, "\n"), 'v:val=~"^HOME="') + call assert_equal(0, len(env)) +endfunc + +func CheckHomeIsInSubprocessEnvironment(exp) + silent! let out = system('set') + let env = filter(split(out, "\n"), 'v:val=~"^HOME="') + let home = len(env) == 0 ? "" : substitute(env[0], '[^=]\+=', '', '') + call assert_equal(a:exp, home) +endfunc + +func CheckHome(exp, ...) + call assert_equal(a:exp, $HOME) + call assert_equal(a:exp, expand('~', ':p')) + if !a:0 + call CheckHomeIsMissingFromSubprocessEnvironment() + else + call CheckHomeIsInSubprocessEnvironment(a:1) + endif +endfunc + +func Test_WindowsHome() + command! -nargs=* SaveEnv call <SID>save_env(<f-args>) + command! -nargs=* RestoreEnv call <SID>restore_env() + command! -nargs=* UnletEnv call <SID>unlet_env(<f-args>) + set noshellslash + + let save_home = $HOME + SaveEnv $USERPROFILE $HOMEDRIVE $HOMEPATH + try + " Normal behavior: use $HOMEDRIVE and $HOMEPATH, ignore $USERPROFILE + let $USERPROFILE = 'unused' + let $HOMEDRIVE = 'C:' + let $HOMEPATH = '\foobar' + let $HOME = '' " Force recomputing "homedir" + call CheckHome('C:\foobar') + + " Same, but with $HOMEPATH not set + UnletEnv $HOMEPATH + let $HOME = '' " Force recomputing "homedir" + call CheckHome('C:\') + + " Use $USERPROFILE if $HOMEPATH and $HOMEDRIVE are empty + UnletEnv $HOMEDRIVE $HOMEPATH + let $USERPROFILE = 'C:\foo' + let $HOME = '' " Force recomputing "homedir" + call CheckHome('C:\foo') + + " If $HOME is set the others don't matter + let $HOME = 'C:\bar' + let $USERPROFILE = 'unused' + let $HOMEDRIVE = 'unused' + let $HOMEPATH = 'unused' + call CheckHome('C:\bar', 'C:\bar') + + " If $HOME contains %USERPROFILE% it is expanded + let $USERPROFILE = 'C:\foo' + let $HOME = '%USERPROFILE%\bar' + let $HOMEDRIVE = 'unused' + let $HOMEPATH = 'unused' + " call CheckHome('C:\foo\bar', '%USERPROFILE%\bar') + + " Invalid $HOME is kept + let $USERPROFILE = 'C:\foo' + let $HOME = '%USERPROFILE' + let $HOMEDRIVE = 'unused' + let $HOMEPATH = 'unused' + call CheckHome('%USERPROFILE', '%USERPROFILE') + + " %USERPROFILE% not at start of $HOME is not expanded + let $USERPROFILE = 'unused' + let $HOME = 'C:\%USERPROFILE%' + let $HOMEDRIVE = 'unused' + let $HOMEPATH = 'unused' + call CheckHome('C:\%USERPROFILE%', 'C:\%USERPROFILE%') + + if has('channel') + RestoreEnv + let $HOME = save_home + let env = '' + let job = job_start('cmd /c set', {'out_cb': {ch,x->[env,execute('let env=x')]}}) + sleep 1 + let env = filter(split(env, "\n"), 'v:val=="HOME"') + let home = len(env) == 0 ? "" : env[0] + call assert_equal('', home) + endif + finally + RestoreEnv + delcommand SaveEnv + delcommand RestoreEnv + delcommand UnletEnv + endtry +endfunc diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index cfcfaa83a0..508d25cd3b 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -802,7 +802,16 @@ static void reset_scroll_region(UI *ui) static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) { TUIData *data = ui->data; - ugrid_resize(&data->grid, (int)width, (int)height); + UGrid *grid = &data->grid; + ugrid_resize(grid, (int)width, (int)height); + + // resize might not always be followed by a clear before flush + // so clip the invalid region + for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { + Rect *r = &kv_A(data->invalid_regions, i); + r->bot = MIN(r->bot, grid->height-1); + r->right = MIN(r->right, grid->width-1); + } if (!got_winch) { // Try to resize the terminal window. UNIBI_SET_NUM_VAR(data->params[0], (int)height); diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 5e12b6a6a4..03aa7fa45f 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -203,6 +203,48 @@ describe('system()', function() ]]) end) + it('prints verbose information', function() + feed(':4verbose echo system("echo hi")<cr>') + screen:expect([[ + | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + | + Calling shell to execute: "echo hi" | + | + hi | + | + Press ENTER or type command to continue^ | + ]]) + feed('<cr>') + end) + + it('self and total time recorded separately', function() + local tempfile = helpers.tmpname() + + feed(':function! AlmostNoSelfTime()<cr>') + feed('echo system("echo hi")<cr>') + feed('endfunction<cr>') + + feed(':profile start ' .. tempfile .. '<cr>') + feed(':profile func AlmostNoSelfTime<cr>') + feed(':call AlmostNoSelfTime()<cr>') + feed(':profile dump<cr>') + + feed(':edit ' .. tempfile .. '<cr>') + + local command_total_time = tonumber(helpers.funcs.split(helpers.funcs.getline(7))[2]) + local command_self_time = tonumber(helpers.funcs.split(helpers.funcs.getline(7))[3]) + + helpers.neq(nil, command_total_time) + helpers.neq(nil, command_self_time) + end) + it('`yes` interrupted with CTRL-C', function() feed(':call system("' .. (iswin() and 'for /L %I in (1,0,2) do @echo y' diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index a6d2764187..72e71a2cf2 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -18,6 +18,7 @@ local expect_err = global_helpers.expect_err local filter = global_helpers.filter local map = global_helpers.map local matches = global_helpers.matches +local near = global_helpers.near local neq = global_helpers.neq local ok = global_helpers.ok local read_file = global_helpers.read_file @@ -699,6 +700,7 @@ local module = { meths = meths, missing_provider = missing_provider, mkdir = lfs.mkdir, + near = near, neq = neq, new_pipename = new_pipename, next_msg = next_msg, diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index c00d99cf90..4fbb46ac34 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command +local source = helpers.source describe('Signs', function() local screen @@ -13,6 +14,9 @@ describe('Signs', function() [0] = {bold=true, foreground=255}, [1] = {background = Screen.colors.Yellow}, [2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Grey}, + [3] = {background = Screen.colors.Gray90}, + [4] = {bold = true, reverse = true}, + [5] = {reverse = true}, } ) end) @@ -45,5 +49,34 @@ describe('Signs', function() | ]]) end) + + it('can be called right after :split', function() + feed('ia<cr>b<cr>c<cr><esc>gg') + -- This used to cause a crash due to :sign using a special redraw + -- (not updating nvim's specific highlight data structures) + -- without proper redraw first, as split just flags for redraw later. + source([[ + set cursorline + sign define piet text=>> texthl=Search + split + sign place 3 line=2 name=piet buffer=1 + ]]) + screen:expect([[ + {2: }{3:^a }| + {1:>>}b | + {2: }c | + {2: } | + {2: }{0:~ }| + {2: }{0:~ }| + {4:[No Name] [+] }| + {2: }{3:a }| + {1:>>}b | + {2: }c | + {2: } | + {2: }{0:~ }| + {5:[No Name] [+] }| + | + ]]) + end) end) end) diff --git a/test/helpers.lua b/test/helpers.lua index a774a67df3..66724f6a78 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -54,6 +54,9 @@ end local function ok(res) return assert.is_true(res) end +local function near(actual, expected, tolerance) + return assert.is.near(actual, expected, tolerance) +end local function matches(pat, actual) if nil ~= string.match(actual, pat) then return true @@ -694,6 +697,7 @@ local module = { map = map, matches = matches, mergedicts_copy = mergedicts_copy, + near = near, neq = neq, ok = ok, popen_r = popen_r, |