diff options
Diffstat (limited to 'src')
46 files changed, 1647 insertions, 609 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 438a85dd5d..c934d44e70 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -4030,8 +4030,8 @@ void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) if (!buf->b_p_bin) { char_u *rfname; - /* If the file name is a shortcut file, use the file it links to. */ - rfname = mch_resolve_shortcut(*ffname); + // If the file name is a shortcut file, use the file it links to. + rfname = os_resolve_shortcut(*ffname); if (rfname != NULL) { xfree(*ffname); *ffname = rfname; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index b515c4e1e4..46687f344c 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -937,8 +937,9 @@ struct matchitem { */ struct window_S { uint64_t handle; - buf_T *w_buffer; /* buffer we are a window into (used - often, keep it the first item!) */ + int w_id; ///< unique window ID + buf_T *w_buffer; ///< buffer we are a window into (used + ///< often, keep it the first item!) synblock_T *w_s; /* for :ownsyntax */ diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 5ae4416052..22ca0fb0cc 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -43,20 +43,29 @@ static bool chartab_initialized = false; #define GET_CHARTAB(buf, c) \ ((buf)->b_chartab[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f))) -/// Fill chartab[]. Also fills curbuf->b_chartab[] with flags for keyword +// Table used below, see init_chartab() for an explanation +static char_u g_chartab[256]; + +// Flags for g_chartab[]. +#define CT_CELL_MASK 0x07 ///< mask: nr of display cells (1, 2 or 4) +#define CT_PRINT_CHAR 0x10 ///< flag: set for printable chars +#define CT_ID_CHAR 0x20 ///< flag: set for ID chars +#define CT_FNAME_CHAR 0x40 ///< flag: set for file name chars + +/// Fill g_chartab[]. Also fills curbuf->b_chartab[] with flags for keyword /// characters for current buffer. /// /// Depends on the option settings 'iskeyword', 'isident', 'isfname', /// 'isprint' and 'encoding'. /// -/// The index in chartab[] depends on 'encoding': +/// The index in g_chartab[] depends on 'encoding': /// - For non-multi-byte index with the byte (same as the character). /// - For DBCS index with the first byte. /// - For UTF-8 index with the character (when first byte is up to 0x80 it is /// the same as the character, if the first byte is 0x80 and above it depends /// on further bytes). /// -/// The contents of chartab[]: +/// The contents of g_chartab[]: /// - The lower two bits, masked by CT_CELL_MASK, give the number of display /// cells the character occupies (1 or 2). Not valid for UTF-8 above 0x80. /// - CT_PRINT_CHAR bit is set when the character is printable (no need to @@ -94,32 +103,32 @@ int buf_init_chartab(buf_T *buf, int global) c = 0; while (c < ' ') { - chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; } while (c <= '~') { - chartab[c++] = 1 + CT_PRINT_CHAR; + g_chartab[c++] = 1 + CT_PRINT_CHAR; } if (p_altkeymap) { while (c < YE) { - chartab[c++] = 1 + CT_PRINT_CHAR; + g_chartab[c++] = 1 + CT_PRINT_CHAR; } } while (c < 256) { if (enc_utf8 && (c >= 0xa0)) { // UTF-8: bytes 0xa0 - 0xff are printable (latin1) - chartab[c++] = CT_PRINT_CHAR + 1; + g_chartab[c++] = CT_PRINT_CHAR + 1; } else if ((enc_dbcs == DBCS_JPNU) && (c == 0x8e)) { // euc-jp characters starting with 0x8e are single width - chartab[c++] = CT_PRINT_CHAR + 1; + g_chartab[c++] = CT_PRINT_CHAR + 1; } else if ((enc_dbcs != 0) && (MB_BYTE2LEN(c) == 2)) { // other double-byte chars can be printable AND double-width - chartab[c++] = CT_PRINT_CHAR + 2; + g_chartab[c++] = CT_PRINT_CHAR + 2; } else { // the rest is unprintable by default - chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; } } @@ -128,7 +137,7 @@ int buf_init_chartab(buf_T *buf, int global) if (((enc_dbcs != 0) && (MB_BYTE2LEN(c) > 1)) || ((enc_dbcs == DBCS_JPNU) && (c == 0x8e)) || (enc_utf8 && (c >= 0xa0))) { - chartab[c] |= CT_FNAME_CHAR; + g_chartab[c] |= CT_FNAME_CHAR; } } } @@ -231,9 +240,9 @@ int buf_init_chartab(buf_T *buf, int global) if (i == 0) { // (re)set ID flag if (tilde) { - chartab[c] &= (uint8_t)~CT_ID_CHAR; + g_chartab[c] &= (uint8_t)~CT_ID_CHAR; } else { - chartab[c] |= CT_ID_CHAR; + g_chartab[c] |= CT_ID_CHAR; } } else if (i == 1) { // (re)set printable @@ -244,20 +253,20 @@ int buf_init_chartab(buf_T *buf, int global) || (p_altkeymap && (F_isalpha(c) || F_isdigit(c)))) && !(enc_dbcs && (MB_BYTE2LEN(c) == 2))) { if (tilde) { - chartab[c] = (uint8_t)((chartab[c] & ~CT_CELL_MASK) - + ((dy_flags & DY_UHEX) ? 4 : 2)); - chartab[c] &= (uint8_t)~CT_PRINT_CHAR; + g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + + ((dy_flags & DY_UHEX) ? 4 : 2)); + g_chartab[c] &= (uint8_t)~CT_PRINT_CHAR; } else { - chartab[c] = (uint8_t)((chartab[c] & ~CT_CELL_MASK) + 1); - chartab[c] |= CT_PRINT_CHAR; + g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + 1); + g_chartab[c] |= CT_PRINT_CHAR; } } } else if (i == 2) { // (re)set fname flag if (tilde) { - chartab[c] &= (uint8_t)~CT_FNAME_CHAR; + g_chartab[c] &= (uint8_t)~CT_FNAME_CHAR; } else { - chartab[c] |= CT_FNAME_CHAR; + g_chartab[c] |= CT_FNAME_CHAR; } } else { // i == 3 // (re)set keyword flag @@ -492,9 +501,9 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) return buf; } -// Catch 22: chartab[] can't be initialized before the options are +// Catch 22: g_chartab[] can't be initialized before the options are // initialized, and initializing options may cause transchar() to be called! -// When chartab_initialized == false don't use chartab[]. +// When chartab_initialized == false don't use g_chartab[]. // Does NOT work for multi-byte characters, c must be <= 255. // Also doesn't work for the first byte of a multi-byte, "c" must be a // character! @@ -633,7 +642,7 @@ int byte2cells(int b) if (enc_utf8 && (b >= 0x80)) { return 0; } - return chartab[b] & CT_CELL_MASK; + return g_chartab[b] & CT_CELL_MASK; } /// Return number of display cells occupied by character "c". @@ -665,7 +674,7 @@ int char2cells(int c) return 2; } } - return chartab[c & 0xff] & CT_CELL_MASK; + return g_chartab[c & 0xff] & CT_CELL_MASK; } /// Return number of display cells occupied by character at "*p". @@ -682,7 +691,7 @@ int ptr2cells(char_u *p) } // For DBCS we can tell the cell count from the first byte. - return chartab[*p] & CT_CELL_MASK; + return g_chartab[*p] & CT_CELL_MASK; } /// Return the number of character cells string "s" will take on the screen, @@ -806,7 +815,7 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) bool vim_isIDc(int c) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return c > 0 && c < 0x100 && (chartab[c] & CT_ID_CHAR); + return c > 0 && c < 0x100 && (g_chartab[c] & CT_ID_CHAR); } /// Check that "c" is a keyword character: @@ -878,7 +887,7 @@ bool vim_iswordp_buf(char_u *p, buf_T *buf) bool vim_isfilec(int c) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return c >= 0x100 || (c > 0 && (chartab[c] & CT_FNAME_CHAR)); + return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_FNAME_CHAR)); } /// Check that "c" is a valid file-name character or a wildcard character @@ -906,7 +915,7 @@ bool vim_isprintc(int c) if (enc_utf8 && (c >= 0x100)) { return utf_printable(c); } - return c >= 0x100 || (c > 0 && (chartab[c] & CT_PRINT_CHAR)); + return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_PRINT_CHAR)); } /// Strict version of vim_isprintc(c), don't return true if "c" is the head @@ -925,7 +934,7 @@ bool vim_isprintc_strict(int c) if (enc_utf8 && (c >= 0x100)) { return utf_printable(c); } - return c >= 0x100 || (c > 0 && (chartab[c] & CT_PRINT_CHAR)); + return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_PRINT_CHAR)); } /// like chartabsize(), but also check for line breaks on the screen @@ -1247,7 +1256,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, if (enc_utf8 && (c >= 0x80)) { incr = utf_ptr2cells(ptr); } else { - incr = CHARSIZE(c); + incr = g_chartab[c] & CT_CELL_MASK; } // If a double-cell char doesn't fit at the end of a line @@ -1261,7 +1270,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, head = 1; } } else { - incr = CHARSIZE(c); + incr = g_chartab[c] & CT_CELL_MASK; } } diff --git a/src/nvim/charset.h b/src/nvim/charset.h index 995ad123ae..78d6f2a76c 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -1,14 +1,6 @@ #ifndef NVIM_CHARSET_H #define NVIM_CHARSET_H -/* - * Flags for chartab[]. - */ -#define CT_CELL_MASK 0x07 /* mask: nr of display cells (1, 2 or 4) */ -#define CT_PRINT_CHAR 0x10 /* flag: set for printable chars */ -#define CT_ID_CHAR 0x20 /* flag: set for ID chars */ -#define CT_FNAME_CHAR 0x40 /* flag: set for file name chars */ - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.h.generated.h" #endif diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 03ef41f849..98ec9ae280 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2385,6 +2385,7 @@ void set_completion(colnr_T startcol, list_T *list) } else { ins_complete(Ctrl_N, false); } + compl_enter_selects = compl_no_insert; // Lazily show the popup menu, unless we got interrupted. if (!compl_interrupted) { @@ -3989,6 +3990,7 @@ static void ins_compl_insert(void) dict_add_nr_str(dict, "info", 0L, EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_INFO])); set_vim_var_dict(VV_COMPLETED_ITEM, dict); + compl_curr_match = compl_shown_match; } /* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 55fa974797..7839a7f645 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3297,6 +3297,26 @@ char_u *get_user_var_name(expand_T *xp, int idx) } +/// Return TRUE if "pat" matches "text". +/// Does not use 'cpo' and always uses 'magic'. +static int pattern_match(char_u *pat, char_u *text, int ic) +{ + int matches = 0; + regmatch_T regmatch; + + // avoid 'l' flag in 'cpoptions' + char_u *save_cpo = p_cpo; + p_cpo = (char_u *)""; + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + if (regmatch.regprog != NULL) { + regmatch.rm_ic = ic; + matches = vim_regexec_nl(®match, text, (colnr_T)0); + vim_regfree(regmatch.regprog); + } + p_cpo = save_cpo; + return matches; +} + /* * types for expressions. */ @@ -3572,9 +3592,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) long n1, n2; char_u *s1, *s2; char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; - regmatch_T regmatch; int ic; - char_u *save_cpo; /* * Get the first variable. @@ -3783,19 +3801,10 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) case TYPE_MATCH: case TYPE_NOMATCH: - /* avoid 'l' flag in 'cpoptions' */ - save_cpo = p_cpo; - p_cpo = (char_u *)""; - regmatch.regprog = vim_regcomp(s2, - RE_MAGIC + RE_STRING); - regmatch.rm_ic = ic; - if (regmatch.regprog != NULL) { - n1 = vim_regexec_nl(®match, s1, (colnr_T)0); - vim_regfree(regmatch.regprog); - if (type == TYPE_NOMATCH) - n1 = !n1; + n1 = pattern_match(s2, s1, ic); + if (type == TYPE_NOMATCH) { + n1 = !n1; } - p_cpo = save_cpo; break; case TYPE_UNKNOWN: break; /* avoid gcc warning */ @@ -6697,6 +6706,9 @@ static struct fst { { "assert_exception", 1, 2, f_assert_exception }, { "assert_fails", 1, 2, f_assert_fails }, { "assert_false", 1, 2, f_assert_false }, + { "assert_match", 2, 3, f_assert_match }, + { "assert_notequal", 2, 3, f_assert_notequal }, + { "assert_notmatch", 2, 3, f_assert_notmatch }, { "assert_true", 1, 2, f_assert_true }, { "atan", 1, 1, f_atan }, { "atan2", 2, 2, f_atan2 }, @@ -6715,7 +6727,6 @@ static struct fst { { "byteidx", 2, 2, f_byteidx }, { "byteidxcomp", 2, 2, f_byteidxcomp }, { "call", 2, 3, f_call }, - { "capture", 1, 1, f_capture }, { "ceil", 1, 1, f_ceil }, { "changenr", 0, 0, f_changenr }, { "char2nr", 1, 2, f_char2nr }, @@ -6744,6 +6755,7 @@ static struct fst { { "eval", 1, 1, f_eval }, { "eventhandler", 0, 0, f_eventhandler }, { "executable", 1, 1, f_executable }, + { "execute", 1, 1, f_execute }, { "exepath", 1, 1, f_exepath }, { "exists", 1, 1, f_exists }, { "exp", 1, 1, f_exp }, @@ -6779,6 +6791,7 @@ static struct fst { { "getcmdpos", 0, 0, f_getcmdpos }, { "getcmdtype", 0, 0, f_getcmdtype }, { "getcmdwintype", 0, 0, f_getcmdwintype }, + { "getcompletion", 2, 2, f_getcompletion }, { "getcurpos", 0, 0, f_getcurpos }, { "getcwd", 0, 2, f_getcwd }, { "getfontname", 0, 1, f_getfontname }, @@ -6974,6 +6987,10 @@ static struct fst { { "virtcol", 1, 1, f_virtcol }, { "visualmode", 0, 1, f_visualmode }, { "wildmenumode", 0, 0, f_wildmenumode }, + { "win_getid", 0, 2, f_win_getid }, + { "win_gotoid", 1, 1, f_win_gotoid }, + { "win_id2tabwin", 1, 1, f_win_id2tabwin }, + { "win_id2win", 1, 1, f_win_id2win }, { "winbufnr", 1, 1, f_winbufnr }, { "wincol", 0, 0, f_wincol }, { "winheight", 1, 1, f_winheight }, @@ -7606,7 +7623,7 @@ static void prepare_assert_error(garray_T *gap) // Fill "gap" with information about an assert error. static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv) + typval_T *got_tv, assert_type_T atype) { char_u *tofree; @@ -7615,7 +7632,11 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, ga_concat(gap, tofree); xfree(tofree); } else { - ga_concat(gap, (char_u *)"Expected "); + if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { + ga_concat(gap, (char_u *)"Pattern "); + } else { + ga_concat(gap, (char_u *)"Expected "); + } if (exp_str == NULL) { tofree = (char_u *) encode_tv2string(exp_tv, NULL); ga_concat(gap, tofree); @@ -7623,8 +7644,16 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, } else { ga_concat(gap, exp_str); } - tofree = (char_u *) encode_tv2string(got_tv, NULL); - ga_concat(gap, (char_u *)" but got "); + tofree = (char_u *)encode_tv2string(got_tv, NULL); + if (atype == ASSERT_MATCH) { + ga_concat(gap, (char_u *)" does not match "); + } else if (atype == ASSERT_NOTMATCH) { + ga_concat(gap, (char_u *)" does match "); + } else if (atype == ASSERT_NOTEQUAL) { + ga_concat(gap, (char_u *)" differs from "); + } else { + ga_concat(gap, (char_u *)" but got "); + } ga_concat(gap, tofree); xfree(tofree); } @@ -7643,20 +7672,32 @@ static void assert_error(garray_T *gap) gap->ga_data, gap->ga_len); } -// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv) +static void assert_equal_common(typval_T *argvars, assert_type_T atype) { garray_T ga; - if (!tv_equal(&argvars[0], &argvars[1], false, false)) { + if (tv_equal(&argvars[0], &argvars[1], false, false) + != (atype == ASSERT_EQUAL)) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, - &argvars[0], &argvars[1]); + &argvars[0], &argvars[1], atype); assert_error(&ga); ga_clear(&ga); } } +// "assert_equal(expected, actual[, msg])" function +static void f_assert_equal(typval_T *argvars, typval_T *rettv) +{ + assert_equal_common(argvars, ASSERT_EQUAL); +} + +// "assert_notequal(expected, actual[, msg])" function +static void f_assert_notequal(typval_T *argvars, typval_T *rettv) +{ + assert_equal_common(argvars, ASSERT_NOTEQUAL); +} + /// "assert_exception(string[, msg])" function static void f_assert_exception(typval_T *argvars, typval_T *rettv) { @@ -7672,7 +7713,7 @@ static void f_assert_exception(typval_T *argvars, typval_T *rettv) && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv); + &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); } @@ -7702,7 +7743,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv) || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - &vimvars[VV_ERRMSG].vv_tv); + &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); } @@ -7732,7 +7773,7 @@ static void assert_bool(typval_T *argvars, bool is_true) prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], (char_u *)(is_true ? "True" : "False"), - NULL, &argvars[0]); + NULL, &argvars[0], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); } @@ -7744,6 +7785,36 @@ static void f_assert_false(typval_T *argvars, typval_T *rettv) assert_bool(argvars, false); } +static void assert_match_common(typval_T *argvars, assert_type_T atype) +{ + char_u buf1[NUMBUFLEN]; + char_u buf2[NUMBUFLEN]; + char_u *pat = get_tv_string_buf_chk(&argvars[0], buf1); + char_u *text = get_tv_string_buf_chk(&argvars[1], buf2); + + if (pat == NULL || text == NULL) { + EMSG(_(e_invarg)); + } else if (pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) { + garray_T ga; + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + } +} + +/// "assert_match(pattern, actual[, msg])" function +static void f_assert_match(typval_T *argvars, typval_T *rettv) +{ + assert_match_common(argvars, ASSERT_MATCH); +} + +/// "assert_notmatch(pattern, actual[, msg])" function +static void f_assert_notmatch(typval_T *argvars, typval_T *rettv) +{ + assert_match_common(argvars, ASSERT_NOTMATCH); +} + // "assert_true(actual[, msg])" function static void f_assert_true(typval_T *argvars, typval_T *rettv) { @@ -8087,38 +8158,6 @@ static void f_call(typval_T *argvars, typval_T *rettv) (void)func_call(func, &argvars[1], selfdict, rettv); } -// "capture(command)" function -static void f_capture(typval_T *argvars, typval_T *rettv) -{ - int save_msg_silent = msg_silent; - garray_T *save_capture_ga = capture_ga; - - if (check_secure()) { - return; - } - - garray_T capture_local; - capture_ga = &capture_local; - ga_init(capture_ga, (int)sizeof(char), 80); - - msg_silent++; - if (argvars[0].v_type != VAR_LIST) { - do_cmdline_cmd((char *)get_tv_string(&argvars[0])); - } else if (argvars[0].vval.v_list != NULL) { - for (listitem_T *li = argvars[0].vval.v_list->lv_first; - li != NULL; li = li->li_next) { - do_cmdline_cmd((char *)get_tv_string(&li->li_tv)); - } - } - msg_silent = save_msg_silent; - - ga_append(capture_ga, NUL); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = capture_ga->ga_data; - - capture_ga = save_capture_ga; -} - /* * "ceil({float})" function */ @@ -8803,6 +8842,38 @@ static void f_executable(typval_T *argvars, typval_T *rettv) || (gettail_dir(name) != name && os_can_exe(name, NULL, false)); } +// "execute(command)" function +static void f_execute(typval_T *argvars, typval_T *rettv) +{ + int save_msg_silent = msg_silent; + garray_T *save_capture_ga = capture_ga; + + if (check_secure()) { + return; + } + + garray_T capture_local; + capture_ga = &capture_local; + ga_init(capture_ga, (int)sizeof(char), 80); + + msg_silent++; + if (argvars[0].v_type != VAR_LIST) { + do_cmdline_cmd((char *)get_tv_string(&argvars[0])); + } else if (argvars[0].vval.v_list != NULL) { + for (listitem_T *li = argvars[0].vval.v_list->lv_first; + li != NULL; li = li->li_next) { + do_cmdline_cmd((char *)get_tv_string(&li->li_tv)); + } + } + msg_silent = save_msg_silent; + + ga_append(capture_ga, NUL); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = capture_ga->ga_data; + + capture_ga = save_capture_ga; +} + /// "exepath()" function static void f_exepath(typval_T *argvars, typval_T *rettv) { @@ -9917,6 +9988,50 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv) rettv->vval.v_string[0] = cmdwin_type; } +// "getcompletion()" function +static void f_getcompletion(typval_T *argvars, typval_T *rettv) +{ + char_u *pat; + expand_T xpc; + int options = WILD_KEEP_ALL | WILD_SILENT | WILD_USE_NL + | WILD_LIST_NOTFOUND | WILD_NO_BEEP; + + if (p_wic) { + options |= WILD_ICASE; + } + + ExpandInit(&xpc); + xpc.xp_pattern = get_tv_string(&argvars[0]); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_context = cmdcomplete_str_to_type(get_tv_string(&argvars[1])); + if (xpc.xp_context == EXPAND_NOTHING) { + if (argvars[1].v_type == VAR_STRING) { + EMSG2(_(e_invarg2), argvars[1].vval.v_string); + } else { + EMSG(_(e_invarg)); + } + return; + } + + if (xpc.xp_context == EXPAND_MENUS) { + set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + } + + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + if ((rettv_list_alloc(rettv) != FAIL) && (pat != NULL)) { + int i; + + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + + for (i = 0; i < xpc.xp_numfiles; i++) { + list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + } + } + xfree(pat); + ExpandCleanup(&xpc); +} + /// `getcwd([{win}[, {tab}]])` function /// /// Every scope not specified implies the currently selected scope object. @@ -13526,11 +13641,12 @@ static void f_resolve(typval_T *argvars, typval_T *rettv) { char_u *v = NULL; - v = mch_resolve_shortcut(p); - if (v != NULL) + v = os_resolve_shortcut(p); + if (v != NULL) { rettv->vval.v_string = v; - else + } else { rettv->vval.v_string = vim_strsave(p); + } } #else # ifdef HAVE_READLINK @@ -17038,6 +17154,32 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = 1; } +/// "win_getid()" function +static void f_win_getid(typval_T *argvars, typval_T *rettv) +{ + rettv->vval.v_number = win_getid(argvars); +} + +/// "win_gotoid()" function +static void f_win_gotoid(typval_T *argvars, typval_T *rettv) +{ + rettv->vval.v_number = win_gotoid(argvars); +} + +/// "win_id2tabwin()" function +static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv) +{ + if (rettv_list_alloc(rettv) != FAIL) { + win_id2tabwin(argvars, rettv->vval.v_list); + } +} + +/// "win_id2win()" function +static void f_win_id2win(typval_T *argvars, typval_T *rettv) +{ + rettv->vval.v_number = win_id2win(argvars); +} + /* * "winbufnr(nr)" function */ @@ -18325,7 +18467,7 @@ static void init_tv(typval_T *varp) * caller of incompatible types: it sets *denote to TRUE if "denote" * is not NULL or returns -1 otherwise. */ -static long get_tv_number(typval_T *varp) +long get_tv_number(typval_T *varp) { int error = FALSE; @@ -18886,19 +19028,6 @@ set_var ( || tv_check_lock(v->di_tv.v_lock, name, false)) { return; } - if (v->di_tv.v_type != tv->v_type - && !((v->di_tv.v_type == VAR_STRING - || v->di_tv.v_type == VAR_NUMBER) - && (tv->v_type == VAR_STRING - || tv->v_type == VAR_NUMBER)) - && !((v->di_tv.v_type == VAR_NUMBER - || v->di_tv.v_type == VAR_FLOAT) - && (tv->v_type == VAR_NUMBER - || tv->v_type == VAR_FLOAT)) - ) { - EMSG2(_("E706: Variable type mismatch for: %s"), name); - return; - } // Handle setting internal v: variables separately where needed to // prevent changing the type. @@ -18908,7 +19037,7 @@ set_var ( if (copy || tv->v_type != VAR_STRING) v->di_tv.vval.v_string = vim_strsave(get_tv_string(tv)); else { - /* Take over the string to avoid an extra alloc/free. */ + // Take over the string to avoid an extra alloc/free. v->di_tv.vval.v_string = tv->vval.v_string; tv->vval.v_string = NULL; } @@ -22344,7 +22473,10 @@ bool eval_has_provider(char *name) } \ } - static int has_clipboard = -1, has_python = -1, has_python3 = -1; + static int has_clipboard = -1; + static int has_python = -1; + static int has_python3 = -1; + static int has_ruby = -1; if (!strcmp(name, "clipboard")) { check_provider(clipboard); @@ -22355,6 +22487,9 @@ bool eval_has_provider(char *name) } else if (!strcmp(name, "python")) { check_provider(python); return has_python; + } else if (!strcmp(name, "ruby")) { + check_provider(ruby); + return has_ruby; } return false; diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index d5c9b2c1ec..884c987f10 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -162,4 +162,14 @@ typedef struct list_stack_S { /// Convert a hashitem pointer to a dictitem pointer #define HI2DI(hi) HIKEY2DI((hi)->hi_key) +/// Type of assert_* check being performed +typedef enum +{ + ASSERT_EQUAL, + ASSERT_NOTEQUAL, + ASSERT_MATCH, + ASSERT_NOTMATCH, + ASSERT_OTHER, +} assert_type_T; + #endif // NVIM_EVAL_DEFS_H diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 1c4c9737c3..317e40e43a 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -116,23 +116,20 @@ void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL process_is_tearing_down = true; kl_iter(WatcherPtr, loop->children, current) { Process *proc = (*current)->data; - if (proc->detach) { + if (proc->detach || proc->type == kProcessTypePty) { // Close handles to process without killing it. CREATE_EVENT(loop->events, process_close_handles, 1, proc); } else { - if (proc->type == kProcessTypeUv) { - uv_kill(proc->pid, SIGTERM); - proc->term_sent = true; - process_stop(proc); - } else { // kProcessTypePty - process_close_streams(proc); - pty_process_close_master((PtyProcess *)proc); - } + uv_kill(proc->pid, SIGTERM); + proc->term_sent = true; + process_stop(proc); } } - // Wait until all children exit - LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1, kl_empty(loop->children)); + // Wait until all children exit and all close events are processed. + LOOP_PROCESS_EVENTS_UNTIL( + loop, loop->events, -1, + kl_empty(loop->children) && queue_empty(loop->events)); pty_process_teardown(loop); } @@ -315,8 +312,10 @@ static void decref(Process *proc) static void process_close(Process *proc) FUNC_ATTR_NONNULL_ARG(1) { - if (process_is_tearing_down && proc->detach && proc->closed) { - // If a detached process dies while tearing down it might get closed twice. + if (process_is_tearing_down && (proc->detach || proc->type == kProcessTypePty) + && proc->closed) { + // If a detached/pty process dies while tearing down it might get closed + // twice. return; } assert(!proc->closed); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index da64533708..5de9ac0523 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4821,9 +4821,8 @@ static void helptags_one(char_u *dir, char_u *ext, char_u *tagfname, int mix = FALSE; /* detected mixed encodings */ // Find all *.txt files. - size_t dirlen = STRLEN(dir); - STRCPY(NameBuff, dir); - STRCAT(NameBuff, "/**/*"); + size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); + STRCAT(NameBuff, "/**/*"); // NOLINT STRCAT(NameBuff, ext); // Note: We cannot just do `&NameBuff` because it is a statically sized array @@ -4841,7 +4840,7 @@ static void helptags_one(char_u *dir, char_u *ext, char_u *tagfname, * Open the tags file for writing. * Do this before scanning through all the files. */ - STRCPY(NameBuff, dir); + STRLCPY(NameBuff, dir, sizeof(NameBuff)); add_pathsep((char *)NameBuff); STRNCAT(NameBuff, tagfname, sizeof(NameBuff) - dirlen - 2); fd_tags = mch_fopen((char *)NameBuff, "w"); @@ -5016,7 +5015,7 @@ static void do_helptags(char_u *dirname, bool add_help_tags) char_u **files; // Get a list of all files in the help directory and in subdirectories. - STRCPY(NameBuff, dirname); + STRLCPY(NameBuff, dirname, sizeof(NameBuff)); add_pathsep((char *)NameBuff); STRCAT(NameBuff, "**"); diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 76191d5a56..3f5d9b3244 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2170,19 +2170,19 @@ return { command='ruby', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type=ADDR_LINES, - func='ex_script_ni', + func='ex_ruby', }, { command='rubydo', flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), addr_type=ADDR_LINES, - func='ex_ni', + func='ex_rubydo', }, { command='rubyfile', flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), addr_type=ADDR_LINES, - func='ex_ni', + func='ex_rubyfile', }, { command='rviminfo', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index b56b1cf013..6d24ba91f2 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -897,6 +897,21 @@ void ex_pydo(exarg_T *eap) script_host_do_range("python", eap); } +void ex_ruby(exarg_T *eap) +{ + script_host_execute("ruby", eap); +} + +void ex_rubyfile(exarg_T *eap) +{ + script_host_execute_file("ruby", eap); +} + +void ex_rubydo(exarg_T *eap) +{ + script_host_do_range("ruby", eap); +} + void ex_python3(exarg_T *eap) { script_host_execute("python3", eap); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 9bc7ec39da..8bae817211 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5597,6 +5597,17 @@ int parse_compl_arg(char_u *value, int vallen, int *complp, return OK; } +int cmdcomplete_str_to_type(char_u *complete_str) +{ + for (int i = 0; command_complete[i].expand != 0; i++) { + if (STRCMP(complete_str, command_complete[i].name) == 0) { + return command_complete[i].expand; + } + } + + return EXPAND_NOTHING; +} + static void ex_colorscheme(exarg_T *eap) { if (*eap->arg == NUL) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 154558b332..3b598bd64b 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -493,22 +493,14 @@ readfile ( curbuf->b_flags &= ~(BF_NEW | BF_NEW_W); } - /* - * Check readonly by trying to open the file for writing. - * If this fails, we know that the file is readonly. - */ - file_readonly = FALSE; + // Check readonly. + file_readonly = false; if (!read_buffer && !read_stdin) { - if (!newfile || readonlymode) { - file_readonly = TRUE; - } else if ((fd = os_open((char *)fname, O_RDWR, 0)) < 0) { - // opening in readwrite mode failed => file is readonly - file_readonly = TRUE; - } - if (file_readonly == TRUE) { - // try to open readonly - fd = os_open((char *)fname, O_RDONLY, 0); + if (!newfile || readonlymode || !(perm & 0222) + || !os_file_is_writable((char *)fname)) { + file_readonly = true; } + fd = os_open((char *)fname, O_RDONLY, 0); } if (fd < 0) { /* cannot open at all */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index ed9862a264..950ceb4c74 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -914,9 +914,6 @@ EXTERN int KeyTyped; // TRUE if user typed current char EXTERN int KeyStuffed; // TRUE if current char from stuffbuf EXTERN int maptick INIT(= 0); // tick for each non-mapped char -EXTERN uint8_t chartab[256]; // table used in charset.c; See - // init_chartab() for explanation - EXTERN int must_redraw INIT(= 0); /* type of redraw necessary */ EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ @@ -989,7 +986,7 @@ EXTERN int redir_off INIT(= false); // no redirection for a moment EXTERN FILE *redir_fd INIT(= NULL); // message redirection file EXTERN int redir_reg INIT(= 0); // message redirection register EXTERN int redir_vname INIT(= 0); // message redirection variable -EXTERN garray_T *capture_ga INIT(= NULL); // capture() buffer +EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute() EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */ diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 5f69fa2f6a..503daa9648 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -73,11 +73,6 @@ /* Returns empty string if it is NULL. */ #define EMPTY_IF_NULL(x) ((x) ? (x) : (char_u *)"") -/* macro version of chartab(). - * Only works with values 0-255! - * Doesn't work for UTF-8 mode with chars >= 0x80. */ -#define CHARSIZE(c) (chartab[c] & CT_CELL_MASK) - /* * Adjust chars in a language according to 'langmap' option. * NOTE that there is no noticeable overhead if 'langmap' is not set. diff --git a/src/nvim/main.c b/src/nvim/main.c index 64b5de8663..e052d0d315 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -670,8 +670,8 @@ static void init_locale(void) { char_u *p; - /* expand_env() doesn't work yet, because chartab[] is not initialized - * yet, call vim_getenv() directly */ + // expand_env() doesn't work yet, because g_chartab[] is not + // initialized yet, call vim_getenv() directly p = (char_u *)vim_getenv("VIMRUNTIME"); if (p != NULL && *p != NUL) { vim_snprintf((char *)NameBuff, MAXPATHL, "%s/lang", p); diff --git a/src/nvim/map.c b/src/nvim/map.c index 03439e7a9c..398e74268f 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -129,7 +129,10 @@ static inline khint_t String_hash(String s) static inline bool String_eq(String a, String b) { - return strncmp(a.data, b.data, MIN(a.size, b.size)) == 0; + if (a.size != b.size) { + return false; + } + return memcmp(a.data, b.data, a.size) == 0; } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 0ba9f8b076..26d94aa6fa 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1885,6 +1885,86 @@ static int utf_strnicmp(char_u *s1, char_u *s2, size_t n1, size_t n2) return n1 == 0 ? -1 : 1; } +#ifdef WIN32 +#ifndef CP_UTF8 +# define CP_UTF8 65001 /* magic number from winnls.h */ +#endif + +int utf8_to_utf16(const char *str, WCHAR **strw) + FUNC_ATTR_NONNULL_ALL +{ + ssize_t wchar_len = 0; + + // Compute the length needed to store the converted widechar string. + wchar_len = MultiByteToWideChar(CP_UTF8, + 0, // dwFlags: must be 0 for utf8 + str, // lpMultiByteStr: string to convert + -1, // -1 => process up to NUL + NULL, // lpWideCharStr: converted string + 0); // 0 => return length, don't convert + if (wchar_len == 0) { + return GetLastError(); + } + + ssize_t buf_sz = wchar_len * sizeof(WCHAR); + + if (buf_sz == 0) { + *strw = NULL; + return 0; + } + + char *buf = xmalloc(buf_sz); + char *pos = buf; + + int r = MultiByteToWideChar(CP_UTF8, + 0, + str, + -1, + (WCHAR *)pos, + wchar_len); + assert(r == wchar_len); + *strw = (WCHAR *)pos; + + return 0; +} + +int utf16_to_utf8(const WCHAR *strw, char **str) + FUNC_ATTR_NONNULL_ALL +{ + // Compute the space required to store the string as UTF-8. + ssize_t utf8_len = WideCharToMultiByte(CP_UTF8, + 0, + strw, + -1, + NULL, + 0, + NULL, + NULL); + if (utf8_len == 0) { + return GetLastError(); + } + + ssize_t buf_sz = utf8_len * sizeof(char); + char *buf = xmalloc(buf_sz); + char *pos = buf; + + // Convert string to UTF-8. + int r = WideCharToMultiByte(CP_UTF8, + 0, + strw, + -1, + (LPSTR *)pos, + utf8_len, + NULL, + NULL); + assert(r == utf8_len); + *str = pos; + + return 0; +} + +#endif + /* * Version of strnicmp() that handles multi-byte characters. * Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 673205f08f..08e82071d7 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3165,9 +3165,10 @@ attention_message ( } /* Some of these messages are long to allow translation to * other languages. */ - MSG_PUTS(_( - "\n(1) Another program may be editing the same file. If this is the case,\n be careful not to end up with two different instances of the same\n file when making changes.")); - MSG_PUTS(_(" Quit, or continue with caution.\n")); + MSG_PUTS(_("\n(1) Another program may be editing the same file. If this is" + " the case,\n be careful not to end up with two different" + " instances of the same\n file when making changes." + " Quit, or continue with caution.\n")); MSG_PUTS(_("(2) An edit session for this file crashed.\n")); MSG_PUTS(_(" If this is the case, use \":recover\" or \"vim -r ")); msg_outtrans(buf->b_fname); diff --git a/src/nvim/message.c b/src/nvim/message.c index 77e8f0e4f2..3c310ed309 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2396,7 +2396,7 @@ static void redir_write(char_u *str, int maxlen) return; } - // Append output to capture(). + // Append output for execute(). if (capture_ga) { size_t len = 0; while (str[len] && (maxlen < 0 ? 1 : (len < (size_t)maxlen))) { diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 2f499e477c..5efac2623c 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -6,6 +6,7 @@ #include "nvim/window.h" #include "nvim/strings.h" #include "nvim/screen.h" +#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/os_unix.h" #include "nvim/fold.h" @@ -303,6 +304,10 @@ retnomove: mouse_past_bottom = true; } + if (!(flags & MOUSE_RELEASED) && which_button == MOUSE_LEFT) { + col = mouse_adjust_click(curwin, row, col); + } + // Start Visual mode before coladvance(), for when 'sel' != "old" if ((flags & MOUSE_MAY_VIS) && !VIsual_active) { check_visual_highlight(); @@ -597,3 +602,74 @@ bool mouse_scroll_horiz(int dir) return leftcol_changed(); } + +// Adjust the clicked column position if there are concealed characters +// before the current column. But only when it's absolutely necessary. +static int mouse_adjust_click(win_T *wp, int row, int col) +{ + if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0 + && wp->w_leftcol < curbuf->b_p_smc && conceal_cursor_line(wp))) { + return col; + } + + int end = (colnr_T)STRLEN(ml_get(wp->w_cursor.lnum)); + int vend = getviscol2(end, 0); + + if (col >= vend) { + return col; + } + + int i = wp->w_leftcol; + + if (row > 0) { + i += row * (wp->w_width - win_col_off(wp) - win_col_off2(wp) + - wp->w_leftcol) + wp->w_skipcol; + } + + int start_col = i; + int matchid; + int last_matchid; + int bcol = end - (vend - col); + + while (i < bcol) { + matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, i); + + if (matchid != 0) { + if (wp->w_p_cole == 3) { + bcol++; + } else { + if (row > 0 && i == start_col) { + // Check if the current concealed character is actually part of + // the previous wrapped row's conceal group. + last_matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, + i - 1); + if (last_matchid == matchid) { + bcol++; + } + } else if (wp->w_p_cole == 1 + || (wp->w_p_cole == 2 + && (lcs_conceal != NUL + || syn_get_sub_char() != NUL))) { + // At least one placeholder character will be displayed. + bcol--; + } + + last_matchid = matchid; + + // Adjust for concealed text that spans more than one character. + do { + i++; + bcol++; + matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, i); + } while (last_matchid == matchid); + + continue; + } + } + + i++; + } + + return getviscol2(bcol, 0); +} + diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index abbd3e8aff..d7c2926a0f 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -40,6 +40,10 @@ bool server_init(void) listen_address = server_address_new(); } + if (!listen_address) { + return false; + } + bool ok = (server_start(listen_address) == 0); if (must_free) { xfree((char *) listen_address); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a498fc481a..e8a79fa820 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -339,10 +339,16 @@ static void shift_block(oparg_T *oap, int amount) total += bd.pre_whitesp; /* all virtual WS up to & incl a split TAB */ ws_vcol = bd.start_vcol - bd.pre_whitesp; if (bd.startspaces) { - if (has_mbyte) - bd.textstart += (*mb_ptr2len)(bd.textstart); - else - ++bd.textstart; + if (has_mbyte) { + if ((*mb_ptr2len)(bd.textstart) == 1) { + bd.textstart++; + } else { + ws_vcol = 0; + bd.startspaces = 0; + } + } else { + bd.textstart++; + } } for (; ascii_iswhite(*bd.textstart); ) { // TODO: is passing bd.textstart for start of the line OK? @@ -5452,7 +5458,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) yankreg_T *target; if (cb_flags & CB_UNNAMEDPLUS) { - *name = cb_flags & CB_UNNAMED ? '"': '+'; + *name = (cb_flags & CB_UNNAMED && writing) ? '"': '+'; target = &y_regs[PLUS_REGISTER]; } else { *name = '*'; diff --git a/src/nvim/option.c b/src/nvim/option.c index a7b44b372c..6baf8c65ce 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2451,16 +2451,13 @@ did_set_string_option ( else if (varp == &curwin->w_p_briopt) { if (briopt_check(curwin) == FAIL) errmsg = e_invarg; - } - /* - * 'isident', 'iskeyword', 'isprint or 'isfname' option: refill chartab[] - * If the new option is invalid, use old value. 'lisp' option: refill - * chartab[] for '-' char - */ - else if ( varp == &p_isi + } else if (varp == &p_isi || varp == &(curbuf->b_p_isk) || varp == &p_isp || varp == &p_isf) { + // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] + // If the new option is invalid, use old value. 'lisp' option: refill + // g_chartab[] for '-' char if (init_chartab() == FAIL) { did_chartab = TRUE; /* need to restore it below */ errmsg = e_invarg; /* error in value */ diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 6cee102305..cf5bfd60ae 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -4,7 +4,6 @@ /// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite /// replacement. -#include <unistd.h> #include <assert.h> #include <stddef.h> #include <stdbool.h> diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index d12d34d595..cd943c4843 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -3,7 +3,6 @@ #include <stddef.h> #include <assert.h> #include <limits.h> -#include <unistd.h> #include <fcntl.h> #include <errno.h> @@ -26,6 +25,10 @@ #include "nvim/path.h" #include "nvim/strings.h" +#ifdef WIN32 +#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fs.c.generated.h" #endif @@ -923,10 +926,100 @@ bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2) /// @param file_info Pointer to a `FileInfo` /// @return `true` if the `FileID` and the `FileInfo` represent te same file. bool os_fileid_equal_fileinfo(const FileID *file_id, - const FileInfo *file_info) + const FileInfo *file_info) FUNC_ATTR_NONNULL_ALL { return file_id->inode == file_info->stat.st_ino && file_id->device_id == file_info->stat.st_dev; } +#ifdef WIN32 +# include <shlobj.h> + +/// When "fname" is the name of a shortcut (*.lnk) resolve the file it points +/// to and return that name in allocated memory. +/// Otherwise NULL is returned. +char_u * os_resolve_shortcut(char_u *fname) +{ + HRESULT hr; + IPersistFile *ppf = NULL; + OLECHAR wsz[MAX_PATH]; + char_u *rfname = NULL; + int len; + int conversion_result; + IShellLinkW *pslw = NULL; + WIN32_FIND_DATAW ffdw; + + // Check if the file name ends in ".lnk". Avoid calling CoCreateInstance(), + // it's quite slow. + if (fname == NULL) { + return rfname; + } + len = (int)STRLEN(fname); + if (len <= 4 || STRNICMP(fname + len - 4, ".lnk", 4) != 0) { + return rfname; + } + + CoInitialize(NULL); + + // create a link manager object and request its interface + hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + &IID_IShellLinkW, (void **)&pslw); + if (hr == S_OK) { + WCHAR *p; + int conversion_result = utf8_to_utf16((char *)fname, &p); + if (conversion_result != 0) { + EMSG2("utf8_to_utf16 failed: %s", uv_strerror(conversion_result)); + } + + if (p != NULL) { + // Get a pointer to the IPersistFile interface. + hr = pslw->lpVtbl->QueryInterface( + pslw, &IID_IPersistFile, (void **)&ppf); + if (hr != S_OK) { + goto shortcut_errorw; + } + + // "load" the name and resolve the link + hr = ppf->lpVtbl->Load(ppf, p, STGM_READ); + if (hr != S_OK) { + goto shortcut_errorw; + } + +# if 0 // This makes Vim wait a long time if the target does not exist. + hr = pslw->lpVtbl->Resolve(pslw, NULL, SLR_NO_UI); + if (hr != S_OK) { + goto shortcut_errorw; + } +# endif + + // Get the path to the link target. + ZeroMemory(wsz, MAX_PATH * sizeof(WCHAR)); + hr = pslw->lpVtbl->GetPath(pslw, wsz, MAX_PATH, &ffdw, 0); + if (hr == S_OK && wsz[0] != NUL) { + int conversion_result = utf16_to_utf8(wsz, &rfname); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %s", uv_strerror(conversion_result)); + } + } + +shortcut_errorw: + xfree(p); + goto shortcut_end; + } + } + +shortcut_end: + // Release all interface pointers (both belong to the same object) + if (ppf != NULL) { + ppf->lpVtbl->Release(ppf); + } + if (pslw != NULL) { + pslw->lpVtbl->Release(pslw); + } + + CoUninitialize(); + return rfname; +} + +#endif diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 436de030ba..b57a69b82b 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -3,7 +3,6 @@ #include <stdlib.h> #include <string.h> -#include <unistd.h> #include <termios.h> #include <sys/types.h> #include <sys/wait.h> diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index 8a3fcb8f78..0326f33bb5 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -5653,7 +5653,7 @@ msgstr "単語 '%.*s' が %s から削除されました" #: ../spell.c:8117 #, c-format msgid "Word '%.*s' added to %s" -msgstr "%s に単語が追加されました" +msgstr "単語 '%.*s' が %s へ追加されました" #: ../spell.c:8381 msgid "E763: Word characters differ between spell files" diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 886a48e7c5..f8fd7d4ef8 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -1389,6 +1389,10 @@ int vim_regcomp_had_eol(void) return had_eol; } +// variables for parsing reginput +static int at_start; // True when on the first character +static int prev_at_start; // True when on the second character + /* * Parse regular expression, i.e. main body or parenthesized thing. * @@ -1768,6 +1772,7 @@ static char_u *regatom(int *flagp) int c; char_u *p; int extra = 0; + int save_prev_at_start = prev_at_start; *flagp = WORST; /* Tentatively. */ @@ -2143,17 +2148,21 @@ static char_u *regatom(int *flagp) } break; } else if (c == 'l' || c == 'c' || c == 'v') { - if (c == 'l') + if (c == 'l') { ret = regnode(RE_LNUM); - else if (c == 'c') + if (save_prev_at_start) { + at_start = true; + } + } else if (c == 'c') { ret = regnode(RE_COL); - else + } else { ret = regnode(RE_VCOL); - if (ret == JUST_CALC_SIZE) + } + if (ret == JUST_CALC_SIZE) { regsize += 5; - else { - /* put the number and the optional - * comparator after the opcode */ + } else { + // put the number and the optional + // comparator after the opcode regcode = re_put_uint32(regcode, n); *regcode++ = cmp; } @@ -2679,9 +2688,6 @@ static void regoptail(char_u *p, char_u *val) * Functions for getting characters from the regexp input. */ -static int at_start; /* True when on the first character */ -static int prev_at_start; /* True when on the second character */ - /* * Start parsing at "str". */ diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index f97dce9e0d..92dbd693ea 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1096,6 +1096,7 @@ static int nfa_regatom(void) int startc = -1; int endc = -1; int oldstartc = -1; + int save_prev_at_start = prev_at_start; c = getchr(); switch (c) { @@ -1412,18 +1413,22 @@ static int nfa_regatom(void) c = getchr(); } if (c == 'l' || c == 'c' || c == 'v') { - if (c == 'l') - /* \%{n}l \%{n}<l \%{n}>l */ + if (c == 'l') { + // \%{n}l \%{n}<l \%{n}>l EMIT(cmp == '<' ? NFA_LNUM_LT : - cmp == '>' ? NFA_LNUM_GT : NFA_LNUM); - else if (c == 'c') - /* \%{n}c \%{n}<c \%{n}>c */ + cmp == '>' ? NFA_LNUM_GT : NFA_LNUM); + if (save_prev_at_start) { + at_start = true; + } + } else if (c == 'c') { + // \%{n}c \%{n}<c \%{n}>c EMIT(cmp == '<' ? NFA_COL_LT : - cmp == '>' ? NFA_COL_GT : NFA_COL); - else - /* \%{n}v \%{n}<v \%{n}>v */ + cmp == '>' ? NFA_COL_GT : NFA_COL); + } else { + // \%{n}v \%{n}<v \%{n}>v EMIT(cmp == '<' ? NFA_VCOL_LT : - cmp == '>' ? NFA_VCOL_GT : NFA_VCOL); + cmp == '>' ? NFA_VCOL_GT : NFA_VCOL); + } EMIT(n); break; } else if (c == '\'' && n == 0) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 34eef83164..d67142822f 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3416,12 +3416,10 @@ win_line ( /* * Handling of non-printable characters. */ - if (!(chartab[c & 0xff] & CT_PRINT_CHAR)) { - /* - * when getting a character from the file, we may have to - * turn it into something else on the way to putting it - * into "ScreenLines". - */ + if (!vim_isprintc(c)) { + // when getting a character from the file, we may have to + // turn it into something else on the way to putting it + // into "ScreenLines". if (c == TAB && (!wp->w_p_list || lcs_tab1)) { int tab_len = 0; long vcol_adjusted = vcol; // removed showbreak length diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 27855184df..3215f7ea14 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5661,6 +5661,24 @@ int get_syntax_info(int *seqnrp) return current_flags; } + +/// Get the sequence number of the concealed file position. +/// +/// @return seqnr if the file position is concealed, 0 otherwise. +int syn_get_concealed_id(win_T *wp, linenr_T lnum, colnr_T col) +{ + int seqnr; + int syntax_flags; + + (void)syn_get_id(wp, lnum, col, false, NULL, false); + syntax_flags = get_syntax_info(&seqnr); + + if (syntax_flags & HL_CONCEAL) { + return seqnr; + } + return 0; +} + /* * Return conceal substitution character */ diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index fd416b3dcc..6f50c03be9 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -241,6 +241,7 @@ Terminal *terminal_open(TerminalOptions opts) set_option_value((uint8_t *)"wrap", false, NULL, OPT_LOCAL); set_option_value((uint8_t *)"number", false, NULL, OPT_LOCAL); set_option_value((uint8_t *)"relativenumber", false, NULL, OPT_LOCAL); + buf_set_term_title(curbuf, (char *)curbuf->b_ffname); RESET_BINDING(curwin); // Apply TermOpen autocmds so the user can configure the terminal apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); @@ -618,6 +619,17 @@ static int term_movecursor(VTermPos new, VTermPos old, int visible, return 1; } +static void buf_set_term_title(buf_T *buf, char *title) + FUNC_ATTR_NONNULL_ALL +{ + Error err; + api_free_object(dict_set_value(buf->b_vars, + cstr_as_string("term_title"), + STRING_OBJ(cstr_as_string(title)), + false, + &err)); +} + static int term_settermprop(VTermProp prop, VTermValue *val, void *data) { Terminal *term = data; @@ -633,12 +645,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) case VTERM_PROP_TITLE: { buf_T *buf = handle_get_buffer(term->buf_handle); - Error err; - api_free_object(dict_set_value(buf->b_vars, - cstr_as_string("term_title"), - STRING_OBJ(cstr_as_string(val->string)), - false, - &err)); + buf_set_term_title(buf, val->string); break; } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 4b0b5e8d26..4979aae57a 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -14,7 +14,6 @@ SCRIPTS := \ test14.out \ test17.out \ test24.out \ - test30.out \ test32.out \ test37.out \ test40.out \ @@ -33,15 +32,14 @@ SCRIPTS := \ # Tests using runtest.vim.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS = \ - test_cursor_func.res \ test_hardcopy.res \ test_help_tagjump.res \ test_langmap.res \ - test_menu.res \ test_syntax.res \ test_timers.res \ - test_unlet.res \ test_viml.res \ + test_visual.res \ + test_window_id.res \ test_alot.res SCRIPTS_GUI := test16.out diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 1b1f5d7688..74bbf418fa 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -2,6 +2,11 @@ " When the script is successful the .res file will be created. " Errors are appended to the test.log file. " +" To execute only specific test functions, add a second argument. It will be +" matched against the names of the Test_ function. E.g.: +" ../vim -u NONE -S runtest.vim test_channel.vim open_delay +" The output can be found in the "messages" file. +" " The test script may contain anything, only functions that start with " "Test_" are special. These will be invoked and should contain assert " functions. See test_assert.vim for an example. @@ -68,6 +73,11 @@ silent function /^Test_ redir END let tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g')) +" If there is an extra argument filter the function names against it. +if argc() > 1 + let tests = filter(tests, 'v:val =~ argv(1)') +endif + " Execute the tests in alphabetical order. for test in sort(tests) echo 'Executing ' . test diff --git a/src/nvim/testdir/test30.in b/src/nvim/testdir/test30.in deleted file mode 100644 index 56d5d5c6c2..0000000000 --- a/src/nvim/testdir/test30.in +++ /dev/null @@ -1,230 +0,0 @@ -Test for a lot of variations of the 'fileformats' option - -Note: This test will fail if "cat" is not available. - -STARTTEST -:" first write three test files, one in each format -:set fileformat=unix -:set fileformats= -:/^unix/;/eof/-1w! XXUnix -:/^dos/;/eof/-1w! XXDos -:set bin noeol -:$w! XXMac -Gonoeol -:$w! XXEol -:set nobin eol -:enew! -:bwipe XXUnix XXDos XXMac -:" create mixed format files -:if has("win32") -: !copy /b XXUnix+XXDos XXUxDs -: !copy /b XXUnix+XXMac XXUxMac -: !copy /b XXDos+XXMac XXDosMac -: !copy /b XXMac+XXEol XXMacEol -: !copy /b XXUnix+XXDos+XXMac XXUxDsMc -:else -: !cat XXUnix XXDos >XXUxDs -: !cat XXUnix XXMac >XXUxMac -: !cat XXDos XXMac >XXDosMac -: !cat XXMac XXEol >XXMacEol -: !cat XXUnix XXDos XXMac >XXUxDsMc -:endif -:" -:" try reading and writing with 'fileformats' empty -:set fileformat=unix -:e! XXUnix -:w! test.out -:e! XXDos -:w! XXtt01 -:e! XXMac -:w! XXtt02 -:bwipe XXUnix XXDos XXMac -:set fileformat=dos -:e! XXUnix -:w! XXtt11 -:e! XXDos -:w! XXtt12 -:e! XXMac -:w! XXtt13 -:bwipe XXUnix XXDos XXMac -:set fileformat=mac -:e! XXUnix -:w! XXtt21 -:e! XXDos -:w! XXtt22 -:e! XXMac -:w! XXtt23 -:bwipe XXUnix XXDos XXMac -:" -:" try reading and writing with 'fileformats' set to one format -:set fileformats=unix -:e! XXUxDsMc -:w! XXtt31 -:bwipe XXUxDsMc -:set fileformats=dos -:e! XXUxDsMc -:w! XXtt32 -:bwipe XXUxDsMc -:set fileformats=mac -:e! XXUxDsMc -:w! XXtt33 -:bwipe XXUxDsMc -:" -:" try reading and writing with 'fileformats' set to two formats -:set fileformats=unix,dos -:e! XXUxDsMc -:w! XXtt41 -:bwipe XXUxDsMc -:e! XXUxMac -:w! XXtt42 -:bwipe XXUxMac -:e! XXDosMac -:w! XXtt43 -:bwipe XXDosMac -:set fileformats=unix,mac -:e! XXUxDs -:w! XXtt51 -:bwipe XXUxDs -:e! XXUxDsMc -:w! XXtt52 -:bwipe XXUxDsMc -:e! XXDosMac -:w! XXtt53 -:bwipe XXDosMac -:e! XXEol -ggO=&ffs -:=&ff -:w! XXtt54 -:bwipe XXEol -:set fileformats=dos,mac -:e! XXUxDs -:w! XXtt61 -:bwipe XXUxDs -:e! XXUxMac -ggO=&ffs -:=&ff -:w! XXtt62 -:bwipe XXUxMac -:e! XXUxDsMc -:w! XXtt63 -:bwipe XXUxDsMc -:e! XXMacEol -ggO=&ffs -:=&ff -:w! XXtt64 -:bwipe XXMacEol -:" -:" try reading and writing with 'fileformats' set to three formats -:set fileformats=unix,dos,mac -:e! XXUxDsMc -:w! XXtt71 -:bwipe XXUxDsMc -:e! XXEol -ggO=&ffs -:=&ff -:w! XXtt72 -:bwipe XXEol -:set fileformats=mac,dos,unix -:e! XXUxDsMc -:w! XXtt81 -:bwipe XXUxDsMc -:e! XXEol -ggO=&ffs -:=&ff -:w! XXtt82 -:bwipe XXEol -:" try with 'binary' set -:set fileformats=mac,unix,dos -:set binary -:e! XXUxDsMc -:w! XXtt91 -:bwipe XXUxDsMc -:set fileformats=mac -:e! XXUxDsMc -:w! XXtt92 -:bwipe XXUxDsMc -:set fileformats=dos -:e! XXUxDsMc -:w! XXtt93 -:" -:" Append "END" to each file so that we can see what the last written char was. -:set fileformat=unix nobin -ggdGaEND:w >>XXtt01 -:w >>XXtt02 -:w >>XXtt11 -:w >>XXtt12 -:w >>XXtt13 -:w >>XXtt21 -:w >>XXtt22 -:w >>XXtt23 -:w >>XXtt31 -:w >>XXtt32 -:w >>XXtt33 -:w >>XXtt41 -:w >>XXtt42 -:w >>XXtt43 -:w >>XXtt51 -:w >>XXtt52 -:w >>XXtt53 -:w >>XXtt54 -:w >>XXtt61 -:w >>XXtt62 -:w >>XXtt63 -:w >>XXtt64 -:w >>XXtt71 -:w >>XXtt72 -:w >>XXtt81 -:w >>XXtt82 -:w >>XXtt91 -:w >>XXtt92 -:w >>XXtt93 -:" -:" Concatenate the results. -:" Make fileformat of test.out the native fileformat. -:" Add a newline at the end. -:set binary -:e! test.out -:$r XXtt01 -:$r XXtt02 -Go1:$r XXtt11 -:$r XXtt12 -:$r XXtt13 -Go2:$r XXtt21 -:$r XXtt22 -:$r XXtt23 -Go3:$r XXtt31 -:$r XXtt32 -:$r XXtt33 -Go4:$r XXtt41 -:$r XXtt42 -:$r XXtt43 -Go5:$r XXtt51 -:$r XXtt52 -:$r XXtt53 -:$r XXtt54 -Go6:$r XXtt61 -:$r XXtt62 -:$r XXtt63 -:$r XXtt64 -Go7:$r XXtt71 -:$r XXtt72 -Go8:$r XXtt81 -:$r XXtt82 -Go9:$r XXtt91 -:$r XXtt92 -:$r XXtt93 -Go10:$r XXUnix -:set nobinary ff& -:w -:qa! -ENDTEST - -unix -unix -eof - -dos
-dos
-eof - -mac
mac
diff --git a/src/nvim/testdir/test30.ok b/src/nvim/testdir/test30.ok deleted file mode 100644 index b35f4f5904..0000000000 --- a/src/nvim/testdir/test30.ok +++ /dev/null @@ -1,130 +0,0 @@ -unix -unix -dos
-dos
-END -mac
mac
-END -1 -unix
-unix
-END -dos
-dos
-END -mac
mac
-END -2 -unix -unix -
END -dos
-dos
-
END -mac
mac
END -3 -unix -unix -dos
-dos
-mac
mac
-END -unix
-unix
-dos
-dos
-mac
mac
-END -unix -unix -dos
-dos
-mac
mac
END -4 -unix -unix -dos
-dos
-mac
mac
-END -unix -unix -mac
mac
-END -dos
-dos
-mac
mac
-END -5 -unix -unix -dos
-dos
-END -unix -unix -dos
-dos
-mac
mac
-END -dos
-dos
-mac
mac
END -unix,mac:unix -noeol -END -6 -unix
-unix
-dos
-dos
-END -dos,mac:dos
-unix
-unix
-mac
mac
-END -unix
-unix
-dos
-dos
-mac
mac
-END -dos,mac:mac
mac
mac
noeol
END -7 -unix -unix -dos
-dos
-mac
mac
-END -unix,dos,mac:unix -noeol -END -8 -unix -unix -dos
-dos
-mac
mac
-END -mac,dos,unix:mac
noeol
END -9 -unix -unix -dos
-dos
-mac
mac
END -unix -unix -dos
-dos
-mac
mac
END -unix -unix -dos
-dos
-mac
mac
END -10 -unix -unix diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 1d1da94bac..30b8a9ceb8 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -1,3 +1,9 @@ " A series of tests that can run in one Vim invocation. " This makes testing go faster, since Vim doesn't need to restart. +source test_assign.vim +source test_cursor_func.vim +source test_cmdline.vim +source test_menu.vim +source test_popup.vim +source test_unlet.vim diff --git a/src/nvim/testdir/test_assign.vim b/src/nvim/testdir/test_assign.vim new file mode 100644 index 0000000000..3d2e7a8998 --- /dev/null +++ b/src/nvim/testdir/test_assign.vim @@ -0,0 +1,9 @@ +" Test for assignment + +func Test_no_type_checking() + let v = 1 + let v = [1,2,3] + let v = {'a':1, 'b':2} + let v = 3.4 + let v = 'hello' +endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim new file mode 100644 index 0000000000..438b20cc5e --- /dev/null +++ b/src/nvim/testdir/test_cmdline.vim @@ -0,0 +1,120 @@ +" Tests for editing the command line. + +func Test_complete_tab() + call writefile(['testfile'], 'Xtestfile') + call feedkeys(":e Xtest\t\r", "tx") + call assert_equal('testfile', getline(1)) + call delete('Xtestfile') +endfunc + +func Test_complete_list() + " We can't see the output, but at least we check the code runs properly. + call feedkeys(":e test\<C-D>\r", "tx") + call assert_equal('test', expand('%:t')) +endfunc + +func Test_complete_wildmenu() + call writefile(['testfile1'], 'Xtestfile1') + call writefile(['testfile2'], 'Xtestfile2') + set wildmenu + call feedkeys(":e Xtest\t\t\r", "tx") + call assert_equal('testfile2', getline(1)) + + call delete('Xtestfile1') + call delete('Xtestfile2') + set nowildmenu +endfunc + +func Test_getcompletion() + if !has('cmdline_compl') + return + endif + let groupcount = len(getcompletion('', 'event')) + call assert_true(groupcount > 0) + let matchcount = len(getcompletion('File', 'event')) + call assert_true(matchcount > 0) + call assert_true(groupcount > matchcount) + + if has('menu') + source $VIMRUNTIME/menu.vim + let matchcount = len(getcompletion('', 'menu')) + call assert_true(matchcount > 0) + call assert_equal(['File.'], getcompletion('File', 'menu')) + call assert_true(matchcount > 0) + let matchcount = len(getcompletion('File.', 'menu')) + call assert_true(matchcount > 0) + endif + + let l = getcompletion('v:n', 'var') + call assert_true(index(l, 'v:null') >= 0) + + let l = getcompletion('', 'augroup') + call assert_true(index(l, 'END') >= 0) + + let l = getcompletion('', 'behave') + call assert_true(index(l, 'mswin') >= 0) + + let l = getcompletion('', 'color') + call assert_true(index(l, 'default') >= 0) + + let l = getcompletion('', 'command') + call assert_true(index(l, 'sleep') >= 0) + + let l = getcompletion('', 'dir') + call assert_true(index(l, 'sautest') >= 0) + + let l = getcompletion('exe', 'expression') + call assert_true(index(l, 'executable(') >= 0) + + let l = getcompletion('tag', 'function') + call assert_true(index(l, 'taglist(') >= 0) + + let l = getcompletion('run', 'file') + call assert_true(index(l, 'runtest.vim') >= 0) + + let l = getcompletion('ha', 'filetype') + call assert_true(index(l, 'hamster') >= 0) + + let l = getcompletion('z', 'syntax') + call assert_true(index(l, 'zimbu') >= 0) + + let l = getcompletion('jikes', 'compiler') + call assert_true(index(l, 'jikes') >= 0) + + let l = getcompletion('time', 'option') + call assert_true(index(l, 'timeoutlen') >= 0) + + let l = getcompletion('er', 'highlight') + call assert_true(index(l, 'ErrorMsg') >= 0) + + " For others test if the name is recognized. + let names = ['buffer', 'environment', 'file_in_path', + \ 'mapping', 'shellcmd', 'tag', 'tag_listfiles', 'user'] + if has('cscope') + call add(names, 'cscope') + endif + if has('cmdline_hist') + call add(names, 'history') + endif + if has('gettext') + call add(names, 'locale') + endif + if has('profile') + call add(names, 'syntime') + endif + if has('signs') + call add(names, 'sign') + endif + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", "word\tfile\tcmd"], 'Xtags') + + for name in names + let matchcount = len(getcompletion('', name)) + call assert_true(matchcount >= 0, 'No matches for ' . name) + endfor + + call delete('Xtags') + + call assert_fails('call getcompletion("", "burp")', 'E475:') +endfunc diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim new file mode 100644 index 0000000000..78fc81e3d2 --- /dev/null +++ b/src/nvim/testdir/test_popup.vim @@ -0,0 +1,30 @@ +" Test for completion menu + +function! ComplTest() abort + call complete(1, ['source', 'soundfold']) + return '' +endfunction + +function! Test() abort + call complete(1, ['source', 'soundfold']) + return '' +endfunction + +func Test_noinsert_complete() + new + set completeopt+=noinsert + inoremap <F5> <C-R>=ComplTest()<CR> + call feedkeys("i\<F5>soun\<CR>\<CR>\<ESC>.", 'tx') + call assert_equal('soundfold', getline(1)) + call assert_equal('soundfold', getline(2)) + bwipe! + + new + inoremap <F5> <C-R>=Test()<CR> + call feedkeys("i\<F5>\<CR>\<ESC>", 'tx') + call assert_equal('source', getline(1)) + bwipe! + + set completeopt-=noinsert + iunmap <F5> +endfunc diff --git a/src/nvim/testdir/test_viml.vim b/src/nvim/testdir/test_viml.vim index 2d989cdad9..c39c5e6b28 100644 --- a/src/nvim/testdir/test_viml.vim +++ b/src/nvim/testdir/test_viml.vim @@ -55,16 +55,26 @@ endfunction " ExecAsScript - Source a temporary script made from a function. {{{2 " " Make a temporary script file from the function a:funcname, ":source" it, and -" delete it afterwards. +" delete it afterwards. However, if an exception is thrown the file may remain, +" the caller should call DeleteTheScript() afterwards. +let s:script_name = '' function! ExecAsScript(funcname) " Make a script from the function passed as argument. - let script = MakeScript(a:funcname) + let s:script_name = MakeScript(a:funcname) " Source and delete the script. - exec "source" script - call delete(script) + exec "source" s:script_name + call delete(s:script_name) + let s:script_name = '' endfunction +function! DeleteTheScript() + if s:script_name + call delete(s:script_name) + let s:script_name = '' + endif +endfunc + com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) @@ -143,6 +153,7 @@ func Test_endwhile_script() XpathINIT ExecAsScript T1_F Xpath 'F' + call DeleteTheScript() try ExecAsScript T1_G @@ -152,6 +163,7 @@ func Test_endwhile_script() Xpath 'x' endtry Xpath 'G' + call DeleteTheScript() call assert_equal('abcFhijxG', g:Xpath) endfunc @@ -260,6 +272,7 @@ function Test_finish() XpathINIT ExecAsScript T4_F Xpath '5' + call DeleteTheScript() call assert_equal('ab3e3b2c25', g:Xpath) endfunction diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim new file mode 100644 index 0000000000..83bae967e2 --- /dev/null +++ b/src/nvim/testdir/test_visual.vim @@ -0,0 +1,18 @@ +" Tests for Visual mode +if !has('multi_byte') + finish +endif +scriptencoding utf-8 + +if !has('visual') + finish +endif + +func Test_block_shift_multibyte() + split + call setline(1, ['xヹxxx', 'ヹxxx']) + exe "normal 1G0l\<C-V>jl>" + call assert_equal('x ヹxxx', getline(1)) + call assert_equal(' ヹxxx', getline(2)) + q! +endfunc diff --git a/src/nvim/testdir/test_window_id.vim b/src/nvim/testdir/test_window_id.vim new file mode 100644 index 0000000000..b9e9f45c49 --- /dev/null +++ b/src/nvim/testdir/test_window_id.vim @@ -0,0 +1,71 @@ +" Test using the window ID. + +func Test_win_getid() + edit one + let id1 = win_getid() + split two + let id2 = win_getid() + split three + let id3 = win_getid() + tabnew + edit four + let id4 = win_getid() + split five + let id5 = win_getid() + tabnext + + wincmd w + call assert_equal("two", expand("%")) + call assert_equal(id2, win_getid()) + let nr2 = winnr() + wincmd w + call assert_equal("one", expand("%")) + call assert_equal(id1, win_getid()) + let nr1 = winnr() + wincmd w + call assert_equal("three", expand("%")) + call assert_equal(id3, win_getid()) + let nr3 = winnr() + tabnext + call assert_equal("five", expand("%")) + call assert_equal(id5, win_getid()) + let nr5 = winnr() + wincmd w + call assert_equal("four", expand("%")) + call assert_equal(id4, win_getid()) + let nr4 = winnr() + tabnext + + exe nr1 . "wincmd w" + call assert_equal(id1, win_getid()) + exe nr2 . "wincmd w" + call assert_equal(id2, win_getid()) + exe nr3 . "wincmd w" + call assert_equal(id3, win_getid()) + tabnext + exe nr4 . "wincmd w" + call assert_equal(id4, win_getid()) + exe nr5 . "wincmd w" + call assert_equal(id5, win_getid()) + + call win_gotoid(id2) + call assert_equal("two", expand("%")) + call win_gotoid(id4) + call assert_equal("four", expand("%")) + call win_gotoid(id1) + call assert_equal("one", expand("%")) + call win_gotoid(id5) + call assert_equal("five", expand("%")) + + call assert_equal(0, win_id2win(9999)) + call assert_equal(nr5, win_id2win(id5)) + call assert_equal(0, win_id2win(id1)) + tabnext + call assert_equal(nr1, win_id2win(id1)) + + call assert_equal([0, 0], win_id2tabwin(9999)) + call assert_equal([1, nr2], win_id2tabwin(id2)) + call assert_equal([2, nr4], win_id2tabwin(id4)) + + only! +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 53551d81cf..aa337f9fae 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -75,29 +75,608 @@ static char *features[] = { // clang-format off static int included_patches[] = { + // 2200, + // 2199, + // 2198, + // 2197, + // 2196, + // 2195, + // 2194, + // 2193, + // 2192, + // 2191, + // 2190, + // 2189, + // 2188, + // 2187, + // 2186, + // 2185, + // 2184, + // 2183, + // 2182, + // 2181, + // 2180, + // 2179, + // 2178, + // 2177, + // 2176, + // 2175, + // 2174, + // 2173, + // 2172, + // 2171, + // 2170, + // 2169, + // 2168, + // 2167, + // 2166, + // 2165, + // 2164, + // 2163, + // 2162, + // 2161, + // 2160, + // 2159, + // 2158, + // 2157, + // 2156, + // 2155, + // 2154, + // 2153, + // 2152, + // 2151, + // 2150, + // 2149, + // 2148, + // 2147, + // 2146, + // 2145, + // 2144, + // 2143, + // 2142, + // 2141, + // 2140, + // 2139, + // 2138, + // 2137, + // 2136, + // 2135, + // 2134, + // 2133, + // 2132, + // 2131, + // 2130, + // 2129, + // 2128, + // 2127, + // 2126, + // 2125, + // 2124, + // 2123, + // 2122, + // 2121, + // 2120, + // 2119, + // 2118, + // 2117, + // 2116, + // 2115, + // 2114, + // 2113, + // 2112, + // 2111, + // 2110, + // 2109, + // 2108, + // 2107, + // 2106, + // 2105 NA + // 2104, + // 2103, + // 2102 NA + // 2101, + // 2100, + // 2099, + // 2098, + // 2097, + // 2096, + // 2095, + // 2094, + // 2093, + // 2092 NA + // 2091 NA + // 2090, + // 2089 NA + // 2088, + // 2087, + // 2086, + // 2085, + // 2084, + // 2083, + // 2082, + // 2081, + // 2080, + // 2079 NA + // 2078 NA + // 2077, + // 2076, + // 2075, + // 2074, + // 2073, + // 2072, + // 2071, + // 2070 NA + // 2069, + // 2068, + // 2067, + 2066, + // 2065, + // 2064, + // 2063 NA + // 2062, + // 2061, + // 2060 NA + // 2059 NA + // 2058, + // 2057 NA + // 2056 NA + // 2055 NA + // 2054 NA + // 2053 NA + // 2052 NA + // 2051, + // 2050, + // 2049, + // 2048 NA + // 2047, + // 2046, + // 2045 NA + // 2044, + // 2043, + // 2042 NA + // 2041 NA + // 2040 NA + // 2039 NA + // 2038 NA + // 2037 NA + // 2036, + // 2035 NA + // 2034 NA + // 2033, + // 2032 NA + // 2031, + // 2030 NA + // 2029, + // 2028, + // 2027 NA + // 2026 NA + // 2025 NA + // 2024, + // 2023, + // 2022, + // 2021, + // 2020 NA + // 2019, + // 2018, + // 2017, + // 2016 NA + // 2015, + 2014, + 2013, + 2012, + 2011, + // 2010, + // 2009, + // 2008, + // 2007, + // 2006, + // 2005, + // 2004 NA + // 2003 NA + // 2002, + // 2001 NA + // 2000, + // 1999, + // 1998 NA + // 1997, + // 1996, + // 1995 NA + // 1994, + // 1993, + // 1992, + // 1991, + // 1990, + // 1989, + // 1988 NA + // 1987 NA + // 1986, + // 1985 NA + // 1984, + // 1983 NA + // 1982 NA + // 1981, + // 1980, + // 1979, + // 1978, + // 1977, + // 1976, + // 1975, + // 1974 NA 1973, + // 1972, + // 1971, + // 1970, + // 1969 NA + // 1968, + // 1967, + // 1966, + // 1965 NA + // 1964, + // 1963 NA + // 1962, + // 1961, 1960, + // 1959 NA + // 1958 NA + // 1957 NA + // 1956, + // 1955, + // 1954, + // 1953, + // 1952, + // 1951 NA + // 1950, + // 1949, + // 1948, + // 1947 NA + // 1946 NA + // 1945, + // 1944 NA + // 1943 NA + // 1942 NA + // 1941, + // 1940, + // 1939 NA + // 1938 NA + // 1937, + // 1936, + // 1935 NA + // 1934 NA + // 1933 NA + // 1932 NA + // 1931 NA + // 1930 NA + // 1929 NA + // 1928, + // 1927 NA + // 1926 NA + // 1925 NA + // 1924 NA + // 1923, + // 1922 NA + // 1921 NA + // 1920 NA + // 1919 NA + // 1918 NA + // 1917 NA + // 1916 NA + // 1915 NA + // 1914, + // 1913, + // 1912, + // 1911, + // 1910, + // 1909, + // 1908 NA + // 1907, + // 1906 NA + // 1905, + // 1904, + // 1903, + // 1902 NA + // 1901 NA + // 1900, + // 1899 NA + // 1898, + // 1897, + // 1896, + // 1895, + // 1894 NA + // 1893, + // 1892 NA + // 1891 NA + // 1890 NA + // 1889, + // 1888, + // 1887 NA + // 1886 NA + // 1885 NA + // 1884, + // 1883 NA + // 1882, + // 1881, + // 1880 NA + // 1879 NA + // 1878 NA + // 1877 NA + // 1876, + // 1875, + // 1874 NA + // 1873 NA + // 1872 NA + // 1871, + // 1870 NA + // 1869 NA + // 1868, + // 1867, + // 1866, + // 1865 NA + // 1864 NA + // 1863 NA + // 1862, + // 1861, + // 1860 NA + // 1859 NA + // 1858 NA + // 1857 NA + // 1856 NA + // 1855 NA + // 1854 NA + // 1853 NA + // 1852 NA + // 1851, + // 1850 NA + // 1849 NA + // 1848 NA + // 1847, + // 1846 NA + // 1845 NA + // 1844, + // 1843 NA + // 1842, + // 1841, 1840, + // 1839, + // 1838, + // 1837, + // 1836, + // 1835, + // 1834, + // 1833, 1832, 1831, + // 1830 NA + // 1829 NA + // 1828 NA + // 1827 NA + // 1826 NA + // 1825 NA + // 1824 NA + // 1823, + // 1822 NA + // 1821, + // 1820, + // 1819 NA + // 1818, + // 1817 NA + // 1816, + // 1815, + // 1814 NA + // 1813, + // 1812, + // 1811, + // 1810 NA 1809, 1808, + // 1807 NA 1806, + // 1805, + // 1804, + // 1803 NA + // 1802, + // 1801 NA + // 1800 NA 1799, + // 1798 NA + // 1797 NA + // 1796 NA + // 1795 NA + // 1794, + // 1793, + // 1792 NA + // 1791 NA + // 1790 NA + // 1789 NA + // 1789 NA + // 1788 NA + // 1787 NA + // 1786 NA + // 1785, + // 1784 NA + // 1783, + // 1782, + // 1781, + // 1780, + // 1779, + // 1778 NA + // 1777 NA + // 1776 NA + // 1775 NA + // 1774 NA + // 1773 NA + // 1772 NA + // 1771 NA + // 1770, + // 1769, + // 1768, + // 1767 NA + // 1766 NA + // 1765, + // 1764 NA + // 1763, + // 1762, + // 1761, + // 1760 NA + // 1759, + // 1758, 1757, + // 1756 NA 1755, + // 1754, 1753, + // 1753, + // 1752, + // 1751, + // 1750, + // 1749 NA + // 1748, + // 1747 NA + // 1746 NA + // 1745 NA + // 1744 NA + // 1743 NA + // 1742, + // 1741, + // 1740, + // 1739, + // 1738, + // 1737 NA + // 1736 NA + // 1735, + // 1734, + // 1733 NA + 1732, + // 1731, + // 1730, + // 1729 NA 1728, + // 1727, + // 1726 NA + // 1725 NA + // 1724 NA + // 1723, + // 1722 NA + // 1721 NA + // 1720, + // 1719, + // 1718, + // 1717 NA 1716, + // 1715, + // 1714, + // 1713 NA 1712, + // 1711, + // 1710, + // 1709 NA + // 1708, + // 1707, + // 1706 NA + // 1705 NA + // 1704, + 1703, + // 1702, + // 1701, + // 1700, + // 1699, + // 1698 NA + // 1697, + // 1696, 1695, + // 1694 NA + // 1693 NA + // 1692, + // 1691, + // 1690 NA + // 1689 NA + // 1688 NA + // 1687 NA + // 1686, + // 1685, + // 1684 NA + // 1683 NA + 1682, + // 1681, + // 1680 NA + // 1679, + // 1678 NA + // 1677 NA + 1676, + 1675, + // 1674 NA + 1673, + // 1672 NA + // 1671, + // 1670, + // 1669 NA + // 1668 NA + // 1667 NA + // 1666 NA + // 1665 NA + // 1664, + 1663, + // 1662 NA + // 1661 NA + // 1660, + // 1659 NA + // 1658, + // 1657 NA + // 1656, + // 1655 NA 1654, + // 1653, 1652, + // 1651 NA + // 1650, 1649, + // 1648, + // 1647, + // 1646 NA + // 1645, + // 1644, 1643, + // 1642, 1641, + // 1640, + // 1639, + // 1638, + // 1637 NA + // 1636 NA + // 1635 NA + // 1634, + // 1633 NA + // 1632 NA + // 1631 NA + // 1630, + // 1629, + // 1628 NA + // 1627 NA + // 1626 NA + // 1625 NA // 1624 NA - + // 1623 NA + // 1622 NA + // 1621 NA + // 1620, + // 1619, + // 1618 NA + // 1617 NA + // 1616 NA + // 1615 NA + // 1614, + // 1613 NA + // 1612 NA + // 1611 NA + // 1610 NA + // 1609 NA + // 1608, + // 1607, + // 1606, + // 1605, + // 1604, + 1603, + // 1602 NA + // 1601 NA // 1600 NA // 1599 NA // 1598 NA @@ -117,7 +696,6 @@ static int included_patches[] = { // 1584 NA // 1583 NA // 1582, - // 1581, // 1580, // 1579 NA @@ -142,7 +720,7 @@ static int included_patches[] = { // 1560 NA // 1559, // 1558, - // 1557, + 1557, // 1556 NA // 1555 NA 1554, @@ -151,9 +729,9 @@ static int included_patches[] = { 1551, 1550, // 1549, - // 1548, + 1548, // 1547, - // 1546, + 1546, // 1545 NA // 1544 NA // 1543 NA @@ -208,7 +786,7 @@ static int included_patches[] = { // 1494, // 1493 NA 1492, - // 1491, + 1491, // 1490 NA // 1489 NA // 1488 NA @@ -243,7 +821,7 @@ static int included_patches[] = { // 1459 NA // 1458 NA // 1457 NA - // 1456, + // 1456 NA // 1455 NA // 1454 NA // 1453 NA @@ -334,7 +912,7 @@ static int included_patches[] = { // 1368 NA // 1367 NA 1366, - // 1365, + 1365, // 1364 NA // 1363 NA // 1362 NA @@ -394,7 +972,7 @@ static int included_patches[] = { // 1308 NA // 1307 NA // 1306 NA - // 1305, + 1305, 1304, // 1303 NA // 1302 NA @@ -426,7 +1004,7 @@ static int included_patches[] = { 1276, // 1275 NA // 1274 NA - // 1273, + // 1273 NA // 1272 NA 1271, // 1270 NA @@ -462,7 +1040,7 @@ static int included_patches[] = { // 1240 NA // 1239 NA // 1238 NA - // 1237, + 1237, 1236, // 1235 NA // 1234 NA @@ -513,14 +1091,14 @@ static int included_patches[] = { // 1189 NA // 1188 NA // 1187 NA - // 1186, + // 1186 NA // 1185 NA // 1184 NA // 1183 NA // 1182 NA 1181, 1180, - // 1179, + 1179, 1178, // 1177 NA // 1176 NA @@ -552,7 +1130,7 @@ static int included_patches[] = { 1150, 1149, // 1148 NA - // 1147, + 1147, // 1146 NA // 1145 NA 1144, @@ -625,7 +1203,7 @@ static int included_patches[] = { // 1077 NA 1076, 1075, - // 1074 NA, + // 1074 NA // 1073 NA 1072, 1071, @@ -668,7 +1246,7 @@ static int included_patches[] = { 1034, // 1033 NA 1032, - // 1031 NA, + // 1031 NA 1030, 1029, // 1028 NA @@ -689,15 +1267,15 @@ static int included_patches[] = { 1013, // 1012 NA // 1011 NA - // 1010 NA, + // 1010 NA // 1009 NA // 1008 NA 1007, 1006, - // 1005 NA, - // 1004 NA, - // 1003 NA, - // 1002 NA, + // 1005 NA + // 1004 NA + // 1003 NA + // 1002 NA 1001, 1000, // 999 NA diff --git a/src/nvim/window.c b/src/nvim/window.c index 1298248f1e..350b54d595 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3296,8 +3296,11 @@ void tabpage_move(int nr) tabpage_T *tp; tabpage_T *tp_dst; - if (first_tabpage->tp_next == NULL) + assert(curtab != NULL); + + if (first_tabpage->tp_next == NULL) { return; + } for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) { ++n; @@ -3680,6 +3683,8 @@ win_T *buf_jump_open_tab(buf_T *buf) return NULL; } +static int last_win_id = 0; + /* * Allocate a window structure and link it in the window list when "hidden" is * FALSE. @@ -3692,6 +3697,7 @@ static win_T *win_alloc(win_T *after, int hidden) win_T *new_wp = xcalloc(1, sizeof(win_T)); handle_register_window(new_wp); win_alloc_lines(new_wp); + new_wp->w_id = ++last_win_id; /* init w: variables */ new_wp->w_vars = dict_alloc(); @@ -5671,3 +5677,93 @@ static bool frame_check_width(frame_T *topfrp, int width) } return true; } + +int win_getid(typval_T *argvars) +{ + if (argvars[0].v_type == VAR_UNKNOWN) { + return curwin->w_id; + } + int winnr = get_tv_number(&argvars[0]); + win_T *wp; + if (winnr > 0) { + if (argvars[1].v_type == VAR_UNKNOWN) { + wp = firstwin; + } else { + tabpage_T *tp; + int tabnr = get_tv_number(&argvars[1]); + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + if (--tabnr == 0) { + break; + } + } + if (tp == NULL) { + return -1; + } + wp = tp->tp_firstwin; + } + for ( ; wp != NULL; wp = wp->w_next) { + if (--winnr == 0) { + return wp->w_id; + } + } + } + return 0; +} + +int win_gotoid(typval_T *argvars) +{ + win_T *wp; + tabpage_T *tp; + int id = get_tv_number(&argvars[0]); + + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + for (wp = tp == curtab ? firstwin : tp->tp_firstwin; + wp != NULL; wp = wp->w_next) { + if (wp->w_id == id) { + goto_tabpage_win(tp, wp); + return 1; + } + } + } + return 0; +} + +void win_id2tabwin(typval_T *argvars, list_T *list) +{ + win_T *wp; + tabpage_T *tp; + int winnr = 1; + int tabnr = 1; + int id = get_tv_number(&argvars[0]); + + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + for (wp = tp == curtab ? firstwin : tp->tp_firstwin; + wp != NULL; wp = wp->w_next) { + if (wp->w_id == id) { + list_append_number(list, tabnr); + list_append_number(list, winnr); + return; + } + winnr++; + } + tabnr++; + winnr = 1; + } + list_append_number(list, 0); + list_append_number(list, 0); +} + +int win_id2win(typval_T *argvars) +{ + win_T *wp; + int nr = 1; + int id = get_tv_number(&argvars[0]); + + for (wp = firstwin; wp != NULL; wp = wp->w_next) { + if (wp->w_id == id) { + return nr; + } + nr++; + } + return 0; +} |