diff options
Diffstat (limited to 'src')
215 files changed, 11190 insertions, 4112 deletions
diff --git a/src/clint.py b/src/clint.py index 944946bd16..28f6031a57 100755 --- a/src/clint.py +++ b/src/clint.py @@ -651,6 +651,9 @@ def Error(filename, linenum, category, confidence, message): elif _cpplint_state.output_format == 'eclipse': sys.stdout.write('%s:%s: warning: %s [%s] [%d]\n' % ( filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'gh_action': + sys.stdout.write('::error file=%s,line=%s::%s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) else: sys.stdout.write('%s:%s: %s [%s] [%d]\n' % ( filename, linenum, message, category, confidence)) @@ -3053,7 +3056,7 @@ def ParseArguments(args): if opt == '--help': PrintUsage(None) elif opt == '--output': - if val not in ('emacs', 'vs7', 'eclipse'): + if val not in ('emacs', 'vs7', 'eclipse', 'gh_action'): PrintUsage('The only allowed output formats are emacs,' ' vs7 and eclipse.') output_format = val diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 77fd84bd73..384e672529 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -221,31 +221,17 @@ function(get_preproc_output varname iname) endif() endfunction() -# Handle generating version from Git. -set(use_git_version 0) -if(NVIM_VERSION_MEDIUM) - message(STATUS "NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") -elseif(EXISTS ${PROJECT_SOURCE_DIR}/.git) - find_program(GIT_EXECUTABLE git) - if(GIT_EXECUTABLE) - message(STATUS "Using NVIM_VERSION_MEDIUM from Git") - set(use_git_version 1) - else() - message(STATUS "Skipping version-string generation (cannot find git)") - endif() -endif() -if(use_git_version) - # Create a update_version_stamp target to update the version during build. - file(RELATIVE_PATH relbuild "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") - add_custom_target(update_version_stamp ALL - COMMAND ${LUA_PRG} scripts/update_version_stamp.lua - ${relbuild}/cmake.config/auto/versiondef_git.h - "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}${NVIM_VERSION_PRERELEASE}" - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - BYPRODUCTS ${CMAKE_BINARY_DIR}/cmake.config/auto/versiondef_git.h) -else() - file(WRITE ${CMAKE_BINARY_DIR}/cmake.config/auto/versiondef_git.h "") -endif() +set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h) +add_custom_target(update_version_stamp + COMMAND ${CMAKE_COMMAND} + -DNVIM_VERSION_MAJOR=${NVIM_VERSION_MAJOR} + -DNVIM_VERSION_MINOR=${NVIM_VERSION_MINOR} + -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH} + -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE} + -DOUTPUT=${NVIM_VERSION_GIT_H} + -P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + BYPRODUCTS ${NVIM_VERSION_GIT_H}) # NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers # NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources @@ -279,9 +265,9 @@ foreach(sfile ${NVIM_SOURCES} get_preproc_output(PREPROC_OUTPUT ${gf_i}) set(depends "${HEADER_GENERATOR}" "${sfile}") - if(use_git_version AND "${f}" STREQUAL "version.c") + if("${f}" STREQUAL "version.c") # Ensure auto/versiondef_git.h exists after "make clean". - list(APPEND depends update_version_stamp) + list(APPEND depends "${NVIM_VERSION_GIT_H}") endif() add_custom_command( OUTPUT "${gf_c_h}" "${gf_h_h}" @@ -777,6 +763,12 @@ add_custom_command( add_download(${LINT_SUPPRESS_FILE} ${LINT_SUPPRESS_URL} off) +if(CI_BUILD) + set(LINT_OUTPUT_FORMAT gh_action) +else() + set(LINT_OUTPUT_FORMAT vs7) +endif() + set(LINT_NVIM_REL_SOURCES) foreach(sfile ${LINT_NVIM_SOURCES}) get_test_target("" "${sfile}" r suffix) @@ -786,7 +778,7 @@ foreach(sfile ${LINT_NVIM_SOURCES}) set(touch_file "${TOUCHES_DIR}/ran-clint-${suffix}") add_custom_command( OUTPUT ${touch_file} - COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} ${rsfile} + COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} --output=${LINT_OUTPUT_FORMAT} ${rsfile} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} DEPENDS ${LINT_PRG} ${sfile} ${LINT_SUPPRESSES_TOUCH_FILE} @@ -806,7 +798,7 @@ add_glob_targets( add_custom_target( lintcfull COMMAND - ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} ${LINT_NVIM_REL_SOURCES} + ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} --output=${LINT_OUTPUT_FORMAT} ${LINT_NVIM_REL_SOURCES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE} lintuncrustify ) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 1504004c6c..806b649ce6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1156,17 +1156,17 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err) return res; } - pos_T *pos = getmark_buf(buf, *name.data, false); + fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data); - // pos point to NULL when there's no mark with name - if (pos == NULL) { + // fm is NULL when there's no mark with the given name + if (fm == NULL) { api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'", *name.data); return res; } - // pos->lnum is 0 when the mark is not valid in the buffer, or is not set. - if (pos->lnum != 0) { + // mark.lnum is 0 when the mark is not valid in the buffer, or is not set. + if (fm->mark.lnum != 0 && fm->fnum == buf->handle) { // since the mark belongs to the buffer delete it. res = set_mark(buf, name, 0, 0, err); } @@ -1239,26 +1239,25 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } - pos_T *posp; + fmark_T *fm; + pos_T pos; char mark = *name.data; - try_start(); - bufref_T save_buf; - switch_buffer(&save_buf, buf); - posp = getmark(mark, false); - restore_buffer(&save_buf); - - if (try_end(err)) { - return rv; - } - - if (posp == NULL) { + fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark); + if (fm == NULL) { api_set_error(err, kErrorTypeValidation, "Invalid mark name"); return rv; } + // (0, 0) uppercase/file mark set in another buffer. + if (fm->fnum != buf->handle) { + pos.lnum = 0; + pos.col = 0; + } else { + pos = fm->mark; + } - ADD(rv, INTEGER_OBJ(posp->lnum)); - ADD(rv, INTEGER_OBJ(posp->col)); + ADD(rv, INTEGER_OBJ(pos.lnum)); + ADD(rv, INTEGER_OBJ(pos.col)); return rv; } diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index e6a055995e..4c2404a0d8 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -54,6 +54,7 @@ /// - force: (boolean) Whether filter is inverted or not. /// - silent: (boolean) |:silent|. /// - emsg_silent: (boolean) |:silent!|. +/// - unsilent: (boolean) |:unsilent|. /// - sandbox: (boolean) |:sandbox|. /// - noautocmd: (boolean) |:noautocmd|. /// - browse: (boolean) |:browse|. @@ -232,6 +233,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT)); PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); + PUT(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab)); @@ -598,6 +600,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'"); OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'"); + OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods.silent, false, "'mods.unsilent'"); OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'"); OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'"); OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'"); @@ -722,6 +725,10 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin kv_concat(cmdline, "silent "); } + if (cmdinfo->cmdmod.cmod_flags & CMOD_UNSILENT) { + kv_concat(cmdline, "unsilent "); + } + switch (cmdinfo->cmdmod.cmod_split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { case WSP_ABOVE: kv_concat(cmdline, "aboveleft "); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 918fe028a8..21319fb7a6 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -181,6 +181,7 @@ return { cmd_mods = { "silent"; "emsg_silent"; + "unsilent"; "filter"; "sandbox"; "noautocmd"; diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 8c174fc129..4ed676e613 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -504,6 +504,7 @@ static int access_option_value(char *key, long *numval, char **stringval, int op static int access_option_value_for(char *key, long *numval, char **stringval, int opt_flags, int opt_type, void *from, bool get, Error *err) { + bool need_switch = false; switchwin_T switchwin; aco_save_T aco; int result = 0; @@ -511,24 +512,32 @@ static int access_option_value_for(char *key, long *numval, char **stringval, in try_start(); switch (opt_type) { case SREQ_WIN: - if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) - == FAIL) { - restore_win_noblock(&switchwin, true); - if (try_end(err)) { + need_switch = (win_T *)from != curwin; + if (need_switch) { + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) + == FAIL) { + restore_win_noblock(&switchwin, true); + if (try_end(err)) { + return result; + } + api_set_error(err, kErrorTypeException, "Problem while switching windows"); return result; } - api_set_error(err, - kErrorTypeException, - "Problem while switching windows"); - return result; } result = access_option_value(key, numval, stringval, opt_flags, get, err); - restore_win_noblock(&switchwin, true); + if (need_switch) { + restore_win_noblock(&switchwin, true); + } break; case SREQ_BUF: - aucmd_prepbuf(&aco, (buf_T *)from); + need_switch = (buf_T *)from != curbuf; + if (need_switch) { + aucmd_prepbuf(&aco, (buf_T *)from); + } result = access_option_value(key, numval, stringval, opt_flags, get, err); - aucmd_restbuf(&aco); + if (need_switch) { + aucmd_restbuf(&aco); + } break; case SREQ_GLOBAL: result = access_option_value(key, numval, stringval, opt_flags, get, err); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 436bcd5212..fad75d55be 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -916,7 +916,7 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err) } assert(INT32_MIN <= line && line <= INT32_MAX); pos_T pos = { (linenr_T)line, (int)col, (int)col }; - res = setmark_pos(*name.data, &pos, buf->handle); + res = setmark_pos(*name.data, &pos, buf->handle, NULL); if (!res) { if (deleting) { api_set_error(err, kErrorTypeException, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f91b74cd31..56516b2ac7 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2027,20 +2027,20 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) return rv; } - xfmark_T mark = get_global_mark(*name.data); - pos_T pos = mark.fmark.mark; + xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer + pos_T pos = mark->fmark.mark; bool allocated = false; int bufnr; char *filename; // Marks are from an open buffer it fnum is non zero - if (mark.fmark.fnum != 0) { - bufnr = mark.fmark.fnum; + if (mark->fmark.fnum != 0) { + bufnr = mark->fmark.fnum; filename = (char *)buflist_nr2name(bufnr, true, true); allocated = true; // Marks comes from shada } else { - filename = mark.fname; + filename = mark->fname; bufnr = 0; } diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 130ce65b86..06536e6e2b 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -5,6 +5,13 @@ /// /// Functions for Arabic language. /// +/// Author: Nadim Shaikli & Isam Bayazidi +/// Farsi support and restructuring to make adding new letters easier by Ali +/// Gholami Rudi. Further work by Ameretat Reith. + +/// Sorted list of unicode Arabic characters. Each entry holds the +/// presentation forms of a letter. +/// /// Arabic characters are categorized into following types: /// /// Isolated - iso-8859-6 form char denoted with a_* @@ -19,12 +26,7 @@ #include "nvim/ascii.h" #include "nvim/vim.h" -// Arabic ISO-10646-1 character set definition - -// Arabic ISO-8859-6 (subset of 10646; 0600 - 06FF) -#define a_COMMA 0x060C -#define a_SEMICOLON 0x061B -#define a_QUESTION 0x061F +// Unicode values for Arabic characters. #define a_HAMZA 0x0621 #define a_ALEF_MADDA 0x0622 #define a_ALEF_HAMZA_ABOVE 0x0623 @@ -62,7 +64,6 @@ #define a_WAW 0x0648 #define a_ALEF_MAKSURA 0x0649 #define a_YEH 0x064a - #define a_FATHATAN 0x064b #define a_DAMMATAN 0x064c #define a_KASRATAN 0x064d @@ -71,168 +72,17 @@ #define a_KASRA 0x0650 #define a_SHADDA 0x0651 #define a_SUKUN 0x0652 - #define a_MADDA_ABOVE 0x0653 #define a_HAMZA_ABOVE 0x0654 #define a_HAMZA_BELOW 0x0655 -#define a_ZERO 0x0660 -#define a_ONE 0x0661 -#define a_TWO 0x0662 -#define a_THREE 0x0663 -#define a_FOUR 0x0664 -#define a_FIVE 0x0665 -#define a_SIX 0x0666 -#define a_SEVEN 0x0667 -#define a_EIGHT 0x0668 -#define a_NINE 0x0669 -#define a_PERCENT 0x066a -#define a_DECIMAL 0x066b -#define a_THOUSANDS 0x066c -#define a_STAR 0x066d -#define a_MINI_ALEF 0x0670 -// Rest of 8859-6 does not relate to Arabic +#define a_PEH 0x067e +#define a_TCHEH 0x0686 +#define a_JEH 0x0698 +#define a_FKAF 0x06a9 +#define a_GAF 0x06af +#define a_FYEH 0x06cc -// Arabic Presentation Form-B (subset of 10646; FE70 - FEFF) -// -// s -> isolated -// i -> initial -// m -> medial -// f -> final -#define a_s_FATHATAN 0xfe70 -#define a_m_TATWEEL_FATHATAN 0xfe71 -#define a_s_DAMMATAN 0xfe72 - -#define a_s_KASRATAN 0xfe74 - -#define a_s_FATHA 0xfe76 -#define a_m_FATHA 0xfe77 -#define a_s_DAMMA 0xfe78 -#define a_m_DAMMA 0xfe79 -#define a_s_KASRA 0xfe7a -#define a_m_KASRA 0xfe7b -#define a_s_SHADDA 0xfe7c -#define a_m_SHADDA 0xfe7d -#define a_s_SUKUN 0xfe7e -#define a_m_SUKUN 0xfe7f - -#define a_s_HAMZA 0xfe80 -#define a_s_ALEF_MADDA 0xfe81 -#define a_f_ALEF_MADDA 0xfe82 -#define a_s_ALEF_HAMZA_ABOVE 0xfe83 -#define a_f_ALEF_HAMZA_ABOVE 0xfe84 -#define a_s_WAW_HAMZA 0xfe85 -#define a_f_WAW_HAMZA 0xfe86 -#define a_s_ALEF_HAMZA_BELOW 0xfe87 -#define a_f_ALEF_HAMZA_BELOW 0xfe88 -#define a_s_YEH_HAMZA 0xfe89 -#define a_f_YEH_HAMZA 0xfe8a -#define a_i_YEH_HAMZA 0xfe8b -#define a_m_YEH_HAMZA 0xfe8c -#define a_s_ALEF 0xfe8d -#define a_f_ALEF 0xfe8e -#define a_s_BEH 0xfe8f -#define a_f_BEH 0xfe90 -#define a_i_BEH 0xfe91 -#define a_m_BEH 0xfe92 -#define a_s_TEH_MARBUTA 0xfe93 -#define a_f_TEH_MARBUTA 0xfe94 -#define a_s_TEH 0xfe95 -#define a_f_TEH 0xfe96 -#define a_i_TEH 0xfe97 -#define a_m_TEH 0xfe98 -#define a_s_THEH 0xfe99 -#define a_f_THEH 0xfe9a -#define a_i_THEH 0xfe9b -#define a_m_THEH 0xfe9c -#define a_s_JEEM 0xfe9d -#define a_f_JEEM 0xfe9e -#define a_i_JEEM 0xfe9f -#define a_m_JEEM 0xfea0 -#define a_s_HAH 0xfea1 -#define a_f_HAH 0xfea2 -#define a_i_HAH 0xfea3 -#define a_m_HAH 0xfea4 -#define a_s_KHAH 0xfea5 -#define a_f_KHAH 0xfea6 -#define a_i_KHAH 0xfea7 -#define a_m_KHAH 0xfea8 -#define a_s_DAL 0xfea9 -#define a_f_DAL 0xfeaa -#define a_s_THAL 0xfeab -#define a_f_THAL 0xfeac -#define a_s_REH 0xfead -#define a_f_REH 0xfeae -#define a_s_ZAIN 0xfeaf -#define a_f_ZAIN 0xfeb0 -#define a_s_SEEN 0xfeb1 -#define a_f_SEEN 0xfeb2 -#define a_i_SEEN 0xfeb3 -#define a_m_SEEN 0xfeb4 -#define a_s_SHEEN 0xfeb5 -#define a_f_SHEEN 0xfeb6 -#define a_i_SHEEN 0xfeb7 -#define a_m_SHEEN 0xfeb8 -#define a_s_SAD 0xfeb9 -#define a_f_SAD 0xfeba -#define a_i_SAD 0xfebb -#define a_m_SAD 0xfebc -#define a_s_DAD 0xfebd -#define a_f_DAD 0xfebe -#define a_i_DAD 0xfebf -#define a_m_DAD 0xfec0 -#define a_s_TAH 0xfec1 -#define a_f_TAH 0xfec2 -#define a_i_TAH 0xfec3 -#define a_m_TAH 0xfec4 -#define a_s_ZAH 0xfec5 -#define a_f_ZAH 0xfec6 -#define a_i_ZAH 0xfec7 -#define a_m_ZAH 0xfec8 -#define a_s_AIN 0xfec9 -#define a_f_AIN 0xfeca -#define a_i_AIN 0xfecb -#define a_m_AIN 0xfecc -#define a_s_GHAIN 0xfecd -#define a_f_GHAIN 0xfece -#define a_i_GHAIN 0xfecf -#define a_m_GHAIN 0xfed0 -#define a_s_FEH 0xfed1 -#define a_f_FEH 0xfed2 -#define a_i_FEH 0xfed3 -#define a_m_FEH 0xfed4 -#define a_s_QAF 0xfed5 -#define a_f_QAF 0xfed6 -#define a_i_QAF 0xfed7 -#define a_m_QAF 0xfed8 -#define a_s_KAF 0xfed9 -#define a_f_KAF 0xfeda -#define a_i_KAF 0xfedb -#define a_m_KAF 0xfedc -#define a_s_LAM 0xfedd -#define a_f_LAM 0xfede -#define a_i_LAM 0xfedf -#define a_m_LAM 0xfee0 -#define a_s_MEEM 0xfee1 -#define a_f_MEEM 0xfee2 -#define a_i_MEEM 0xfee3 -#define a_m_MEEM 0xfee4 -#define a_s_NOON 0xfee5 -#define a_f_NOON 0xfee6 -#define a_i_NOON 0xfee7 -#define a_m_NOON 0xfee8 -#define a_s_HEH 0xfee9 -#define a_f_HEH 0xfeea -#define a_i_HEH 0xfeeb -#define a_m_HEH 0xfeec -#define a_s_WAW 0xfeed -#define a_f_WAW 0xfeee -#define a_s_ALEF_MAKSURA 0xfeef -#define a_f_ALEF_MAKSURA 0xfef0 -#define a_s_YEH 0xfef1 -#define a_f_YEH 0xfef2 -#define a_i_YEH 0xfef3 -#define a_m_YEH 0xfef4 #define a_s_LAM_ALEF_MADDA_ABOVE 0xfef5 #define a_f_LAM_ALEF_MADDA_ABOVE 0xfef6 #define a_s_LAM_ALEF_HAMZA_ABOVE 0xfef7 @@ -242,664 +92,201 @@ #define a_s_LAM_ALEF 0xfefb #define a_f_LAM_ALEF 0xfefc +static struct achar { + unsigned c; + unsigned isolated; + unsigned initial; + unsigned medial; + unsigned final; +} achars[] = { + { a_HAMZA, 0xfe80, 0, 0, 0 }, + { a_ALEF_MADDA, 0xfe81, 0, 0, 0xfe82 }, + { a_ALEF_HAMZA_ABOVE, 0xfe83, 0, 0, 0xfe84 }, + { a_WAW_HAMZA, 0xfe85, 0, 0, 0xfe86 }, + { a_ALEF_HAMZA_BELOW, 0xfe87, 0, 0, 0xfe88 }, + { a_YEH_HAMZA, 0xfe89, 0xfe8b, 0xfe8c, 0xfe8a }, + { a_ALEF, 0xfe8d, 0, 0, 0xfe8e }, + { a_BEH, 0xfe8f, 0xfe91, 0xfe92, 0xfe90 }, + { a_TEH_MARBUTA, 0xfe93, 0, 0, 0xfe94 }, + { a_TEH, 0xfe95, 0xfe97, 0xfe98, 0xfe96 }, + { a_THEH, 0xfe99, 0xfe9b, 0xfe9c, 0xfe9a }, + { a_JEEM, 0xfe9d, 0xfe9f, 0xfea0, 0xfe9e }, + { a_HAH, 0xfea1, 0xfea3, 0xfea4, 0xfea2 }, + { a_KHAH, 0xfea5, 0xfea7, 0xfea8, 0xfea6 }, + { a_DAL, 0xfea9, 0, 0, 0xfeaa }, + { a_THAL, 0xfeab, 0, 0, 0xfeac }, + { a_REH, 0xfead, 0, 0, 0xfeae }, + { a_ZAIN, 0xfeaf, 0, 0, 0xfeb0 }, + { a_SEEN, 0xfeb1, 0xfeb3, 0xfeb4, 0xfeb2 }, + { a_SHEEN, 0xfeb5, 0xfeb7, 0xfeb8, 0xfeb6 }, + { a_SAD, 0xfeb9, 0xfebb, 0xfebc, 0xfeba }, + { a_DAD, 0xfebd, 0xfebf, 0xfec0, 0xfebe }, + { a_TAH, 0xfec1, 0xfec3, 0xfec4, 0xfec2 }, + { a_ZAH, 0xfec5, 0xfec7, 0xfec8, 0xfec6 }, + { a_AIN, 0xfec9, 0xfecb, 0xfecc, 0xfeca }, + { a_GHAIN, 0xfecd, 0xfecf, 0xfed0, 0xfece }, + { a_TATWEEL, 0, 0x0640, 0x0640, 0x0640 }, + { a_FEH, 0xfed1, 0xfed3, 0xfed4, 0xfed2 }, + { a_QAF, 0xfed5, 0xfed7, 0xfed8, 0xfed6 }, + { a_KAF, 0xfed9, 0xfedb, 0xfedc, 0xfeda }, + { a_LAM, 0xfedd, 0xfedf, 0xfee0, 0xfede }, + { a_MEEM, 0xfee1, 0xfee3, 0xfee4, 0xfee2 }, + { a_NOON, 0xfee5, 0xfee7, 0xfee8, 0xfee6 }, + { a_HEH, 0xfee9, 0xfeeb, 0xfeec, 0xfeea }, + { a_WAW, 0xfeed, 0, 0, 0xfeee }, + { a_ALEF_MAKSURA, 0xfeef, 0, 0, 0xfef0 }, + { a_YEH, 0xfef1, 0xfef3, 0xfef4, 0xfef2 }, + { a_FATHATAN, 0xfe70, 0, 0, 0 }, + { a_DAMMATAN, 0xfe72, 0, 0, 0 }, + { a_KASRATAN, 0xfe74, 0, 0, 0 }, + { a_FATHA, 0xfe76, 0, 0xfe77, 0 }, + { a_DAMMA, 0xfe78, 0, 0xfe79, 0 }, + { a_KASRA, 0xfe7a, 0, 0xfe7b, 0 }, + { a_SHADDA, 0xfe7c, 0, 0xfe7c, 0 }, + { a_SUKUN, 0xfe7e, 0, 0xfe7f, 0 }, + { a_MADDA_ABOVE, 0, 0, 0, 0 }, + { a_HAMZA_ABOVE, 0, 0, 0, 0 }, + { a_HAMZA_BELOW, 0, 0, 0, 0 }, + { a_PEH, 0xfb56, 0xfb58, 0xfb59, 0xfb57 }, + { a_TCHEH, 0xfb7a, 0xfb7c, 0xfb7d, 0xfb7b }, + { a_JEH, 0xfb8a, 0, 0, 0xfb8b }, + { a_FKAF, 0xfb8e, 0xfb90, 0xfb91, 0xfb8f }, + { a_GAF, 0xfb92, 0xfb94, 0xfb95, 0xfb93 }, + { a_FYEH, 0xfbfc, 0xfbfe, 0xfbff, 0xfbfd }, +}; + #define a_BYTE_ORDER_MARK 0xfeff #ifdef INCLUDE_GENERATED_DECLARATIONS # include "arabic.c.generated.h" #endif -// Returns true if c is an ISO-8859-6 shaped ARABIC letter (user entered). -static bool A_is_a(int cur_c) -{ - switch (cur_c) { - case a_HAMZA: - case a_ALEF_MADDA: - case a_ALEF_HAMZA_ABOVE: - case a_WAW_HAMZA: - case a_ALEF_HAMZA_BELOW: - case a_YEH_HAMZA: - case a_ALEF: - case a_BEH: - case a_TEH_MARBUTA: - case a_TEH: - case a_THEH: - case a_JEEM: - case a_HAH: - case a_KHAH: - case a_DAL: - case a_THAL: - case a_REH: - case a_ZAIN: - case a_SEEN: - case a_SHEEN: - case a_SAD: - case a_DAD: - case a_TAH: - case a_ZAH: - case a_AIN: - case a_GHAIN: - case a_TATWEEL: - case a_FEH: - case a_QAF: - case a_KAF: - case a_LAM: - case a_MEEM: - case a_NOON: - case a_HEH: - case a_WAW: - case a_ALEF_MAKSURA: - case a_YEH: - return true; - } - - return false; -} - -// Returns true if c is an Isolated Form-B ARABIC letter -static bool A_is_s(int cur_c) +/// Find the struct achar pointer to the given Arabic char. +/// Returns NULL if not found. +static struct achar *find_achar(int c) { - switch (cur_c) { - case a_s_HAMZA: - case a_s_ALEF_MADDA: - case a_s_ALEF_HAMZA_ABOVE: - case a_s_WAW_HAMZA: - case a_s_ALEF_HAMZA_BELOW: - case a_s_YEH_HAMZA: - case a_s_ALEF: - case a_s_BEH: - case a_s_TEH_MARBUTA: - case a_s_TEH: - case a_s_THEH: - case a_s_JEEM: - case a_s_HAH: - case a_s_KHAH: - case a_s_DAL: - case a_s_THAL: - case a_s_REH: - case a_s_ZAIN: - case a_s_SEEN: - case a_s_SHEEN: - case a_s_SAD: - case a_s_DAD: - case a_s_TAH: - case a_s_ZAH: - case a_s_AIN: - case a_s_GHAIN: - case a_s_FEH: - case a_s_QAF: - case a_s_KAF: - case a_s_LAM: - case a_s_MEEM: - case a_s_NOON: - case a_s_HEH: - case a_s_WAW: - case a_s_ALEF_MAKSURA: - case a_s_YEH: - return true; + // using binary search to find c + int h = ARRAY_SIZE(achars); + int l = 0; + while (l < h) { + int m = (h + l) / 2; + if (achars[m].c == (unsigned)c) { + return &achars[m]; + } + if ((unsigned)c < achars[m].c) { + h = m; + } else { + l = m + 1; + } } - - return false; + return NULL; } -// Returns true if c is a Final shape of an ARABIC letter -static bool A_is_f(int cur_c) +/// Change shape - from Combination (2 char) to an Isolated +static int chg_c_laa2i(int hid_c) { - switch (cur_c) { - case a_f_ALEF_MADDA: - case a_f_ALEF_HAMZA_ABOVE: - case a_f_WAW_HAMZA: - case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - case a_f_ALEF: - case a_f_BEH: - case a_f_TEH_MARBUTA: - case a_f_TEH: - case a_f_THEH: - case a_f_JEEM: - case a_f_HAH: - case a_f_KHAH: - case a_f_DAL: - case a_f_THAL: - case a_f_REH: - case a_f_ZAIN: - case a_f_SEEN: - case a_f_SHEEN: - case a_f_SAD: - case a_f_DAD: - case a_f_TAH: - case a_f_ZAH: - case a_f_AIN: - case a_f_GHAIN: - case a_f_FEH: - case a_f_QAF: - case a_f_KAF: - case a_f_LAM: - case a_f_MEEM: - case a_f_NOON: - case a_f_HEH: - case a_f_WAW: - case a_f_ALEF_MAKSURA: - case a_f_YEH: - case a_f_LAM_ALEF_MADDA_ABOVE: - case a_f_LAM_ALEF_HAMZA_ABOVE: - case a_f_LAM_ALEF_HAMZA_BELOW: - case a_f_LAM_ALEF: - return true; - } - return false; -} + int tempc; -// Change shape - from ISO-8859-6/Isolated to Form-B Isolated -static int chg_c_a2s(int cur_c) -{ - switch (cur_c) { - case a_HAMZA: - return a_s_HAMZA; + switch (hid_c) { case a_ALEF_MADDA: - return a_s_ALEF_MADDA; + tempc = a_s_LAM_ALEF_MADDA_ABOVE; + break; case a_ALEF_HAMZA_ABOVE: - return a_s_ALEF_HAMZA_ABOVE; - case a_WAW_HAMZA: - return a_s_WAW_HAMZA; + tempc = a_s_LAM_ALEF_HAMZA_ABOVE; + break; case a_ALEF_HAMZA_BELOW: - return a_s_ALEF_HAMZA_BELOW; - case a_YEH_HAMZA: - return a_s_YEH_HAMZA; + tempc = a_s_LAM_ALEF_HAMZA_BELOW; + break; case a_ALEF: - return a_s_ALEF; - case a_TEH_MARBUTA: - return a_s_TEH_MARBUTA; - case a_DAL: - return a_s_DAL; - case a_THAL: - return a_s_THAL; - case a_REH: - return a_s_REH; - case a_ZAIN: - return a_s_ZAIN; - case a_TATWEEL: - return cur_c; // exceptions - case a_WAW: - return a_s_WAW; - case a_ALEF_MAKSURA: - return a_s_ALEF_MAKSURA; - case a_BEH: - return a_s_BEH; - case a_TEH: - return a_s_TEH; - case a_THEH: - return a_s_THEH; - case a_JEEM: - return a_s_JEEM; - case a_HAH: - return a_s_HAH; - case a_KHAH: - return a_s_KHAH; - case a_SEEN: - return a_s_SEEN; - case a_SHEEN: - return a_s_SHEEN; - case a_SAD: - return a_s_SAD; - case a_DAD: - return a_s_DAD; - case a_TAH: - return a_s_TAH; - case a_ZAH: - return a_s_ZAH; - case a_AIN: - return a_s_AIN; - case a_GHAIN: - return a_s_GHAIN; - case a_FEH: - return a_s_FEH; - case a_QAF: - return a_s_QAF; - case a_KAF: - return a_s_KAF; - case a_LAM: - return a_s_LAM; - case a_MEEM: - return a_s_MEEM; - case a_NOON: - return a_s_NOON; - case a_HEH: - return a_s_HEH; - case a_YEH: - return a_s_YEH; + tempc = a_s_LAM_ALEF; + break; + default: + tempc = 0; } - return 0; -} -// Change shape - from ISO-8859-6/Isolated to Initial -static int chg_c_a2i(int cur_c) -{ - switch (cur_c) { - case a_YEH_HAMZA: - return a_i_YEH_HAMZA; - case a_HAMZA: - return a_s_HAMZA; // exceptions - case a_ALEF_MADDA: - return a_s_ALEF_MADDA; // exceptions - case a_ALEF_HAMZA_ABOVE: - return a_s_ALEF_HAMZA_ABOVE; // exceptions - case a_WAW_HAMZA: - return a_s_WAW_HAMZA; // exceptions - case a_ALEF_HAMZA_BELOW: - return a_s_ALEF_HAMZA_BELOW; // exceptions - case a_ALEF: - return a_s_ALEF; // exceptions - case a_TEH_MARBUTA: - return a_s_TEH_MARBUTA; // exceptions - case a_DAL: - return a_s_DAL; // exceptions - case a_THAL: - return a_s_THAL; // exceptions - case a_REH: - return a_s_REH; // exceptions - case a_ZAIN: - return a_s_ZAIN; // exceptions - case a_TATWEEL: - return cur_c; // exceptions - case a_WAW: - return a_s_WAW; // exceptions - case a_ALEF_MAKSURA: - return a_s_ALEF_MAKSURA; // exceptions - case a_BEH: - return a_i_BEH; - case a_TEH: - return a_i_TEH; - case a_THEH: - return a_i_THEH; - case a_JEEM: - return a_i_JEEM; - case a_HAH: - return a_i_HAH; - case a_KHAH: - return a_i_KHAH; - case a_SEEN: - return a_i_SEEN; - case a_SHEEN: - return a_i_SHEEN; - case a_SAD: - return a_i_SAD; - case a_DAD: - return a_i_DAD; - case a_TAH: - return a_i_TAH; - case a_ZAH: - return a_i_ZAH; - case a_AIN: - return a_i_AIN; - case a_GHAIN: - return a_i_GHAIN; - case a_FEH: - return a_i_FEH; - case a_QAF: - return a_i_QAF; - case a_KAF: - return a_i_KAF; - case a_LAM: - return a_i_LAM; - case a_MEEM: - return a_i_MEEM; - case a_NOON: - return a_i_NOON; - case a_HEH: - return a_i_HEH; - case a_YEH: - return a_i_YEH; - } - return 0; + return tempc; } -// Change shape - from ISO-8859-6/Isolated to Medial -static int chg_c_a2m(int cur_c) +/// Change shape - from Combination-Isolated to Final +static int chg_c_laa2f(int hid_c) { - switch (cur_c) { - case a_HAMZA: - return a_s_HAMZA; // exception + int tempc; + + switch (hid_c) { case a_ALEF_MADDA: - return a_f_ALEF_MADDA; // exception + tempc = a_f_LAM_ALEF_MADDA_ABOVE; + break; case a_ALEF_HAMZA_ABOVE: - return a_f_ALEF_HAMZA_ABOVE; // exception - case a_WAW_HAMZA: - return a_f_WAW_HAMZA; // exception + tempc = a_f_LAM_ALEF_HAMZA_ABOVE; + break; case a_ALEF_HAMZA_BELOW: - return a_f_ALEF_HAMZA_BELOW; // exception - case a_YEH_HAMZA: - return a_m_YEH_HAMZA; + tempc = a_f_LAM_ALEF_HAMZA_BELOW; + break; case a_ALEF: - return a_f_ALEF; // exception - case a_BEH: - return a_m_BEH; - case a_TEH_MARBUTA: - return a_f_TEH_MARBUTA; // exception - case a_TEH: - return a_m_TEH; - case a_THEH: - return a_m_THEH; - case a_JEEM: - return a_m_JEEM; - case a_HAH: - return a_m_HAH; - case a_KHAH: - return a_m_KHAH; - case a_DAL: - return a_f_DAL; // exception - case a_THAL: - return a_f_THAL; // exception - case a_REH: - return a_f_REH; // exception - case a_ZAIN: - return a_f_ZAIN; // exception - case a_SEEN: - return a_m_SEEN; - case a_SHEEN: - return a_m_SHEEN; - case a_SAD: - return a_m_SAD; - case a_DAD: - return a_m_DAD; - case a_TAH: - return a_m_TAH; - case a_ZAH: - return a_m_ZAH; - case a_AIN: - return a_m_AIN; - case a_GHAIN: - return a_m_GHAIN; - case a_TATWEEL: - return cur_c; // exception - case a_FEH: - return a_m_FEH; - case a_QAF: - return a_m_QAF; - case a_KAF: - return a_m_KAF; - case a_LAM: - return a_m_LAM; - case a_MEEM: - return a_m_MEEM; - case a_NOON: - return a_m_NOON; - case a_HEH: - return a_m_HEH; - case a_WAW: - return a_f_WAW; // exception - case a_ALEF_MAKSURA: - return a_f_ALEF_MAKSURA; // exception - case a_YEH: - return a_m_YEH; + tempc = a_f_LAM_ALEF; + break; + default: + tempc = 0; } - return 0; + + return tempc; } -// Change shape - from ISO-8859-6/Isolated to final -static int chg_c_a2f(int cur_c) +/// Returns whether it is possible to join the given letters +static int can_join(int c1, int c2) { - // NOTE: these encodings need to be accounted for - // - // a_f_ALEF_MADDA; - // a_f_ALEF_HAMZA_ABOVE; - // a_f_ALEF_HAMZA_BELOW; - // a_f_LAM_ALEF_MADDA_ABOVE; - // a_f_LAM_ALEF_HAMZA_ABOVE; - // a_f_LAM_ALEF_HAMZA_BELOW; + struct achar *a1 = find_achar(c1); + struct achar *a2 = find_achar(c2); - switch (cur_c) { - case a_HAMZA: - return a_s_HAMZA; // exception - case a_ALEF_MADDA: - return a_f_ALEF_MADDA; - case a_ALEF_HAMZA_ABOVE: - return a_f_ALEF_HAMZA_ABOVE; - case a_WAW_HAMZA: - return a_f_WAW_HAMZA; - case a_ALEF_HAMZA_BELOW: - return a_f_ALEF_HAMZA_BELOW; - case a_YEH_HAMZA: - return a_f_YEH_HAMZA; - case a_ALEF: - return a_f_ALEF; - case a_BEH: - return a_f_BEH; - case a_TEH_MARBUTA: - return a_f_TEH_MARBUTA; - case a_TEH: - return a_f_TEH; - case a_THEH: - return a_f_THEH; - case a_JEEM: - return a_f_JEEM; - case a_HAH: - return a_f_HAH; - case a_KHAH: - return a_f_KHAH; - case a_DAL: - return a_f_DAL; - case a_THAL: - return a_f_THAL; - case a_REH: - return a_f_REH; - case a_ZAIN: - return a_f_ZAIN; - case a_SEEN: - return a_f_SEEN; - case a_SHEEN: - return a_f_SHEEN; - case a_SAD: - return a_f_SAD; - case a_DAD: - return a_f_DAD; - case a_TAH: - return a_f_TAH; - case a_ZAH: - return a_f_ZAH; - case a_AIN: - return a_f_AIN; - case a_GHAIN: - return a_f_GHAIN; - case a_TATWEEL: - return cur_c; // exception - case a_FEH: - return a_f_FEH; - case a_QAF: - return a_f_QAF; - case a_KAF: - return a_f_KAF; - case a_LAM: - return a_f_LAM; - case a_MEEM: - return a_f_MEEM; - case a_NOON: - return a_f_NOON; - case a_HEH: - return a_f_HEH; - case a_WAW: - return a_f_WAW; - case a_ALEF_MAKSURA: - return a_f_ALEF_MAKSURA; - case a_YEH: - return a_f_YEH; - } - return 0; + return a1 && a2 && (a1->initial || a1->medial) && (a2->final || a2->medial); } -// Change shape - from Initial to Medial -// This code is unreachable, because for the relevant characters ARABIC_CHAR() -// is FALSE; -#if 0 -static int chg_c_i2m(int cur_c) +/// Check whether we are dealing with a character that could be regarded as an +/// Arabic combining character, need to check the character before this. +bool arabic_maycombine(int two) + FUNC_ATTR_PURE { - switch (cur_c) { - case a_i_YEH_HAMZA: - return a_m_YEH_HAMZA; - case a_i_BEH: - return a_m_BEH; - case a_i_TEH: - return a_m_TEH; - case a_i_THEH: - return a_m_THEH; - case a_i_JEEM: - return a_m_JEEM; - case a_i_HAH: - return a_m_HAH; - case a_i_KHAH: - return a_m_KHAH; - case a_i_SEEN: - return a_m_SEEN; - case a_i_SHEEN: - return a_m_SHEEN; - case a_i_SAD: - return a_m_SAD; - case a_i_DAD: - return a_m_DAD; - case a_i_TAH: - return a_m_TAH; - case a_i_ZAH: - return a_m_ZAH; - case a_i_AIN: - return a_m_AIN; - case a_i_GHAIN: - return a_m_GHAIN; - case a_i_FEH: - return a_m_FEH; - case a_i_QAF: - return a_m_QAF; - case a_i_KAF: - return a_m_KAF; - case a_i_LAM: - return a_m_LAM; - case a_i_MEEM: - return a_m_MEEM; - case a_i_NOON: - return a_m_NOON; - case a_i_HEH: - return a_m_HEH; - case a_i_YEH: - return a_m_YEH; + if (p_arshape && !p_tbidi) { + return two == a_ALEF_MADDA + || two == a_ALEF_HAMZA_ABOVE + || two == a_ALEF_HAMZA_BELOW + || two == a_ALEF; } - return 0; + return false; } -#endif -// Change shape - from Final to Medial -static int chg_c_f2m(int cur_c) +/// Check whether we are dealing with Arabic combining characters. +/// Note: these are NOT really composing characters! +/// +/// @param one First character. +/// @param two Character just after "one". +bool arabic_combine(int one, int two) + FUNC_ATTR_PURE { - switch (cur_c) { - // NOTE: these encodings are multi-positional, no ? - // case a_f_ALEF_MADDA: - // case a_f_ALEF_HAMZA_ABOVE: - // case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - return a_m_YEH_HAMZA; - case a_f_WAW_HAMZA: // exceptions - case a_f_ALEF: - case a_f_TEH_MARBUTA: - case a_f_DAL: - case a_f_THAL: - case a_f_REH: - case a_f_ZAIN: - case a_f_WAW: - case a_f_ALEF_MAKSURA: - return cur_c; - case a_f_BEH: - return a_m_BEH; - case a_f_TEH: - return a_m_TEH; - case a_f_THEH: - return a_m_THEH; - case a_f_JEEM: - return a_m_JEEM; - case a_f_HAH: - return a_m_HAH; - case a_f_KHAH: - return a_m_KHAH; - case a_f_SEEN: - return a_m_SEEN; - case a_f_SHEEN: - return a_m_SHEEN; - case a_f_SAD: - return a_m_SAD; - case a_f_DAD: - return a_m_DAD; - case a_f_TAH: - return a_m_TAH; - case a_f_ZAH: - return a_m_ZAH; - case a_f_AIN: - return a_m_AIN; - case a_f_GHAIN: - return a_m_GHAIN; - case a_f_FEH: - return a_m_FEH; - case a_f_QAF: - return a_m_QAF; - case a_f_KAF: - return a_m_KAF; - case a_f_LAM: - return a_m_LAM; - case a_f_MEEM: - return a_m_MEEM; - case a_f_NOON: - return a_m_NOON; - case a_f_HEH: - return a_m_HEH; - case a_f_YEH: - return a_m_YEH; - // NOTE: these encodings are multi-positional, no ? - // case a_f_LAM_ALEF_MADDA_ABOVE: - // case a_f_LAM_ALEF_HAMZA_ABOVE: - // case a_f_LAM_ALEF_HAMZA_BELOW: - // case a_f_LAM_ALEF: + if (one == a_LAM) { + return arabic_maycombine(two); } - return 0; + return false; } -// Change shape - from Combination (2 char) to an Isolated. -static int chg_c_laa2i(int hid_c) +/// A_is_iso returns true if 'c' is an Arabic ISO-8859-6 character +/// (alphabet/number/punctuation) +static int A_is_iso(int c) { - switch (hid_c) { - case a_ALEF_MADDA: - return a_s_LAM_ALEF_MADDA_ABOVE; - case a_ALEF_HAMZA_ABOVE: - return a_s_LAM_ALEF_HAMZA_ABOVE; - case a_ALEF_HAMZA_BELOW: - return a_s_LAM_ALEF_HAMZA_BELOW; - case a_ALEF: - return a_s_LAM_ALEF; - } - return 0; + return find_achar(c) != NULL; } -// Change shape - from Combination-Isolated to Final. -static int chg_c_laa2f(int hid_c) +/// A_is_ok returns true if 'c' is an Arabic 10646 (8859-6 or Form-B) +static int A_is_ok(int c) { - switch (hid_c) { - case a_ALEF_MADDA: - return a_f_LAM_ALEF_MADDA_ABOVE; - case a_ALEF_HAMZA_ABOVE: - return a_f_LAM_ALEF_HAMZA_ABOVE; - case a_ALEF_HAMZA_BELOW: - return a_f_LAM_ALEF_HAMZA_BELOW; - case a_ALEF: - return a_f_LAM_ALEF; - } - return 0; + return (A_is_iso(c) || c == a_BYTE_ORDER_MARK); } -// Do "half-shaping" on character "c". Return zero if no shaping. -static int half_shape(int c) +/// A_is_valid returns true if 'c' is an Arabic 10646 (8859-6 or Form-B) +/// with some exceptions/exclusions +static int A_is_valid(int c) { - if (A_is_a(c)) { - return chg_c_a2i(c); - } - - if (A_is_valid(c) && A_is_f(c)) { - return chg_c_f2m(c); - } - return 0; + return (A_is_ok(c) && c != a_HAMZA); } // Do Arabic shaping on character "c". Returns the shaped character. @@ -916,37 +303,35 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c) return c; } - // half-shape current and previous character - int shape_c = half_shape(prev_c); - int curr_c; - int curr_laa = A_firstc_laa(c, *c1p); - int prev_laa = A_firstc_laa(prev_c, prev_c1); + int curr_laa = arabic_combine(c, *c1p); + int prev_laa = arabic_combine(prev_c, prev_c1); if (curr_laa) { - if (A_is_valid(prev_c) && !A_is_f(shape_c) && !A_is_s(shape_c) - && !prev_laa) { - curr_c = chg_c_laa2f(curr_laa); + if (A_is_valid(prev_c) && can_join(prev_c, a_LAM) && !prev_laa) { + curr_c = chg_c_laa2f(*c1p); } else { - curr_c = chg_c_laa2i(curr_laa); + curr_c = chg_c_laa2i(*c1p); } - // Remove the composing character *c1p = 0; - } else if (!A_is_valid(prev_c) && A_is_valid(next_c)) { - curr_c = chg_c_a2i(c); - } else if (!shape_c || A_is_f(shape_c) || A_is_s(shape_c) || prev_laa) { - curr_c = A_is_valid(next_c) ? chg_c_a2i(c) : chg_c_a2s(c); - } else if (A_is_valid(next_c)) { -#if 0 - curr_c = A_is_iso(c) ? chg_c_a2m(c) : chg_c_i2m(c); -#else - curr_c = A_is_iso(c) ? chg_c_a2m(c) : 0; -#endif - } else if (A_is_valid(prev_c)) { - curr_c = chg_c_a2f(c); } else { - curr_c = chg_c_a2s(c); + struct achar *curr_a = find_achar(c); + int backward_combine = !prev_laa && can_join(prev_c, c); + int forward_combine = can_join(c, next_c); + + if (backward_combine && forward_combine) { + curr_c = (int)curr_a->medial; + } + if (backward_combine && !forward_combine) { + curr_c = (int)curr_a->final; + } + if (!backward_combine && forward_combine) { + curr_c = (int)curr_a->initial; + } + if (!backward_combine && !forward_combine) { + curr_c = (int)curr_a->isolated; + } } // Sanity check -- curr_c should, in the future, never be 0. @@ -966,88 +351,3 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c) // Return the shaped character return curr_c; } - -/// Check whether we are dealing with Arabic combining characters. -/// Note: these are NOT really composing characters! -/// -/// @param one First character. -/// @param two Character just after "one". -bool arabic_combine(int one, int two) - FUNC_ATTR_PURE -{ - if (one == a_LAM) { - return arabic_maycombine(two); - } - return false; -} - -/// Check whether we are dealing with a character that could be regarded as an -/// Arabic combining character, need to check the character before this. -bool arabic_maycombine(int two) - FUNC_ATTR_PURE -{ - if (p_arshape && !p_tbidi) { - return two == a_ALEF_MADDA - || two == a_ALEF_HAMZA_ABOVE - || two == a_ALEF_HAMZA_BELOW - || two == a_ALEF; - } - return false; -} - -// A_firstc_laa returns first character of LAA combination if it exists -// in: "c" base character -// in: "c1" first composing character -static int A_firstc_laa(int c, int c1) -{ - if ((c1 != NUL) && (c == a_LAM) && !A_is_harakat(c1)) { - return c1; - } - return 0; -} - -// A_is_harakat returns true if 'c' is an Arabic Harakat character. -// (harakat/tanween) -static bool A_is_harakat(int c) -{ - return c >= a_FATHATAN && c <= a_SUKUN; -} - -// A_is_iso returns true if 'c' is an Arabic ISO-8859-6 character. -// (alphabet/number/punctuation) -static bool A_is_iso(int c) -{ - return ((c >= a_HAMZA && c <= a_GHAIN) - || (c >= a_TATWEEL && c <= a_HAMZA_BELOW) - || c == a_MINI_ALEF); -} - -// A_is_formb returns true if 'c' is an Arabic 10646-1 FormB character. -// (alphabet/number/punctuation) -static bool A_is_formb(int c) -{ - return ((c >= a_s_FATHATAN && c <= a_s_DAMMATAN) - || c == a_s_KASRATAN - || (c >= a_s_FATHA && c <= a_f_LAM_ALEF) - || c == a_BYTE_ORDER_MARK); -} - -// A_is_ok returns true if 'c' is an Arabic 10646 (8859-6 or Form-B). -static bool A_is_ok(int c) -{ - return A_is_iso(c) || A_is_formb(c); -} - -// A_is_valid returns true if 'c' is an Arabic 10646 (8859-6 or Form-B), -// with some exceptions/exclusions. -static bool A_is_valid(int c) -{ - return A_is_ok(c) && !A_is_special(c); -} - -// A_is_special returns true if 'c' is not a special Arabic character. -// Specials don't adhere to most of the rules. -static bool A_is_special(int c) -{ - return c == a_HAMZA || c == a_s_HAMZA; -} diff --git a/src/nvim/arabic.h b/src/nvim/arabic.h index eaab463777..3c34de1449 100644 --- a/src/nvim/arabic.h +++ b/src/nvim/arabic.h @@ -3,12 +3,7 @@ #include <stdbool.h> -/// Whether c belongs to the range of Arabic characters that might be shaped. -static inline bool arabic_char(int c) -{ - // return c >= a_HAMZA && c <= a_MINI_ALEF; - return c >= 0x0621 && c <= 0x0670; -} +#define ARABIC_CHAR(ch) (((ch) & 0xFF00) == 0x0600) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "arabic.h.generated.h" diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 75666c600c..d51079b515 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1679,7 +1679,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force } else if (fname != NULL && !ends_excmd(*fname)) { autocmd_fname = fname; } else if (buf != NULL) { - autocmd_fname = (char *)buf->b_ffname; + autocmd_fname = buf->b_ffname; } else { autocmd_fname = NULL; } @@ -1711,9 +1711,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force fname = (char *)buf->b_p_ft; } else { if (buf->b_sfname != NULL) { - sfname = (char *)vim_strsave(buf->b_sfname); + sfname = xstrdup(buf->b_sfname); } - fname = (char *)buf->b_ffname; + fname = buf->b_ffname; } } if (fname == NULL) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index baecda8e3c..f937450107 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -55,6 +55,7 @@ #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mark.h" +#include "nvim/mark_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -88,15 +89,24 @@ static char *msg_loclist = N_("[Location List]"); static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); +static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); // Number of times free_buffer() was called. static int buf_free_count = 0; +static int top_file_num = 1; ///< highest file number + typedef enum { kBffClearWinInfo = 1, kBffInitChangedtick = 2, } BufFreeFlags; +/// @return the highest possible buffer number +int get_highest_fnum(void) +{ + return top_file_num - 1; +} + /// Read data from buffer for retrying. /// /// @param read_stdin read file from stdin, otherwise fifo @@ -112,7 +122,7 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags) // the end. This makes it possible to retry when 'fileformat' or // 'fileencoding' was guessed wrong. line_count = curbuf->b_ml.ml_line_count; - retval = readfile(read_stdin ? NULL : (char *)curbuf->b_ffname, + retval = readfile(read_stdin ? NULL : curbuf->b_ffname, read_stdin ? NULL : curbuf->b_fname, line_count, (linenr_T)0, (linenr_T)MAXLNUM, eap, flags | READ_BUFFER, silent); @@ -230,7 +240,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) } #endif - retval = readfile((char *)curbuf->b_ffname, curbuf->b_fname, + retval = readfile(curbuf->b_ffname, curbuf->b_fname, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap, flags | READ_NEW | (read_fifo ? READ_FIFO : 0), silent); #ifdef UNIX @@ -437,10 +447,11 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. if (buf->b_locked > 0 && (del_buf || wipe_buf)) { - emsg(_("E937: Attempt to delete a buffer that is in use")); + emsg(_(e_buflocked)); return false; } + // check no autocommands closed the window if (win != NULL // Avoid bogus clang warning. && win_valid_any_tab(win)) { // Set b_last_cursor when closing the last window for the buffer. @@ -528,7 +539,9 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i } if (buf->terminal) { + buf->b_locked++; terminal_close(&buf->terminal, -1); + buf->b_locked--; } // Always remove the buffer when there is no file name. @@ -802,8 +815,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) // Avoid losing b:changedtick when deleting buffer: clearing variables // implies using clear_tv() on b:changedtick and that sets changedtick to // zero. - hashitem_T *const changedtick_hi = hash_find(&buf->b_vars->dv_hashtab, - (const char_u *)"changedtick"); + hashitem_T *const changedtick_hi = hash_find(&buf->b_vars->dv_hashtab, "changedtick"); assert(changedtick_hi != NULL); hash_remove(&buf->b_vars->dv_hashtab, changedtick_hi); } @@ -813,7 +825,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_init_changedtick(buf); } uc_clear(&buf->b_ucmds); // clear local user commands - buf_delete_signs(buf, (char_u *)"*"); // delete any signs + buf_delete_signs(buf, "*"); // delete any signs extmark_free_all(buf); // delete any extmarks map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs @@ -942,7 +954,7 @@ void handle_swap_exists(bufref_T *old_curbuf) /// @param end_bnr buffer nr or last buffer nr in a range /// /// @return error message or NULL -char *do_bufdel(int command, char_u *arg, int addr_count, int start_bnr, int end_bnr, int forceit) +char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_bnr, int forceit) { int do_current = 0; // delete current buffer? int deleted = 0; // number of buffers deleted @@ -980,17 +992,17 @@ char *do_bufdel(int command, char_u *arg, int addr_count, int start_bnr, int end break; } } else { // addr_count == 1 - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); if (*arg == NUL) { break; } if (!ascii_isdigit(*arg)) { - p = (char *)skiptowhite_esc(arg); - bnr = buflist_findpat(arg, (char_u *)p, command == DOBUF_WIPE, false, false); + p = skiptowhite_esc(arg); + bnr = buflist_findpat(arg, p, command == DOBUF_WIPE, false, false); if (bnr < 0) { // failed break; } - arg = (char_u *)p; + arg = p; } else { bnr = getdigits_int(&arg, false, 0); } @@ -1182,6 +1194,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (unload) { int forward; bufref_T bufref; + if (buf->b_locked) { + emsg(_(e_buflocked)); + return FAIL; + } set_bufref(&bufref, buf); // When unloading or deleting a buffer that's already unloaded and @@ -1636,8 +1652,6 @@ void no_write_message_nobang(const buf_T *const buf) // functions for dealing with the buffer list // -static int top_file_num = 1; ///< highest file number - /// Initialize b:changedtick and changedtick_val attribute /// /// @param[out] buf Buffer to initialize for. @@ -1676,10 +1690,10 @@ static inline void buf_init_changedtick(buf_T *const buf) /// @param bufnr /// /// @return pointer to the buffer -buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int flags) +buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags) { - char *ffname = (char *)ffname_arg; - char *sfname = (char *)sfname_arg; + char *ffname = ffname_arg; + char *sfname = sfname_arg; buf_T *buf; fname_expand(curbuf, &ffname, &sfname); // will allocate ffname @@ -1746,8 +1760,8 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl } if (ffname != NULL) { - buf->b_ffname = (char_u *)ffname; - buf->b_sfname = vim_strsave((char_u *)sfname); + buf->b_ffname = ffname; + buf->b_sfname = xstrdup(sfname); } clear_wininfo(buf); @@ -1789,13 +1803,14 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl buf_copy_options(buf, BCO_ALWAYS); } - buf->b_wininfo->wi_fpos.lnum = lnum; + buf->b_wininfo->wi_mark = (fmark_T)INIT_FMARK; + buf->b_wininfo->wi_mark.mark.lnum = lnum; buf->b_wininfo->wi_win = curwin; hash_init(&buf->b_s.b_keywtab); hash_init(&buf->b_s.b_keywtab_ic); - buf->b_fname = (char *)buf->b_sfname; + buf->b_fname = buf->b_sfname; if (!file_id_valid) { buf->file_id_valid = false; } else { @@ -1937,7 +1952,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) { buf_T *buf; win_T *wp = NULL; - pos_T *fpos; + fmark_T *fm = NULL; colnr_T col; buf = buflist_findnr(n); @@ -1955,19 +1970,17 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) return OK; } - if (text_locked()) { - text_locked_msg(); - return FAIL; - } - if (curbuf_locked()) { + if (text_or_buf_locked()) { return FAIL; } + bool restore_view = false; // altfpos may be changed by getfile(), get it now if (lnum == 0) { - fpos = buflist_findfpos(buf); - lnum = fpos->lnum; - col = fpos->col; + fm = buflist_findfmark(buf); + lnum = fm->mark.lnum; + col = fm->mark.col; + restore_view = true; } else { col = 0; } @@ -2011,6 +2024,9 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) curwin->w_cursor.coladd = 0; curwin->w_set_curswant = true; } + if (jop_flags & JOP_VIEW && restore_view) { + mark_view_restore(fm); + } return OK; } RedrawingDisabled--; @@ -2022,7 +2038,7 @@ void buflist_getfpos(void) { pos_T *fpos; - fpos = buflist_findfpos(curbuf); + fpos = &buflist_findfmark(curbuf)->mark; curwin->w_cursor.lnum = fpos->lnum; check_cursor_lnum(); @@ -2040,13 +2056,13 @@ void buflist_getfpos(void) /// Find file in buffer list by name (it has to be for the current window). /// /// @return buffer or NULL if not found -buf_T *buflist_findname_exp(char_u *fname) +buf_T *buflist_findname_exp(char *fname) { char *ffname; buf_T *buf = NULL; // First make the name into a full path name - ffname = FullName_save((char *)fname, + ffname = FullName_save(fname, #ifdef UNIX // force expansion, get rid of symbolic links true @@ -2055,7 +2071,7 @@ buf_T *buflist_findname_exp(char_u *fname) #endif ); // NOLINT(whitespace/parens) if (ffname != NULL) { - buf = buflist_findname((char_u *)ffname); + buf = buflist_findname(ffname); xfree(ffname); } return buf; @@ -2066,11 +2082,11 @@ buf_T *buflist_findname_exp(char_u *fname) /// Skips dummy buffers. /// /// @return buffer or NULL if not found -buf_T *buflist_findname(char_u *ffname) +buf_T *buflist_findname(char *ffname) { FileID file_id; - bool file_id_valid = os_fileid((char *)ffname, &file_id); - return buflist_findname_file_id((char *)ffname, &file_id, file_id_valid); + bool file_id_valid = os_fileid(ffname, &file_id); + return buflist_findname_file_id(ffname, &file_id, file_id_valid); } /// Same as buflist_findname(), but pass the FileID structure to avoid @@ -2098,7 +2114,7 @@ static buf_T *buflist_findname_file_id(char *ffname, FileID *file_id, bool file_ /// @param curtab_only find buffers in current tab only /// /// @return fnum of the found buffer or < 0 for error. -int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlisted, bool diffmode, +int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted, bool diffmode, bool curtab_only) FUNC_ATTR_NONNULL_ARG(1) { @@ -2229,7 +2245,7 @@ static int buf_time_compare(const void *s1, const void *s2) /// For command line expansion of ":buf" and ":sbuf". /// /// @return OK if matches found, FAIL otherwise. -int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) +int ExpandBufnames(char *pat, int *num_file, char ***file, int options) { int count = 0; int round; @@ -2251,20 +2267,20 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) STRCPY(patc, "\\(^\\|[\\/]\\)"); STRCPY(patc + 11, pat + 1); } else { - patc = (char *)pat; + patc = pat; } // attempt == 0: try match with '\<', match at start of word // attempt == 1: try match without '\<', match anywhere for (attempt = 0; attempt <= 1; attempt++) { - if (attempt > 0 && (char_u *)patc == pat) { + if (attempt > 0 && patc == pat) { break; // there was no anchor, no need to try again } regmatch_T regmatch; regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); if (regmatch.regprog == NULL) { - if ((char_u *)patc != pat) { + if (patc != pat) { xfree(patc); } return FAIL; @@ -2291,7 +2307,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) count++; } else { if (options & WILD_HOME_REPLACE) { - p = (char *)home_replace_save(buf, (char_u *)p); + p = home_replace_save(buf, p); } else { p = xstrdup(p); } @@ -2300,7 +2316,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) matches[count].match = p; count++; } else { - (*file)[count++] = (char_u *)p; + (*file)[count++] = p; } } } @@ -2322,7 +2338,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) } } - if ((char_u *)patc != pat) { + if (patc != pat) { xfree(patc); } @@ -2334,12 +2350,12 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) // if the current buffer is first in the list, place it at the end if (matches[0].buf == curbuf) { for (int i = 1; i < count; i++) { - (*file)[i - 1] = (char_u *)matches[i].match; + (*file)[i - 1] = matches[i].match; } - (*file)[count - 1] = (char_u *)matches[0].match; + (*file)[count - 1] = matches[0].match; } else { for (int i = 0; i < count; i++) { - (*file)[i] = (char_u *)matches[i].match; + (*file)[i] = matches[i].match; } } xfree(matches); @@ -2355,9 +2371,9 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) { // First try the short file name, then the long file name. - char *match = fname_match(rmp, (char *)buf->b_sfname, ignore_case); + char *match = fname_match(rmp, buf->b_sfname, ignore_case); if (match == NULL && rmp->regprog != NULL) { - match = fname_match(rmp, (char *)buf->b_ffname, ignore_case); + match = fname_match(rmp, buf->b_ffname, ignore_case); } return match; } @@ -2375,12 +2391,12 @@ static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case) if (name != NULL) { // Ignore case when 'fileignorecase' or the argument is set. rmp->rm_ic = p_fic || ignore_case; - if (vim_regexec(rmp, (char_u *)name, (colnr_T)0)) { + if (vim_regexec(rmp, name, (colnr_T)0)) { match = name; } else if (rmp->regprog != NULL) { // Replace $(HOME) with '~' and try matching again. - p = (char *)home_replace_save(NULL, (char_u *)name); - if (vim_regexec(rmp, (char_u *)p, (colnr_T)0)) { + p = home_replace_save(NULL, name); + if (vim_regexec(rmp, p, (colnr_T)0)) { match = name; } xfree(p); @@ -2407,16 +2423,14 @@ buf_T *buflist_findnr(int nr) /// @param helptail for help buffers return tail only /// /// @return a pointer to allocated memory, of NULL when failed. -char_u *buflist_nr2name(int n, int fullname, int helptail) +char *buflist_nr2name(int n, int fullname, int helptail) { - buf_T *buf; - - buf = buflist_findnr(n); + buf_T *buf = buflist_findnr(n); if (buf == NULL) { return NULL; } return home_replace_save(helptail ? buf : NULL, - fullname ? buf->b_ffname : (char_u *)buf->b_fname); + fullname ? buf->b_ffname : buf->b_fname); } /// Set the line and column numbers for the given buffer and window @@ -2462,8 +2476,11 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T } } if (lnum != 0) { - wip->wi_fpos.lnum = lnum; - wip->wi_fpos.col = col; + wip->wi_mark.mark.lnum = lnum; + wip->wi_mark.mark.col = col; + if (win != NULL) { + wip->wi_mark.view = mark_view_make(win->w_topline, wip->wi_mark.mark); + } } if (copy_options && win != NULL) { // Save the window-specific option values. @@ -2581,24 +2598,23 @@ void get_winopts(buf_T *buf) didset_window_options(curwin); } -/// Find the position (lnum and col) for the buffer 'buf' for the current -/// window. +/// Find the mark for the buffer 'buf' for the current window. /// /// @return a pointer to no_position if no position is found. -pos_T *buflist_findfpos(buf_T *buf) +fmark_T *buflist_findfmark(buf_T *buf) FUNC_ATTR_PURE { - static pos_T no_position = { 1, 0, 0 }; + static fmark_T no_position = { { 1, 0, 0 }, 0, 0, { 0 }, NULL }; wininfo_T *const wip = find_wininfo(buf, false, false); - return (wip == NULL) ? &no_position : &(wip->wi_fpos); + return (wip == NULL) ? &no_position : &(wip->wi_mark); } /// Find the lnum for the buffer 'buf' for the current window. linenr_T buflist_findlnum(buf_T *buf) FUNC_ATTR_PURE { - return buflist_findfpos(buf)->lnum; + return buflist_findfmark(buf)->mark.lnum; } /// List all known file names (for :files and :buffers command). @@ -2710,16 +2726,14 @@ void buflist_list(exarg_T *eap) /// Used by insert_reg() and cmdline_paste() for '#' register. /// /// @return FAIL if not found, OK for success. -int buflist_name_nr(int fnum, char_u **fname, linenr_T *lnum) +int buflist_name_nr(int fnum, char **fname, linenr_T *lnum) { - buf_T *buf; - - buf = buflist_findnr(fnum); + buf_T *buf = buflist_findnr(fnum); if (buf == NULL || buf->b_fname == NULL) { return FAIL; } - *fname = (char_u *)buf->b_fname; + *fname = buf->b_fname; *lnum = buflist_findlnum(buf); return OK; @@ -2774,16 +2788,16 @@ int setfname(buf_T *buf, char *ffname_arg, char *sfname_arg, bool message) } sfname = xstrdup(sfname); #ifdef USE_FNAME_CASE - path_fix_case((char_u *)sfname); // set correct case for short file name + path_fix_case(sfname); // set correct case for short file name #endif if (buf->b_sfname != buf->b_ffname) { xfree(buf->b_sfname); } xfree(buf->b_ffname); - buf->b_ffname = (char_u *)ffname; - buf->b_sfname = (char_u *)sfname; + buf->b_ffname = ffname; + buf->b_sfname = sfname; } - buf->b_fname = (char *)buf->b_sfname; + buf->b_fname = buf->b_sfname; if (!file_id_valid) { buf->file_id_valid = false; } else { @@ -2797,22 +2811,20 @@ int setfname(buf_T *buf, char *ffname_arg, char *sfname_arg, bool message) /// Crude way of changing the name of a buffer. Use with care! /// The name should be relative to the current directory. -void buf_set_name(int fnum, char_u *name) +void buf_set_name(int fnum, char *name) { - buf_T *buf; - - buf = buflist_findnr(fnum); + buf_T *buf = buflist_findnr(fnum); if (buf != NULL) { if (buf->b_sfname != buf->b_ffname) { xfree(buf->b_sfname); } xfree(buf->b_ffname); - buf->b_ffname = vim_strsave(name); + buf->b_ffname = xstrdup(name); buf->b_sfname = NULL; // Allocate ffname and expand into full path. Also resolves .lnk // files on Win32. - fname_expand(buf, (char **)&buf->b_ffname, (char **)&buf->b_sfname); - buf->b_fname = (char *)buf->b_sfname; + fname_expand(buf, &buf->b_ffname, &buf->b_sfname); + buf->b_fname = buf->b_sfname; } } @@ -2838,12 +2850,10 @@ void buf_name_changed(buf_T *buf) /// Used by do_one_cmd(), do_write() and do_ecmd(). /// /// @return the buffer. -buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum) +buf_T *setaltfname(char *ffname, char *sfname, linenr_T lnum) { - buf_T *buf; - // Create a buffer. 'buflisted' is not set if it's a new buffer - buf = buflist_new(ffname, sfname, lnum, 0); + buf_T *buf = buflist_new(ffname, sfname, lnum, 0); if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { curwin->w_alt_fnum = buf->b_fnum; } @@ -2854,29 +2864,27 @@ buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum) /// Return NULL if there isn't any, and give error message if requested. /// /// @param errmsg give error message -char_u *getaltfname(bool errmsg) +char *getaltfname(bool errmsg) { char *fname; linenr_T dummy; - if (buflist_name_nr(0, (char_u **)&fname, &dummy) == FAIL) { + if (buflist_name_nr(0, &fname, &dummy) == FAIL) { if (errmsg) { emsg(_(e_noalt)); } return NULL; } - return (char_u *)fname; + return fname; } /// Add a file name to the buflist and return its number. /// Uses same flags as buflist_new(), except BLN_DUMMY. /// /// Used by qf_init(), main() and doarglist() -int buflist_add(char_u *fname, int flags) +int buflist_add(char *fname, int flags) { - buf_T *buf; - - buf = buflist_new(fname, NULL, (linenr_T)0, flags); + buf_T *buf = buflist_new(fname, NULL, (linenr_T)0, flags); if (buf != NULL) { return buf->b_fnum; } @@ -2910,10 +2918,10 @@ void buflist_altfpos(win_T *win) /// Fname must have a full path (expanded by path_to_absolute()). /// /// @param ffname full path name to check -bool otherfile(char_u *ffname) +bool otherfile(char *ffname) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return otherfile_buf(curbuf, (char *)ffname, NULL, false); + return otherfile_buf(curbuf, ffname, NULL, false); } /// Check that "ffname" is not the same file as the file loaded in "buf". @@ -3014,7 +3022,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) if (!fullname && curbuf->b_fname != NULL) { name = curbuf->b_fname; } else { - name = (char *)curbuf->b_ffname; + name = curbuf->b_ffname; } home_replace(shorthelp ? curbuf : NULL, name, p, (size_t)(IOSIZE - (p - buffer)), true); @@ -3062,7 +3070,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) n); validate_virtcol(); len = STRLEN(buffer); - col_print((char_u *)buffer + len, IOSIZE - len, + col_print(buffer + len, IOSIZE - len, (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); } @@ -3091,12 +3099,12 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) xfree(buffer); } -void col_print(char_u *buf, size_t buflen, int col, int vcol) +void col_print(char *buf, size_t buflen, int col, int vcol) { if (col == vcol) { - vim_snprintf((char *)buf, buflen, "%d", col); + vim_snprintf(buf, buflen, "%d", col); } else { - vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol); + vim_snprintf(buf, buflen, "%d-%d", col, vcol); } } @@ -3135,18 +3143,16 @@ void maketitle(void) if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { int use_sandbox = false; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; use_sandbox = was_set_insecurely(curwin, "titlestring", 0); - called_emsg = false; build_stl_str_hl(curwin, buf, sizeof(buf), (char *)p_titlestring, use_sandbox, 0, maxlen, NULL, NULL); title_str = buf; - if (called_emsg) { + if (called_emsg > called_emsg_before) { set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR); } - called_emsg |= save_called_emsg; } else { title_str = (char *)p_titlestring; } @@ -3191,7 +3197,7 @@ void maketitle(void) // Get path of file, replace home dir with ~. *buf_p++ = ' '; *buf_p++ = '('; - home_replace(curbuf, (char *)curbuf->b_ffname, buf_p, + home_replace(curbuf, curbuf->b_ffname, buf_p, (SPACE_FOR_DIR - (size_t)(buf_p - buf)), true); #ifdef BACKSLASH_IN_FILENAME // Avoid "c:/name" to be reduced to "c". @@ -3200,7 +3206,7 @@ void maketitle(void) } #endif // Remove the file name. - char *p = (char *)path_tail_with_sep((char_u *)buf_p); + char *p = path_tail_with_sep(buf_p); if (p == buf_p) { // Must be a help buffer. xstrlcpy(buf_p, _("help"), SPACE_FOR_DIR - (size_t)(buf_p - buf)); @@ -3235,7 +3241,7 @@ void maketitle(void) if (maxlen > 0) { // Make it shorter by removing a bit in the middle. if (vim_strsize(buf) > maxlen) { - trunc_string((char_u *)buf, (char_u *)buf, maxlen, sizeof(buf)); + trunc_string(buf, buf, maxlen, sizeof(buf)); } } title_str = buf; @@ -3251,17 +3257,15 @@ void maketitle(void) if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { int use_sandbox = false; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; use_sandbox = was_set_insecurely(curwin, "iconstring", 0); - called_emsg = false; build_stl_str_hl(curwin, icon_str, sizeof(buf), (char *)p_iconstring, use_sandbox, 0, 0, NULL, NULL); - if (called_emsg) { + if (called_emsg > called_emsg_before) { set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR); } - called_emsg |= save_called_emsg; } else { icon_str = (char *)p_iconstring; } @@ -3270,18 +3274,18 @@ void maketitle(void) if (buf_spname(curbuf) != NULL) { buf_p = buf_spname(curbuf); } else { // use file name only in icon - buf_p = path_tail((char *)curbuf->b_ffname); + buf_p = path_tail(curbuf->b_ffname); } *icon_str = NUL; // Truncate name at 100 bytes. len = (int)STRLEN(buf_p); if (len > 100) { len -= 100; - len += mb_tail_off((char_u *)buf_p, (char_u *)buf_p + len) + 1; + len += mb_tail_off(buf_p, buf_p + len) + 1; buf_p += len; } STRCPY(icon_str, buf_p); - trans_characters((char_u *)icon_str, IOSIZE); + trans_characters(icon_str, IOSIZE); } } @@ -3679,7 +3683,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // The first digit group is the item's min width if (ascii_isdigit(*fmt_p)) { - minwid = getdigits_int((char_u **)&fmt_p, false, 0); + minwid = getdigits_int(&fmt_p, false, 0); } // User highlight groups override the min width field @@ -3762,7 +3766,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san if (*fmt_p == '.') { fmt_p++; if (ascii_isdigit(*fmt_p)) { - maxwid = getdigits_int((char_u **)&fmt_p, false, 50); + maxwid = getdigits_int(&fmt_p, false, 50); } } @@ -3815,11 +3819,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san if (buf_spname(wp->w_buffer) != NULL) { STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); } else { - char *t = (opt == STL_FULLPATH) ? (char *)wp->w_buffer->b_ffname + char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname : wp->w_buffer->b_fname; home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true); } - trans_characters(NameBuff, MAXPATHL); + trans_characters((char *)NameBuff, MAXPATHL); if (opt != STL_FILENAME) { str = (char *)NameBuff; } else { @@ -4699,7 +4703,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) if (i < alist->al_ga.ga_len && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum || path_full_compare(alist_name(&AARGLIST(alist)[i]), - (char *)buf->b_ffname, + buf->b_ffname, true, true) & kEqualFiles)) { int weight = 1; @@ -5267,9 +5271,9 @@ bool bt_terminal(const buf_T *const buf) return buf != NULL && buf->b_p_bt[0] == 't'; } -/// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" / +/// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" /// buffer. This means the buffer name is not a file name. -bool bt_nofile(const buf_T *const buf) +bool bt_nofilename(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') @@ -5278,6 +5282,13 @@ bool bt_nofile(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } +/// @return true if "buf" has 'buftype' set to "nofile". +bool bt_nofile(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return buf != NULL && buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f'; +} + /// @return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" /// buffer. bool bt_dontwrite(const buf_T *const buf) @@ -5329,7 +5340,7 @@ char *buf_spname(buf_T *buf) } // There is no _file_ when 'buftype' is "nofile", b_sfname // contains the name as specified by the user. - if (bt_nofile(buf)) { + if (bt_nofilename(buf)) { if (buf->b_fname != NULL) { return buf->b_fname; } @@ -5567,7 +5578,7 @@ bool buf_contents_changed(buf_T *buf) aucmd_prepbuf(&aco, newbuf); if (ml_open(curbuf) == OK - && readfile((char *)buf->b_ffname, buf->b_fname, + && readfile(buf->b_ffname, buf->b_fname, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, &ea, READ_NEW | READ_DUMMY, false) == OK) { // compare the two files line by line diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 5d1135f91c..8c70765d30 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -283,8 +283,8 @@ typedef struct { struct wininfo_S { wininfo_T *wi_next; // next entry or NULL for last entry wininfo_T *wi_prev; // previous entry or NULL for first entry - win_T *wi_win; // pointer to window that did set wi_fpos - pos_T wi_fpos; // last cursor position in the file + win_T *wi_win; // pointer to window that did set wi_mark + fmark_T wi_mark; // last cursor mark in the file bool wi_optset; // true when wi_opt has useful values winopt_T wi_opt; // local window options bool wi_fold_manual; // copy of w_fold_manual @@ -545,8 +545,8 @@ struct file_buffer { // b_sfname is the name as the user typed it (or NULL). // b_fname is the same as b_sfname, unless ":cd" has been done, // then it is the same as b_ffname (NULL for no name). - char_u *b_ffname; // full path file name, allocated - char_u *b_sfname; // short file name, allocated, may be equal to + char *b_ffname; // full path file name, allocated + char *b_sfname; // short file name, allocated, may be equal to // b_ffname char *b_fname; // current file name, points to b_ffname or // b_sfname @@ -829,7 +829,7 @@ struct file_buffer { int b_start_eol; // last line had eol when it was read int b_start_ffc; // first char of 'ff' when edit started - char_u *b_start_fenc; // 'fileencoding' when edit started or NULL + char *b_start_fenc; // 'fileencoding' when edit started or NULL int b_bad_char; // "++bad=" argument when edit started or 0 int b_start_bomb; // 'bomb' when it was read @@ -855,7 +855,7 @@ struct file_buffer { // are not used! Use the B_SPELL macro to // access b_spell without #ifdef. - char_u *b_prompt_text; // set by prompt_setprompt() + char *b_prompt_text; // set by prompt_setprompt() Callback b_prompt_callback; // set by prompt_setcallback() Callback b_prompt_interrupt; // set by prompt_setinterrupt() int b_prompt_insert; // value for restart_edit when entering @@ -963,8 +963,8 @@ struct tabpage_S { frame_T *(tp_snapshot[SNAP_COUNT]); ///< window layout snapshots ScopeDictDictItem tp_winvar; ///< Variable for "t:" Dictionary. dict_T *tp_vars; ///< Internal variables, local to tab page. - char_u *tp_localdir; ///< Absolute path of local cwd or NULL. - char_u *tp_prevdir; ///< Previous directory. + char *tp_localdir; ///< Absolute path of local cwd or NULL. + char *tp_prevdir; ///< Previous directory. }; /* @@ -1061,7 +1061,7 @@ struct matchitem { matchitem_T *next; int id; ///< match ID int priority; ///< match priority - char_u *pattern; ///< pattern to highlight + char *pattern; ///< pattern to highlight regmmatch_T match; ///< regexp program for pattern posmatch_T pos; ///< position matches match_T hl; ///< struct for doing the actual highlighting @@ -1144,8 +1144,9 @@ enum { MENU_INDEX_OP_PENDING = 3, MENU_INDEX_INSERT = 4, MENU_INDEX_CMDLINE = 5, - MENU_INDEX_TIP = 6, - MENU_MODES = 7, + MENU_INDEX_TERMINAL = 6, + MENU_INDEX_TIP = 7, + MENU_MODES = 8, }; typedef struct VimMenu vimmenu_T; @@ -1408,8 +1409,8 @@ struct window_S { // out of range!) int w_arg_idx_invalid; // editing another file than w_arg_idx - char_u *w_localdir; // absolute path of local directory or NULL - char_u *w_prevdir; // previous directory + char *w_localdir; // absolute path of local directory or NULL + char *w_prevdir; // previous directory // Options local to a window. // They are local because they influence the layout of the window or // depend on the window layout. @@ -1423,10 +1424,10 @@ struct window_S { uint32_t w_p_wbr_flags; // flags for 'winbar' uint32_t w_p_fde_flags; // flags for 'foldexpr' uint32_t w_p_fdt_flags; // flags for 'foldtext' - int *w_p_cc_cols; // array of columns to highlight or NULL - char_u w_p_culopt_flags; // flags for cursorline highlighting - long w_p_siso; // 'sidescrolloff' local value - long w_p_so; // 'scrolloff' local value + int *w_p_cc_cols; // array of columns to highlight or NULL + uint8_t w_p_culopt_flags; // flags for cursorline highlighting + long w_p_siso; // 'sidescrolloff' local value + long w_p_so; // 'scrolloff' local value int w_briopt_min; // minimum width for breakindent int w_briopt_shift; // additional shift for breakindent diff --git a/src/nvim/change.c b/src/nvim/change.c index a383fe6bb3..4568b71fd9 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -149,7 +149,13 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // set the '. mark if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) { - RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), 0); + fmarkv_T view = INIT_FMARKV; + // Set the markview only if lnum is visible, as changes might be done + // outside of the current window view. + if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) { + view = mark_view_make(curwin->w_topline, curwin->w_cursor); + } + RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), curbuf->handle, view); // Create a new entry if a new undo-able change was started or we // don't have an entry yet. @@ -1053,7 +1059,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) old_cursor = curwin->w_cursor; ptr = saved_line; if (flags & OPENLINE_DO_COM) { - lead_len = get_leader_len(ptr, NULL, false, true); + lead_len = get_leader_len((char *)ptr, NULL, false, true); } else { lead_len = 0; } @@ -1066,7 +1072,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) newindent = get_indent(); } if (flags & OPENLINE_DO_COM) { - lead_len = get_leader_len(ptr, NULL, false, true); + lead_len = get_leader_len((char *)ptr, NULL, false, true); } else { lead_len = 0; } @@ -1185,14 +1191,14 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // This may then be inserted in front of the new line. end_comment_pending = NUL; if (flags & OPENLINE_DO_COM) { - lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); + lead_len = get_leader_len((char *)saved_line, (char **)&lead_flags, dir == BACKWARD, true); if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD && (!has_format_option(FO_NO_OPEN_COMS) || (flags & OPENLINE_FORMAT))) { // Check for a line comment after code. comment_start = check_linecomment(saved_line); if (comment_start != MAXCOL) { - lead_len = get_leader_len(saved_line + comment_start, - &lead_flags, false, true); + lead_len = get_leader_len((char *)saved_line + comment_start, + (char **)&lead_flags, false, true); if (lead_len != 0) { lead_len += comment_start; if (did_do_comment != NULL) { @@ -1232,7 +1238,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } // find start of middle part - (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + (void)copy_option_part((char **)&p, (char *)lead_middle, COM_MAX_LEN, ","); require_blank = false; } @@ -1243,7 +1249,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } p++; } - (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + (void)copy_option_part((char **)&p, (char *)lead_middle, COM_MAX_LEN, ","); while (*p && p[-1] != ':') { // find end of end flags // Check whether we allow automatic ending of comments @@ -1252,7 +1258,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } p++; } - size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + size_t n = copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); if (end_comment_pending == -1) { // we can set it now end_comment_pending = lead_end[n - 1]; @@ -1371,7 +1377,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (*p == COM_RIGHT || *p == COM_LEFT) { c = *p++; } else if (ascii_isdigit(*p) || *p == '-') { - off = getdigits_int(&p, true, 0); + off = getdigits_int((char **)&p, true, 0); } else { p++; } @@ -1875,16 +1881,16 @@ void del_lines(long nlines, bool undo) /// When "flags" is not NULL, it is set to point to the flags of the recognized comment leader. /// "backward" must be true for the "O" command. /// If "include_space" is set, include trailing whitespace while calculating the length. -int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) +int get_leader_len(char *line, char **flags, bool backward, bool include_space) { int j; int got_com = false; char part_buf[COM_MAX_LEN]; // buffer for one option part - char_u *string; // pointer to comment string - char_u *list; + char *string; // pointer to comment string + char *list; int middle_match_len = 0; - char_u *prev_list; - char_u *saved_flags = NULL; + char *prev_list; + char *saved_flags = NULL; int result = 0; int i = 0; @@ -1896,15 +1902,15 @@ int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_spa while (line[i] != NUL) { // scan through the 'comments' option for a match int found_one = false; - for (list = curbuf->b_p_com; *list;) { + for (list = (char *)curbuf->b_p_com; *list;) { // Get one option part into part_buf[]. Advance "list" to next // one. Put "string" at start of string. if (!got_com && flags != NULL) { *flags = list; // remember where flags started } prev_list = list; - (void)copy_option_part(&list, (char_u *)part_buf, COM_MAX_LEN, ","); - string = (char_u *)vim_strchr(part_buf, ':'); + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); if (string == NULL) { // missing ':', ignore this part continue; } @@ -2017,15 +2023,15 @@ int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_spa /// /// When "flags" is not null, it is set to point to the flags describing the /// recognized comment leader. -int get_last_leader_offset(char_u *line, char_u **flags) +int get_last_leader_offset(char *line, char **flags) { int result = -1; int j; int lower_check_bound = 0; - char_u *string; - char_u *com_leader; - char_u *com_flags; - char_u *list; + char *string; + char *com_leader; + char *com_flags; + char *list; char part_buf[COM_MAX_LEN]; // buffer for one option part // Repeat to match several nested comment strings. @@ -2033,13 +2039,13 @@ int get_last_leader_offset(char_u *line, char_u **flags) while (--i >= lower_check_bound) { // scan through the 'comments' option for a match int found_one = false; - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; + for (list = (char *)curbuf->b_p_com; *list;) { + char *flags_save = list; // Get one option part into part_buf[]. Advance list to next one. // put string at start of string. - (void)copy_option_part(&list, (char_u *)part_buf, COM_MAX_LEN, ","); - string = (char_u *)vim_strchr(part_buf, ':'); + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); if (string == NULL) { // If everything is fine, this cannot actually // happen. continue; @@ -2118,14 +2124,14 @@ int get_last_leader_offset(char_u *line, char_u **flags) } len1 = (int)STRLEN(com_leader); - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; + for (list = (char *)curbuf->b_p_com; *list;) { + char *flags_save = list; - (void)copy_option_part(&list, (char_u *)part_buf2, COM_MAX_LEN, ","); + (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); if (flags_save == com_flags) { continue; } - string = (char_u *)vim_strchr(part_buf2, ':'); + string = vim_strchr(part_buf2, ':'); string++; while (ascii_iswhite(*string)) { string++; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 885ebca214..028dd70eb2 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -162,7 +162,7 @@ int buf_init_chartab(buf_T *buf, int global) } if (ascii_isdigit(*p)) { - c = getdigits_int((char_u **)&p, true, 0); + c = getdigits_int((char **)&p, true, 0); } else { c = mb_ptr2char_adv(&p); } @@ -172,7 +172,7 @@ int buf_init_chartab(buf_T *buf, int global) p++; if (ascii_isdigit(*p)) { - c2 = getdigits_int((char_u **)&p, true, 0); + c2 = getdigits_int((char **)&p, true, 0); } else { c2 = mb_ptr2char_adv(&p); } @@ -265,7 +265,7 @@ int buf_init_chartab(buf_T *buf, int global) /// /// @param buf /// @param bufsize -void trans_characters(char_u *buf, int bufsize) +void trans_characters(char *buf, int bufsize) { char_u *trs; // translated character int len = (int)STRLEN(buf); // length of string needing translation @@ -274,10 +274,10 @@ void trans_characters(char_u *buf, int bufsize) while (*buf != 0) { int trs_len; // length of trs[] // Assume a multi-byte character doesn't need translation. - if ((trs_len = utfc_ptr2len((char *)buf)) > 1) { + if ((trs_len = utfc_ptr2len(buf)) > 1) { len -= trs_len; } else { - trs = transchar_byte(*buf); + trs = transchar_byte((uint8_t)(*buf)); trs_len = (int)STRLEN(trs); if (trs_len > 1) { @@ -1302,7 +1302,7 @@ char_u *skiptowhite(const char_u *p) /// @param p /// /// @return Pointer to the next whitespace character. -char_u *skiptowhite_esc(char_u *p) +char *skiptowhite_esc(char *p) FUNC_ATTR_PURE { while (*p != ' ' && *p != '\t' && *p != NUL) { @@ -1364,9 +1364,9 @@ intmax_t getdigits(char_u **pp, bool strict, intmax_t def) /// Gets an int number from a string. /// /// @see getdigits -int getdigits_int(char_u **pp, bool strict, int def) +int getdigits_int(char **pp, bool strict, int def) { - intmax_t number = getdigits(pp, strict, def); + intmax_t number = getdigits((char_u **)pp, strict, def); #if SIZEOF_INTMAX_T > SIZEOF_INT if (strict) { assert(number >= INT_MIN && number <= INT_MAX); diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index c7c7e6ae58..62cf60e03b 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -189,7 +189,7 @@ char *parse_shape_opt(int what) if (!ascii_isdigit(*p)) { return N_("E548: digit expected"); } - int n = getdigits_int((char_u **)&p, false, 0); + int n = getdigits_int(&p, false, 0); if (len == 3) { // "ver" or "hor" if (n == 0) { return N_("E549: Illegal percentage"); diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index 803427dd17..0eaff06833 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -523,7 +523,7 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) if (bp->dbg_type == DBG_FUNC) { bp->dbg_name = vim_strsave((char_u *)p); } else if (here) { - bp->dbg_name = vim_strsave(curbuf->b_ffname); + bp->dbg_name = vim_strsave((char_u *)curbuf->b_ffname); } else if (bp->dbg_type == DBG_EXPR) { bp->dbg_name = vim_strsave((char_u *)p); bp->dbg_val = eval_expr_no_emsg(bp); diff --git a/src/nvim/diff.c b/src/nvim/diff.c index e4d77cec9c..75021e90d6 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2143,7 +2143,7 @@ int diffopt_changed(void) diff_flags_new |= DIFF_FILLER; } else if ((STRNCMP(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { p += 8; - diff_context_new = getdigits_int(&p, false, diff_context_new); + diff_context_new = getdigits_int((char **)&p, false, diff_context_new); } else if (STRNCMP(p, "iblank", 6) == 0) { p += 6; diff_flags_new |= DIFF_IBLANK; @@ -2167,7 +2167,7 @@ int diffopt_changed(void) diff_flags_new |= DIFF_VERTICAL; } else if ((STRNCMP(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { p += 11; - diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new); + diff_foldcolumn_new = getdigits_int((char **)&p, false, diff_foldcolumn_new); } else if (STRNCMP(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; @@ -2562,7 +2562,7 @@ void ex_diffgetput(exarg_T *eap) // digits only i = (int)atol(eap->arg); } else { - i = buflist_findpat((char_u *)eap->arg, p, false, true, false); + i = buflist_findpat(eap->arg, (char *)p, false, true, false); if (i < 0) { // error message already given diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index cbff15c84a..355900c93f 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1683,7 +1683,7 @@ void putdigraph(char_u *str) emsg(_(e_number_exp)); return; } - int n = getdigits_int(&str, true, 0); + int n = getdigits_int((char **)&str, true, 0); registerdigraph(char1, char2, n); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 1223d98fbf..0571e71cb5 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -136,7 +136,6 @@ static char *ctrl_x_mode_names[] = { }; static char e_hitend[] = N_("Hit end of paragraph"); -static char e_complwin[] = N_("E839: Completion function changed window"); static char e_compldel[] = N_("E840: Completion function deleted text"); /* @@ -1409,14 +1408,9 @@ bool edit(int cmdchar, bool startln, long count) // Don't allow changes in the buffer while editing the cmdline. The // caller of getcmdline() may get confused. - if (textlock != 0) { - emsg(_(e_secure)); - return false; - } - // Don't allow recursive insert mode when busy with completion. - if (compl_started || compl_busy || pum_visible()) { - emsg(_(e_secure)); + if (textlock != 0 || compl_started || compl_busy || pum_visible()) { + emsg(_(e_textlock)); return false; } @@ -1618,7 +1612,7 @@ char_u *buf_prompt_text(const buf_T *const buf) if (buf->b_prompt_text == NULL) { return (char_u *)"% "; } - return buf->b_prompt_text; + return (char_u *)buf->b_prompt_text; } // Return the effective prompt for the current buffer. @@ -2955,7 +2949,7 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i /* Expand wildcards in the dictionary name, but do not allow * backticks (for security, the 'dict' option may have been set in * a modeline). */ - copy_option_part(&dict, buf, LSIZE, ","); + copy_option_part((char **)&dict, (char *)buf, LSIZE, ","); if (!thesaurus && STRCMP(buf, "spell") == 0) { count = -1; } else if (vim_strchr((char *)buf, '`') != NULL @@ -3020,7 +3014,7 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) { ptr = buf; - while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { + while (vim_regexec(regmatch, (char *)buf, (colnr_T)(ptr - buf))) { ptr = regmatch->startp[0]; if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) { ptr = find_line_end(ptr); @@ -3944,8 +3938,6 @@ static void expand_by_function(int type, char_u *base) dict_T *matchdict = NULL; char_u *funcname; pos_T pos; - win_T *curwin_save; - buf_T *curbuf_save; typval_T rettv; const int save_State = State; @@ -3964,8 +3956,10 @@ static void expand_by_function(int type, char_u *base) args[1].vval.v_string = base != NULL ? (char *)base : ""; pos = curwin->w_cursor; - curwin_save = curwin; - curbuf_save = curbuf; + // Lock the text to avoid weird things from happening. Also disallow + // switching to another window, it should not be needed and may end up in + // Insert mode in another buffer. + textlock++; // Call a function, which returns a list or dict. if (call_vim_function((char *)funcname, 2, args, &rettv) == OK) { @@ -3984,11 +3978,8 @@ static void expand_by_function(int type, char_u *base) break; } } + textlock--; - if (curwin_save != curwin || curbuf_save != curbuf) { - emsg(_(e_complwin)); - goto theend; - } curwin->w_cursor = pos; // restore the cursor position validate_cursor(); if (!equalpos(curwin->w_cursor, pos)) { @@ -4223,9 +4214,9 @@ static int ins_compl_get_exp(pos_T *ini) msg_hist_off = true; // reset in msg_trunc_attr() vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), ins_buf->b_fname == NULL - ? (char_u *)buf_spname(ins_buf) + ? buf_spname(ins_buf) : ins_buf->b_sfname == NULL - ? (char_u *)ins_buf->b_fname + ? ins_buf->b_fname : ins_buf->b_sfname); (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); } else if (*e_cpt == NUL) { @@ -4257,7 +4248,7 @@ static int ins_compl_get_exp(pos_T *ini) } // in any case e_cpt is advanced to the next entry - (void)copy_option_part(&e_cpt, IObuff, IOSIZE, ","); + (void)copy_option_part((char **)&e_cpt, (char *)IObuff, IOSIZE, ","); found_all = true; if (type == -1) { @@ -4313,7 +4304,7 @@ static int ins_compl_get_exp(pos_T *ini) if (find_tags(compl_pattern, &num_matches, &matches, TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP | (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), - TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { + TAG_MANY, (char_u *)curbuf->b_ffname) == OK && num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } g_tag_at_cursor = false; @@ -4516,7 +4507,8 @@ static int ins_compl_get_exp(pos_T *ini) } } } - if (ins_compl_add_infercase(ptr, len, p_ic, ins_buf == curbuf ? NULL : ins_buf->b_sfname, + if (ins_compl_add_infercase(ptr, len, p_ic, + ins_buf == curbuf ? NULL : (char_u *)ins_buf->b_sfname, 0, cont_s_ipos) != NOTDONE) { found_new_match = OK; break; @@ -5225,8 +5217,6 @@ static int ins_complete(int c, bool enable_pum) // set to 1 to obtain the length of text to use for completion. char_u *funcname; pos_T pos; - win_T *curwin_save; - buf_T *curbuf_save; const int save_State = State; // Call 'completefunc' or 'omnifunc' and get pattern length as a string @@ -5247,15 +5237,11 @@ static int ins_complete(int c, bool enable_pum) args[1].vval.v_string = ""; pos = curwin->w_cursor; - curwin_save = curwin; - curbuf_save = curbuf; + textlock++; colnr_T col = (colnr_T)call_func_retnr((char *)funcname, 2, args); + textlock--; State = save_State; - if (curwin_save != curwin || curbuf_save != curbuf) { - emsg(_(e_complwin)); - return FAIL; - } curwin->w_cursor = pos; // restore the cursor position validate_cursor(); if (!equalpos(curwin->w_cursor, pos)) { @@ -5773,21 +5759,18 @@ void insertchar(int c, int flags, int second_indent) // Check whether this character should end a comment. if (did_ai && c == end_comment_pending) { - char_u *line; char_u lead_end[COM_MAX_LEN]; // end-comment string - int i; - /* - * Need to remove existing (middle) comment leader and insert end - * comment leader. First, check what comment leader we can find. - */ - i = get_leader_len(line = get_cursor_line_ptr(), &p, false, true); + // Need to remove existing (middle) comment leader and insert end + // comment leader. First, check what comment leader we can find. + char_u *line = get_cursor_line_ptr(); + int i = get_leader_len((char *)line, (char **)&p, false, true); if (i > 0 && vim_strchr((char *)p, COM_MIDDLE) != NULL) { // Just checking // Skip middle-comment string while (*p && p[-1] != ':') { // find end of middle flags p++; } - int middle_len = (int)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + int middle_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); // Don't count trailing white space for middle_len while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) { middle_len--; @@ -5797,7 +5780,7 @@ void insertchar(int c, int flags, int second_indent) while (*p && p[-1] != ':') { // find end of end flags p++; } - int end_len = (int)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + int end_len = (int)copy_option_part((char **)&p, (char *)lead_end, COM_MAX_LEN, ","); // Skip white space before the cursor i = curwin->w_cursor.col; @@ -5974,12 +5957,12 @@ static void internal_format(int textwidth, int second_indent, int flags, int for // Don't break until after the comment leader if (do_comments) { char_u *line = get_cursor_line_ptr(); - leader_len = get_leader_len(line, NULL, false, true); + leader_len = get_leader_len((char *)line, NULL, false, true); if (leader_len == 0 && curbuf->b_p_cin) { // Check for a line comment after code. int comment_start = check_linecomment(line); if (comment_start != MAXCOL) { - leader_len = get_leader_len(line + comment_start, NULL, false, true); + leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); if (leader_len != 0) { leader_len += comment_start; } @@ -6374,7 +6357,7 @@ void auto_format(bool trailblank, bool prev_line) // With the 'c' flag in 'formatoptions' and 't' missing: only format // comments. if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) - && get_leader_len(old, NULL, false, true) == 0) { + && get_leader_len((char *)old, NULL, false, true) == 0) { return; } @@ -7332,7 +7315,7 @@ static void replace_do_bs(int limit_col) } /// Check that C-indenting is on. -static bool cindent_on(void) +bool cindent_on(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); @@ -7928,7 +7911,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Remember the last Insert position in the '^ mark. if ((cmdmod.cmod_flags & CMOD_KEEPJUMPS) == 0) { - RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum); + fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor); + RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum, view); } /* @@ -7936,13 +7920,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) * Don't do it for CTRL-O, unless past the end of the line. */ if (!nomove - && (curwin->w_cursor.col != 0 - || curwin->w_cursor.coladd > 0 - ) - && (restart_edit == NUL - || (gchar_cursor() == NUL - && !VIsual_active - )) + && (curwin->w_cursor.col != 0 || curwin->w_cursor.coladd > 0) + && (restart_edit == NUL || (gchar_cursor() == NUL && !VIsual_active)) && !revins_on) { if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) { oneleft(); @@ -8374,7 +8353,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } // delete characters until we are at or before want_vcol - while (vcol > want_vcol + while (vcol > want_vcol && curwin->w_cursor.col > 0 && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) { ins_bs_one(&vcol); } @@ -8558,14 +8537,12 @@ static void ins_mousescroll(int dir) } // Don't scroll the window in which completion is being done. - if (!pum_visible() - || curwin != old_curwin) { + if (!pum_visible() || curwin != old_curwin) { if (dir == MSCR_DOWN || dir == MSCR_UP) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { - scroll_redraw(dir, - (curwin->w_botline - curwin->w_topline)); + scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline)); } else { - scroll_redraw(dir, 3L); + scroll_redraw(dir, p_mousescroll_vert); } } else { mouse_scroll_horiz(dir); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bb2404750b..096fcba981 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -986,7 +986,7 @@ void restore_vimvar(int idx, typval_T *save_tv) vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); + hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); if (HASHITEM_EMPTY(hi)) { internal_error("restore_vimvar()"); } else { @@ -2822,7 +2822,7 @@ void ex_lockvar(exarg_T *eap) if (eap->forceit) { deep = -1; } else if (ascii_isdigit(*arg)) { - deep = getdigits_int((char_u **)&arg, false, -1); + deep = getdigits_int(&arg, false, -1); arg = skipwhite(arg); } @@ -3019,7 +3019,7 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) } } - hashitem_T *hi = hash_find(ht, (const char_u *)varname); + hashitem_T *hi = hash_find(ht, varname); if (HASHITEM_EMPTY(hi)) { hi = find_hi_in_scoped_ht(name, &ht); } @@ -6554,7 +6554,8 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const const char *prompt = ""; const char *defstr = ""; - const char *cancelreturn = NULL; + typval_T *cancelreturn = NULL; + typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE; const char *xp_name = NULL; Callback input_callback = { .type = kCallbackNone }; char prompt_buf[NUMBUFLEN]; @@ -6576,13 +6577,9 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const if (defstr == NULL) { return; } - cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), - cancelreturn_buf, def); - if (cancelreturn == NULL) { // error - return; - } - if (*cancelreturn == NUL) { - cancelreturn = NULL; + dictitem_T *cancelreturn_di = tv_dict_find(dict, S_LEN("cancelreturn")); + if (cancelreturn_di != NULL) { + cancelreturn = &cancelreturn_di->di_tv; } xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), xp_name_buf, def); @@ -6606,15 +6603,16 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const return; } if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const arg2 = tv_get_string_buf_chk(&argvars[2], - cancelreturn_buf); - if (arg2 == NULL) { + const char *const strarg2 = tv_get_string_buf_chk(&argvars[2], cancelreturn_buf); + if (strarg2 == NULL) { return; } if (inputdialog) { - cancelreturn = arg2; + cancelreturn_strarg2.v_type = VAR_STRING; + cancelreturn_strarg2.vval.v_string = (char *)strarg2; + cancelreturn = &cancelreturn_strarg2; } else { - xp_name = arg2; + xp_name = strarg2; } } } @@ -6662,7 +6660,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const callback_free(&input_callback); if (rettv->vval.v_string == NULL && cancelreturn != NULL) { - rettv->vval.v_string = xstrdup(cancelreturn); + tv_copy(cancelreturn, rettv); } xfree(xp_arg); @@ -7296,7 +7294,7 @@ void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; int save_did_emsg = did_emsg; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; const bool save_ex_pressedreturn = get_pressedreturn(); if (timer->stopped || timer->paused) { @@ -7313,19 +7311,17 @@ void timer_due_cb(TimeWatcher *tw, void *data) argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = timer->timer_id; typval_T rettv = TV_INITIAL_VALUE; - called_emsg = false; callback_call(&timer->callback, 1, argv, &rettv); // Handle error message - if (called_emsg && did_emsg) { + if (called_emsg > called_emsg_before && did_emsg) { timer->emsg_count++; if (current_exception != NULL) { discard_current_exception(); } } did_emsg = save_did_emsg; - called_emsg = save_called_emsg; set_pressedreturn(save_ex_pressedreturn); if (timer->emsg_count >= 3) { @@ -7754,11 +7750,14 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret } } else if (name[0] == '\'') { // mark - const pos_T *const pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); - if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) { + int mname = (uint8_t)name[1]; + const fmark_T *const fm = mark_get(curbuf, curwin, NULL, kMarkAll, mname); + if (fm == NULL || fm->mark.lnum <= 0) { return NULL; } - pos = *pp; + pos = fm->mark; + // Vimscript behavior, only provide fnum if mark is global. + *ret_fnum = ASCII_ISUPPER(mname) || ascii_isdigit(mname) ? fm->fnum: *ret_fnum; } if (pos.lnum != 0) { if (charcol) { @@ -9811,7 +9810,7 @@ void func_line_start(void *cookie) if (fp->uf_profiling && sourcing_lnum >= 1 && sourcing_lnum <= fp->uf_lines.ga_len) { - fp->uf_tml_idx = (int)(sourcing_lnum - 1); + fp->uf_tml_idx = sourcing_lnum - 1; // Skip continuation lines. while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) { fp->uf_tml_idx--; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f50b355045..022e2497b7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -476,13 +476,13 @@ static buf_T *find_buffer(typval_T *avar) if (avar->v_type == VAR_NUMBER) { buf = buflist_findnr((int)avar->vval.v_number); } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { - buf = buflist_findname_exp((char_u *)avar->vval.v_string); + buf = buflist_findname_exp(avar->vval.v_string); if (buf == NULL) { /* No full path name match, try a match with a URL or a "nofile" * buffer, these don't use the full path. */ FOR_ALL_BUFFERS(bp) { if (bp->b_fname != NULL - && (path_with_url(bp->b_fname) || bt_nofile(bp)) + && (path_with_url(bp->b_fname) || bt_nofilename(bp)) && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { buf = bp; break; @@ -498,7 +498,7 @@ static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *name = (char_u *)tv_get_string(&argvars[0]); - rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); + rettv->vval.v_number = buflist_add(*name == NUL ? NULL : (char *)name, 0); } /// "bufexists(expr)" function @@ -586,7 +586,7 @@ static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) && tv_get_number_chk(&argvars[1], &error) != 0 && !error && (name = tv_get_string_chk(&argvars[0])) != NULL) { - buf = buflist_new((char_u *)name, NULL, 1, 0); + buf = buflist_new((char *)name, NULL, 1, 0); } if (buf != NULL) { @@ -655,7 +655,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) save_cpo = p_cpo; p_cpo = ""; - buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name), + buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), true, false, curtab_only)); p_magic = save_magic; @@ -1070,15 +1070,12 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_LIST) { emsg(_(e_invarg)); - return; - } - - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol <= 0) { - return; + } else { + const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); + if (startcol > 0) { + set_completion(startcol - 1, argvars[1].vval.v_list); + } } - - set_completion(startcol - 1, argvars[1].vval.v_list); } /// "complete_add()" function @@ -2051,6 +2048,12 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) (void)os_can_exe(tv_get_string(&argvars[0]), &path, true); +#ifdef BACKSLASH_IN_FILENAME + if (path != NULL) { + slash_adjust((char_u *)path); + } +#endif + rettv->v_type = VAR_STRING; rettv->vval.v_string = path; } @@ -2396,7 +2399,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, first ? strlen(fname) : 0, 0, first, path, - find_what, curbuf->b_ffname, + find_what, (char_u *)curbuf->b_ffname, (find_what == FINDFILE_DIR ? (char_u *)"" : curbuf->b_p_sua)); @@ -2464,7 +2467,7 @@ static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "fnameescape({string})" function static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_string = vim_strsave_fnameescape(tv_get_string(&argvars[0]), false); + rettv->vval.v_string = vim_strsave_fnameescape(tv_get_string(&argvars[0]), VSE_NONE); rettv->v_type = VAR_STRING; } @@ -2574,7 +2577,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - unsigned long count = (unsigned long)(foldend - foldstart + 1); + unsigned long count = (unsigned long)foldend - foldstart + 1; txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); r = xmalloc(STRLEN(txt) + STRLEN(dashes) // for %s @@ -3308,8 +3311,8 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) [kCdScopeTabpage] = 0, // Number of tab to look at. }; - char_u *cwd = NULL; // Current working directory to print - char_u *from = NULL; // The original string to copy + char *cwd = NULL; // Current working directory to print + char *from = NULL; // The original string to copy tabpage_T *tp = curtab; // The tabpage to look at. win_T *win = curwin; // The window to look at. @@ -3386,13 +3389,13 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) FALLTHROUGH; case kCdScopeGlobal: if (globaldir) { // `globaldir` is not always set. - from = (char_u *)globaldir; + from = globaldir; break; } FALLTHROUGH; // In global directory, just need to get OS CWD. case kCdScopeInvalid: // If called without any arguments, get OS CWD. - if (os_dirname(cwd, MAXPATHL) == FAIL) { - from = (char_u *)""; // Return empty string on failure. + if (os_dirname((char_u *)cwd, MAXPATHL) == FAIL) { + from = ""; // Return empty string on failure. } } @@ -3400,7 +3403,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) STRLCPY(cwd, from, MAXPATHL); } - rettv->vval.v_string = (char *)vim_strsave(cwd); + rettv->vval.v_string = xstrdup(cwd); #ifdef BACKSLASH_IN_FILENAME slash_adjust(rettv->vval.v_string); #endif @@ -3899,15 +3902,14 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) typval_T argv = TV_INITIAL_VALUE; typval_T exprval = TV_INITIAL_VALUE; bool error = false; - int save_called_emsg = called_emsg; - called_emsg = false; + const int called_emsg_before = called_emsg; LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, eval_expr_typval(&expr, &argv, 0, &exprval) != OK || tv_get_number_chk(&exprval, &error) - || called_emsg || error || got_int); + || called_emsg > called_emsg_before || error || got_int); - if (called_emsg || error) { + if (called_emsg > called_emsg_before || error) { rettv->vval.v_number = -3; } else if (got_int) { got_int = false; @@ -3917,8 +3919,6 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } - called_emsg = save_called_emsg; - // Stop dummy timer time_watcher_stop(tw); time_watcher_close(tw, dummy_timer_close_cb); @@ -5968,7 +5968,7 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (*path_tail(dir) == NUL) { // Remove trailing slashes. - *path_tail_with_sep((char_u *)dir) = NUL; + *path_tail_with_sep((char *)dir) = NUL; } if (argvars[1].v_type != VAR_UNKNOWN) { @@ -6366,20 +6366,17 @@ static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "prompt_setprompt({buffer}, {text})" function static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - buf_T *buf; - const char_u *text; - if (check_secure()) { return; } - buf = tv_get_buf(&argvars[0], false); + buf_T *buf = tv_get_buf(&argvars[0], false); if (buf == NULL) { return; } - text = (const char_u *)tv_get_string(&argvars[1]); + const char *text = tv_get_string(&argvars[1]); xfree(buf->b_prompt_text); - buf->b_prompt_text = vim_strsave(text); + buf->b_prompt_text = xstrdup(text); } /// "pum_getpos()" function @@ -7341,7 +7338,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!has_trailing_pathsep) { q = p + strlen(p); if (after_pathsep(p, q)) { - *path_tail_with_sep((char_u *)p) = NUL; + *path_tail_with_sep(p) = NUL; } } @@ -8521,7 +8518,7 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) rettv->vval.v_number = 0; } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { + if (setmark_pos((uint8_t)name[1], &pos, fnum, NULL) == OK) { rettv->vval.v_number = 0; } } else { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 97726da5f4..e19cf411c0 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1434,7 +1434,7 @@ dictitem_T *tv_dict_item_copy(dictitem_T *const di) void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item) FUNC_ATTR_NONNULL_ALL { - hashitem_T *const hi = hash_find(&dict->dv_hashtab, item->di_key); + hashitem_T *const hi = hash_find(&dict->dv_hashtab, (char *)item->di_key); if (HASHITEM_EMPTY(hi)) { semsg(_(e_intern2), "tv_dict_item_remove()"); } else { @@ -1567,7 +1567,7 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key, const ptr return NULL; } hashitem_T *const hi = (len < 0 - ? hash_find(&d->dv_hashtab, (const char_u *)key) + ? hash_find(&d->dv_hashtab, key) : hash_find_len(&d->dv_hashtab, key, (size_t)len)); if (HASHITEM_EMPTY(hi)) { return NULL; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 8646520ec3..c2579944e4 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -547,9 +547,7 @@ static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf /// @return NULL for unknown function. ufunc_T *find_func(const char_u *name) { - hashitem_T *hi; - - hi = hash_find(&func_hashtab, name); + hashitem_T *hi = hash_find(&func_hashtab, (char *)name); if (!HASHITEM_EMPTY(hi)) { return HI2UF(hi); } @@ -724,7 +722,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) /// @return true if the entry was deleted, false if it wasn't found. static bool func_remove(ufunc_T *fp) { - hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + hashitem_T *hi = hash_find(&func_hashtab, (char *)UF2HIKEY(fp)); if (!HASHITEM_EMPTY(hi)) { hash_remove(&func_hashtab, hi); @@ -1045,7 +1043,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett char *s = tofree; char buf[MSG_BUF_LEN]; if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, sizeof(buf)); + trunc_string(s, buf, MSG_BUF_CLEN, sizeof(buf)); s = buf; } msg_puts(s); @@ -1159,7 +1157,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett emsg_off--; if (s != NULL) { if (vim_strsize(s) > MSG_BUF_CLEN) { - trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, MSG_BUF_LEN); + trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); s = buf; } smsg(_("%s returning %s"), sourcing_name, s); @@ -1976,7 +1974,7 @@ void ex_function(exarg_T *eap) --todo; fp = HI2UF(hi); if (!isdigit(*fp->uf_name) - && vim_regexec(®match, fp->uf_name, 0)) { + && vim_regexec(®match, (char *)fp->uf_name, 0)) { list_func_head(fp, false, false); } } @@ -2537,7 +2535,7 @@ void ex_function(exarg_T *eap) // insert the new function in the function list STRCPY(fp->uf_name, name); if (overwrite) { - hi = hash_find(&func_hashtab, name); + hi = hash_find(&func_hashtab, (char *)name); hi->hi_key = UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index f40573a0bc..1b5cc23b09 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -27,6 +27,7 @@ void loop_init(Loop *loop, void *data) uv_signal_init(&loop->uv, &loop->children_watcher); uv_timer_init(&loop->uv, &loop->children_kill_timer); uv_timer_init(&loop->uv, &loop->poll_timer); + uv_timer_init(&loop->uv, &loop->exit_delay_timer); loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag } @@ -136,6 +137,7 @@ bool loop_close(Loop *loop, bool wait) uv_close((uv_handle_t *)&loop->children_watcher, NULL); uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb); + uv_close((uv_handle_t *)&loop->exit_delay_timer, NULL); uv_close((uv_handle_t *)&loop->async, NULL); uint64_t start = wait ? os_hrtime() : 0; bool didstop = false; diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index c0c5bc527f..65980c6c05 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -36,6 +36,8 @@ typedef struct loop { // generic timer, used by loop_poll_events() uv_timer_t poll_timer; + uv_timer_t exit_delay_timer; + uv_async_t async; uv_mutex_t mutex; int recursive; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 1ec11f1eb6..e029f778f6 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -386,11 +386,13 @@ static void process_close_handles(void **argv) { Process *proc = argv[0]; + exit_need_delay++; flush_stream(proc, &proc->out); flush_stream(proc, &proc->err); process_close_streams(proc); process_close(proc); + exit_need_delay--; } static void on_process_exit(Process *proc) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8016c69bcb..28e1893b31 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -560,7 +560,7 @@ void ex_sort(exarg_T *eap) start_col = 0; end_col = len; - if (regmatch.regprog != NULL && vim_regexec(®match, (char_u *)s, 0)) { + if (regmatch.regprog != NULL && vim_regexec(®match, s, 0)) { if (sort_rx) { start_col = (colnr_T)(regmatch.startp[0] - (char_u *)s); end_col = (colnr_T)(regmatch.endp[0] - (char_u *)s); @@ -1732,19 +1732,19 @@ int rename_buffer(char *new_fname) * But don't set the alternate file name if the buffer didn't have a * name. */ - fname = (char *)curbuf->b_ffname; - sfname = (char *)curbuf->b_sfname; + fname = curbuf->b_ffname; + sfname = curbuf->b_sfname; xfname = curbuf->b_fname; curbuf->b_ffname = NULL; curbuf->b_sfname = NULL; if (setfname(curbuf, new_fname, NULL, true) == FAIL) { - curbuf->b_ffname = (char_u *)fname; - curbuf->b_sfname = (char_u *)sfname; + curbuf->b_ffname = fname; + curbuf->b_sfname = sfname; return FAIL; } curbuf->b_flags |= BF_NOTEDITED; if (xfname != NULL && *xfname != NUL) { - buf = buflist_new((char_u *)fname, (char_u *)xfname, curwin->w_cursor.lnum, 0); + buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0); if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { curwin->w_alt_fnum = buf->b_fnum; } @@ -1842,7 +1842,7 @@ int do_write(exarg_T *eap) if (free_fname != NULL) { ffname = free_fname; } - other = otherfile((char_u *)ffname); + other = otherfile(ffname); } /* @@ -1851,9 +1851,9 @@ int do_write(exarg_T *eap) if (other) { if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL || eap->cmdidx == CMD_saveas) { - alt_buf = setaltfname((char_u *)ffname, (char_u *)fname, (linenr_T)1); + alt_buf = setaltfname(ffname, fname, (linenr_T)1); } else { - alt_buf = buflist_findname((char_u *)ffname); + alt_buf = buflist_findname(ffname); } if (alt_buf != NULL && alt_buf->b_ml.ml_mfp != NULL) { // Overwriting a file that is loaded in another buffer is not a @@ -1873,7 +1873,7 @@ int do_write(exarg_T *eap) } if (!other) { - ffname = (char *)curbuf->b_ffname; + ffname = curbuf->b_ffname; fname = curbuf->b_fname; // Not writing the whole file is only allowed with '!'. if ((eap->line1 != 1 @@ -1913,12 +1913,12 @@ int do_write(exarg_T *eap) fname = alt_buf->b_fname; alt_buf->b_fname = curbuf->b_fname; curbuf->b_fname = fname; - fname = (char *)alt_buf->b_ffname; + fname = alt_buf->b_ffname; alt_buf->b_ffname = curbuf->b_ffname; - curbuf->b_ffname = (char_u *)fname; - fname = (char *)alt_buf->b_sfname; + curbuf->b_ffname = fname; + fname = alt_buf->b_sfname; alt_buf->b_sfname = curbuf->b_sfname; - curbuf->b_sfname = (char_u *)fname; + curbuf->b_sfname = fname; buf_name_changed(curbuf); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, alt_buf); @@ -1942,7 +1942,7 @@ int do_write(exarg_T *eap) // Autocommands may have changed buffer names, esp. when // 'autochdir' is set. - fname = (char *)curbuf->b_sfname; + fname = curbuf->b_sfname; } name_was_missing = curbuf->b_ffname == NULL; @@ -1980,17 +1980,14 @@ theend: /// @return OK if it's OK, FAIL if it is not. int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int other) { - /* - * write to other file or b_flags set or not writing the whole file: - * overwriting only allowed with '!' - */ + // Write to another file or b_flags set or not writing the whole file: + // overwriting only allowed with '!' if ((other || (buf->b_flags & BF_NOTEDITED) || ((buf->b_flags & BF_NEW) && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) || (buf->b_flags & BF_READERR)) && !p_wa - && !bt_nofile(buf) && os_path_exists((char_u *)ffname)) { if (!eap->forceit && !eap->append) { #ifdef UNIX @@ -2031,7 +2028,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth } else { dir = xmalloc(MAXPATHL); p = (char *)p_dir; - copy_option_part((char_u **)&p, (char_u *)dir, MAXPATHL, ","); + copy_option_part(&p, dir, MAXPATHL, ","); } swapname = (char *)makeswapname((char_u *)fname, (char_u *)ffname, curbuf, (char_u *)dir); xfree(dir); @@ -2112,7 +2109,7 @@ void do_wqall(exarg_T *eap) semsg(_("E141: No file name for buffer %" PRId64), (int64_t)buf->b_fnum); error++; } else if (check_readonly(&eap->forceit, buf) - || check_overwrite(eap, buf, buf->b_fname, (char *)buf->b_ffname, false) == FAIL) { + || check_overwrite(eap, buf, buf->b_fname, buf->b_ffname, false) == FAIL) { error++; } else { bufref_T bufref; @@ -2155,8 +2152,8 @@ static int check_readonly(int *forceit, buf_T *buf) // Handle a file being readonly when the 'readonly' option is set or when // the file exists and permissions are read-only. if (!*forceit && (buf->b_p_ro - || (os_path_exists(buf->b_ffname) - && !os_file_is_writable((char *)buf->b_ffname)))) { + || (os_path_exists((char_u *)buf->b_ffname) + && !os_file_is_writable(buf->b_ffname)))) { if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && buf->b_fname != NULL) { char buff[DIALOG_MSG_SIZE]; @@ -2218,7 +2215,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln if (fnum == 0) { // make ffname full path, set sfname fname_expand(curbuf, &ffname, &sfname); - other = otherfile((char_u *)ffname); + other = otherfile(ffname); free_me = ffname; // has been allocated, free() later } else { other = (fnum != curbuf->b_fnum); @@ -2339,7 +2336,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum } #ifdef USE_FNAME_CASE if (sfname != NULL) { - path_fix_case((char_u *)sfname); // set correct case for sfname + path_fix_case(sfname); // set correct case for sfname } #endif @@ -2354,14 +2351,14 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum other_file = false; } else { if (*ffname == NUL) { // re-edit with same file name - ffname = (char *)curbuf->b_ffname; + ffname = curbuf->b_ffname; sfname = curbuf->b_fname; } free_fname = fix_fname(ffname); // may expand to full path name if (free_fname != NULL) { ffname = free_fname; } - other_file = otherfile((char_u *)ffname); + other_file = otherfile(ffname); } } @@ -2384,7 +2381,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) | (eap == NULL ? 0 : CCGD_EXCMD))) { if (fnum == 0 && other_file && ffname != NULL) { - (void)setaltfname((char_u *)ffname, (char_u *)sfname, newlnum < 0 ? 0 : newlnum); + (void)setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum); } goto theend; } @@ -2415,6 +2412,8 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum * Otherwise we re-use the current buffer. */ if (other_file) { + const int prev_alt_fnum = curwin->w_alt_fnum; + if (!(flags & (ECMD_ADDBUF | ECMD_ALTBUF))) { if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { curwin->w_alt_fnum = curbuf->b_fnum; @@ -2441,13 +2440,13 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Add BLN_NOCURWIN to avoid a new wininfo items are associated // with the current window. const buf_T *const newbuf - = buflist_new((char_u *)ffname, (char_u *)sfname, tlnum, BLN_LISTED | BLN_NOCURWIN); + = buflist_new(ffname, sfname, tlnum, BLN_LISTED | BLN_NOCURWIN); if (newbuf != NULL && (flags & ECMD_ALTBUF)) { curwin->w_alt_fnum = newbuf->b_fnum; } goto theend; } - buf = buflist_new((char_u *)ffname, (char_u *)sfname, 0L, + buf = buflist_new(ffname, sfname, 0L, BLN_CURBUF | (flags & ECMD_SET_HELP ? 0 : BLN_LISTED)); // Autocmds may change curwin and curbuf. if (oldwin != NULL) { @@ -2458,6 +2457,10 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum if (buf == NULL) { goto theend; } + if (curwin->w_alt_fnum == buf->b_fnum && prev_alt_fnum != 0) { + // reusing the buffer, keep the old alternate file + curwin->w_alt_fnum = prev_alt_fnum; + } if (buf->b_ml.ml_mfp == NULL) { // No memfile yet. oldbuf = false; @@ -2480,7 +2483,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // May jump to last used line number for a loaded buffer or when asked // for explicitly if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) { - pos = buflist_findfpos(buf); + pos = &buflist_findfmark(buf)->mark; newlnum = pos->lnum; solcol = pos->col; } @@ -3651,6 +3654,13 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T sub_needs_free = cmdpreview && sub != source; } + bool cmdheight0 = p_ch < 1 && !ui_has(kUIMessages); + if (cmdheight0) { + // If cmdheight is 0, cmdheight must be set to 1 when we enter command line. + set_option_value("ch", 1L, NULL, 0); + redraw_statuslines(); + } + // Check for a match on each line. // If preview: limit to max('cmdwinheight', viewport). linenr_T line2 = eap->line2; @@ -3861,6 +3871,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T curwin->w_cursor.col = 0; } getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); + curwin->w_cursor.col = regmatch.startpos[0].col; if (subflags.do_number || curwin->w_p_nu) { int numw = number_width(curwin) + 1; sc += numw; @@ -3870,7 +3881,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T prompt = xmallocz((size_t)ec + 1); memset(prompt, ' ', (size_t)sc); memset(prompt + sc, '^', (size_t)(ec - sc) + 1); - resp = getcmdline_prompt((char)(-1), prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); + resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); msg_putchar('\n'); xfree(prompt); if (resp != NULL) { @@ -4461,6 +4472,11 @@ skip: } } + if (cmdheight0) { + // Restore cmdheight + set_option_value("ch", 0L, NULL, 0); + } + kv_destroy(preview_lines.subresults); return retv; #undef ADJUST_SUB_FIRSTLNUM @@ -4843,8 +4859,7 @@ void ex_help(exarg_T *eap) * Re-use an existing help window or open a new one. * Always open a new one for ":tab help". */ - if (!bt_help(curwin->w_buffer) - || cmdmod.cmod_tab != 0) { + if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { if (cmdmod.cmod_tab != 0) { wp = NULL; } else { @@ -5007,7 +5022,15 @@ static int help_compare(const void *s1, const void *s2) p1 = *(char **)s1 + strlen(*(char **)s1) + 1; p2 = *(char **)s2 + strlen(*(char **)s2) + 1; - return strcmp(p1, p2); + + // Compare by help heuristic number first. + int cmp = strcmp(p1, p2); + if (cmp != 0) { + return cmp; + } + + // Compare by strings as tie-breaker when same heuristic number. + return strcmp(*(char **)s1, *(char **)s2); } /// Find all help tags matching "arg", sort them and return in matches[], with @@ -5368,7 +5391,7 @@ void fix_help_buffer(void) // $VIMRUNTIME. char *p = (char *)p_rtp; while (*p != NUL) { - copy_option_part((char_u **)&p, NameBuff, MAXPATHL, ","); + copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); char *const rt = vim_getenv("VIMRUNTIME"); if (rt != NULL && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index b18bdefc2a..a5ba5e0b30 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -26,6 +26,7 @@ local SBOXOK = 0x40000 local CMDWIN = 0x80000 local MODIFY = 0x100000 local FLAGS = 0x200000 +local LOCK_OK = 0x1000000 local PREVIEW = 0x8000000 local FILES = bit.bor(XFILE, EXTRA) local WORD1 = bit.bor(EXTRA, NOSPC) @@ -41,19 +42,19 @@ module.flags = { module.cmds = { { command='append', - flags=bit.bor(BANG, RANGE, ZEROR, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(BANG, RANGE, ZEROR, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_append', }, { command='abbreviate', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='abclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abclear', }, @@ -71,13 +72,13 @@ module.cmds = { }, { command='amenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='anoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -131,25 +132,25 @@ module.cmds = { }, { command='ascii', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='do_ascii', }, { command='autocmd', - flags=bit.bor(BANG, EXTRA, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(BANG, EXTRA, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_autocmd', }, { command='augroup', - flags=bit.bor(BANG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_autocmd', }, { command='aunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, @@ -173,13 +174,13 @@ module.cmds = { }, { command='badd', - flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN), + flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_edit', }, { command='balt', - flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN), + flags=bit.bor(NEEDARG, FILE1, CMDARG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_edit', }, @@ -191,7 +192,7 @@ module.cmds = { }, { command='behave', - flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_behave', }, @@ -245,37 +246,37 @@ module.cmds = { }, { command='break', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_break', }, { command='breakadd', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_breakadd', }, { command='breakdel', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_breakdel', }, { command='breaklist', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_breaklist', }, { command='browse', - flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_wrongmodifier', }, { command='buffers', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='buflist_list', }, @@ -299,7 +300,7 @@ module.cmds = { }, { command='change', - flags=bit.bor(BANG, WHOLEFOLD, RANGE, COUNT, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(BANG, WHOLEFOLD, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_change', }, @@ -317,13 +318,13 @@ module.cmds = { }, { command='cabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='cabclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abclear', }, @@ -359,13 +360,13 @@ module.cmds = { }, { command='call', - flags=bit.bor(RANGE, NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_call', }, { command='catch', - flags=bit.bor(EXTRA, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_catch', }, @@ -407,7 +408,7 @@ module.cmds = { }, { command='cd', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_cd', }, @@ -419,7 +420,7 @@ module.cmds = { }, { command='center', - flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), + flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_align', }, @@ -469,13 +470,13 @@ module.cmds = { }, { command='chdir', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_cd', }, { command='changes', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_changes', }, @@ -487,7 +488,7 @@ module.cmds = { }, { command='checkpath', - flags=bit.bor(TRLBAR, BANG, CMDWIN), + flags=bit.bor(TRLBAR, BANG, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_checkpath', }, @@ -505,7 +506,7 @@ module.cmds = { }, { command='clist', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='qf_list', }, @@ -517,31 +518,31 @@ module.cmds = { }, { command='close', - flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_WINDOWS', func='ex_close', }, { command='clearjumps', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_clearjumps', }, { command='cmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='cmapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='cmenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -565,25 +566,25 @@ module.cmds = { }, { command='cnoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='cnoreabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='cnoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='copy', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_copymove', }, @@ -595,43 +596,43 @@ module.cmds = { }, { command='colorscheme', - flags=bit.bor(WORD1, TRLBAR, CMDWIN), + flags=bit.bor(WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_colorscheme', }, { command='command', - flags=bit.bor(EXTRA, BANG, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, BANG, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_command', }, { command='comclear', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_comclear', }, { command='compiler', - flags=bit.bor(BANG, TRLBAR, WORD1, CMDWIN), + flags=bit.bor(BANG, TRLBAR, WORD1, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_compiler', }, { command='continue', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_continue', }, { command='confirm', - flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_wrongmodifier', }, { command='const', - flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_const', }, @@ -679,19 +680,19 @@ module.cmds = { }, { command='cunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='cunabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='cunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, @@ -703,43 +704,43 @@ module.cmds = { }, { command='delete', - flags=bit.bor(RANGE, WHOLEFOLD, REGSTR, COUNT, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, REGSTR, COUNT, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_operators', }, { command='delmarks', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_delmarks', }, { command='debug', - flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_debug', }, { command='debuggreedy', - flags=bit.bor(RANGE, ZEROR, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, ZEROR, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_debuggreedy', }, { command='delcommand', - flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_delcommand', }, { command='delfunction', - flags=bit.bor(BANG, NEEDARG, WORD1, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_delfunction', }, { command='display', - flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_display', }, @@ -787,7 +788,7 @@ module.cmds = { }, { command='digraphs', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_digraphs', }, @@ -799,19 +800,19 @@ module.cmds = { }, { command='dlist', - flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN), + flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_findpat', }, { command='doautocmd', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_doautocmd', }, { command='doautoall', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_doautoall', }, @@ -823,7 +824,7 @@ module.cmds = { }, { command='dsearch', - flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN), + flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_findpat', }, @@ -841,85 +842,85 @@ module.cmds = { }, { command='earlier', - flags=bit.bor(TRLBAR, EXTRA, NOSPC, CMDWIN), + flags=bit.bor(TRLBAR, EXTRA, NOSPC, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_later', }, { command='echo', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_echo', }, { command='echoerr', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_execute', }, { command='echohl', - flags=bit.bor(EXTRA, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_echohl', }, { command='echomsg', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_execute', }, { command='echon', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_echo', }, { command='else', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_else', }, { command='elseif', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_else', }, { command='emenu', - flags=bit.bor(NEEDARG, EXTRA, TRLBAR, NOTRLCOM, RANGE, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, TRLBAR, NOTRLCOM, RANGE, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_emenu', }, { command='endif', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_endif', }, { command='endfunction', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_endfunction', }, { command='endfor', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_endwhile', }, { command='endtry', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_endtry', }, { command='endwhile', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_endwhile', }, @@ -931,7 +932,7 @@ module.cmds = { }, { command='eval', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_eval', }, @@ -943,13 +944,13 @@ module.cmds = { }, { command='execute', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_execute', }, { command='exit', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILE1, ARGOPT, DFLALL, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILE1, ARGOPT, DFLALL, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_exit', }, @@ -967,13 +968,13 @@ module.cmds = { }, { command='files', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='buflist_list', }, { command='filetype', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_filetype', }, @@ -991,13 +992,13 @@ module.cmds = { }, { command='finally', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_finally', }, { command='finish', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_finish', }, @@ -1009,13 +1010,13 @@ module.cmds = { }, { command='fold', - flags=bit.bor(RANGE, WHOLEFOLD, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_fold', }, { command='foldclose', - flags=bit.bor(RANGE, BANG, WHOLEFOLD, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, BANG, WHOLEFOLD, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_foldopen', }, @@ -1033,31 +1034,31 @@ module.cmds = { }, { command='foldopen', - flags=bit.bor(RANGE, BANG, WHOLEFOLD, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, BANG, WHOLEFOLD, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_foldopen', }, { command='for', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_while', }, { command='function', - flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_function', }, { command='global', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, EXTRA, DFLALL, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, EXTRA, DFLALL, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_global', }, { command='goto', - flags=bit.bor(RANGE, COUNT, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, COUNT, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_goto', }, @@ -1075,13 +1076,13 @@ module.cmds = { }, { command='gui', - flags=bit.bor(BANG, FILES, CMDARG, ARGOPT, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILES, CMDARG, ARGOPT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_nogui', }, { command='gvim', - flags=bit.bor(BANG, FILES, CMDARG, ARGOPT, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILES, CMDARG, ARGOPT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_nogui', }, @@ -1105,7 +1106,7 @@ module.cmds = { }, { command='helptags', - flags=bit.bor(NEEDARG, FILES, TRLBAR, CMDWIN), + flags=bit.bor(NEEDARG, FILES, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_helptags', }, @@ -1117,7 +1118,7 @@ module.cmds = { }, { command='highlight', - flags=bit.bor(BANG, EXTRA, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_highlight', }, @@ -1129,31 +1130,31 @@ module.cmds = { }, { command='history', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_history', }, { command='insert', - flags=bit.bor(BANG, RANGE, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(BANG, RANGE, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_append', }, { command='iabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='iabclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abclear', }, { command='if', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_if', }, @@ -1165,55 +1166,55 @@ module.cmds = { }, { command='ilist', - flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN), + flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_findpat', }, { command='imap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='imapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='imenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='inoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='inoreabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='inoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='intro', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_intro', }, { command='isearch', - flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN), + flags=bit.bor(BANG, RANGE, DFLALL, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_findpat', }, @@ -1225,37 +1226,37 @@ module.cmds = { }, { command='iunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='iunabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='iunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='join', - flags=bit.bor(BANG, RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(BANG, RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_join', }, { command='jumps', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_jumps', }, { command='k', - flags=bit.bor(RANGE, WORD1, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, WORD1, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_mark', }, @@ -1285,7 +1286,7 @@ module.cmds = { }, { command='list', - flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_print', }, @@ -1315,7 +1316,7 @@ module.cmds = { }, { command='language', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_language', }, @@ -1345,7 +1346,7 @@ module.cmds = { }, { command='later', - flags=bit.bor(TRLBAR, EXTRA, NOSPC, CMDWIN), + flags=bit.bor(TRLBAR, EXTRA, NOSPC, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_later', }, @@ -1375,13 +1376,13 @@ module.cmds = { }, { command='lcd', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_cd', }, { command='lchdir', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_cd', }, @@ -1405,7 +1406,7 @@ module.cmds = { }, { command='left', - flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), + flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_align', }, @@ -1417,7 +1418,7 @@ module.cmds = { }, { command='let', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_let', }, @@ -1503,19 +1504,19 @@ module.cmds = { }, { command='llist', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='qf_list', }, { command='lmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='lmapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, @@ -1527,7 +1528,7 @@ module.cmds = { }, { command='lnoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, @@ -1557,7 +1558,7 @@ module.cmds = { }, { command='loadkeymap', - flags=bit.bor(CMDWIN), + flags=bit.bor(CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_loadkeymap', }, @@ -1569,7 +1570,7 @@ module.cmds = { }, { command='lockvar', - flags=bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_lockvar', }, @@ -1611,37 +1612,37 @@ module.cmds = { }, { command='lunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='lua', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_lua', }, { command='luado', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_luado', }, { command='luafile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_luafile', }, { command='lvimgrep', - flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE), + flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE, LOCK_OK), addr_type='ADDR_OTHER', func='ex_vimgrep', }, { command='lvimgrepadd', - flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE), + flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE, LOCK_OK), addr_type='ADDR_OTHER', func='ex_vimgrep', }, @@ -1653,19 +1654,19 @@ module.cmds = { }, { command='ls', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='buflist_list', }, { command='move', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_copymove', }, { command='mark', - flags=bit.bor(RANGE, WORD1, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, WORD1, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_mark', }, @@ -1677,49 +1678,49 @@ module.cmds = { }, { command='map', - flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='mapclear', - flags=bit.bor(EXTRA, BANG, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='marks', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_marks', }, { command='match', - flags=bit.bor(RANGE, EXTRA, CMDWIN), + flags=bit.bor(RANGE, EXTRA, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_match', }, { command='menu', - flags=bit.bor(RANGE, ZEROR, BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='menutranslate', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menutranslate', }, { command='messages', - flags=bit.bor(EXTRA, TRLBAR, RANGE, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, RANGE, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_messages', }, { command='mkexrc', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mkrc', }, @@ -1737,7 +1738,7 @@ module.cmds = { }, { command='mkvimrc', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mkrc', }, @@ -1749,19 +1750,19 @@ module.cmds = { }, { command='mode', - flags=bit.bor(WORD1, TRLBAR, CMDWIN), + flags=bit.bor(WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mode', }, { command='mzscheme', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, SBOXOK), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_LINES', func='ex_script_ni', }, { command='mzfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_ni', }, @@ -1779,37 +1780,37 @@ module.cmds = { }, { command='nmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='nmapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='nmenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='nnoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='nnoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='noremap', - flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, @@ -1821,19 +1822,19 @@ module.cmds = { }, { command='nohlsearch', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_nohlsearch', }, { command='noreabbrev', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, { command='noremenu', - flags=bit.bor(RANGE, ZEROR, BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -1845,49 +1846,49 @@ module.cmds = { }, { command='normal', - flags=bit.bor(RANGE, BANG, EXTRA, NEEDARG, NOTRLCOM, CTRLV, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, BANG, EXTRA, NEEDARG, NOTRLCOM, CTRLV, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_normal', }, { command='number', - flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_print', }, { command='nunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='nunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='oldfiles', - flags=bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_oldfiles', }, { command='omap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='omapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='omenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -1899,13 +1900,13 @@ module.cmds = { }, { command='onoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='onoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -1917,37 +1918,37 @@ module.cmds = { }, { command='ounmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='ounmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='ownsyntax', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_ownsyntax', }, { command='print', - flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, SBOXOK), + flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_LINES', func='ex_print', }, { command='packadd', - flags=bit.bor(BANG, FILE1, NEEDARG, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(BANG, FILE1, NEEDARG, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_packadd', }, { command='packloadall', - flags=bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_packloadall', }, @@ -1959,19 +1960,19 @@ module.cmds = { }, { command='perl', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_perl', }, { command='perldo', - flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_perldo', }, { command='perlfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_perlfile', }, @@ -1989,9 +1990,9 @@ module.cmds = { }, { command='popup', - flags=bit.bor(NEEDARG, EXTRA, BANG, TRLBAR, NOTRLCOM, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, BANG, TRLBAR, NOTRLCOM, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', - func='ex_ni', + func='ex_popup', }, { command='ppop', @@ -2013,13 +2014,13 @@ module.cmds = { }, { command='profile', - flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_profile', }, { command='profdel', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_breakdel', }, @@ -2085,85 +2086,85 @@ module.cmds = { }, { command='put', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, REGSTR, TRLBAR, ZEROR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, REGSTR, TRLBAR, ZEROR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_put', }, { command='pwd', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_pwd', }, { command='python', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_python3', }, { command='pydo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_pydo3', }, { command='pyfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_py3file', }, { command='py3', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_python3', }, { command='py3do', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_pydo3', }, { command='python3', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_python3', }, { command='py3file', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_py3file', }, { command='pyx', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_python3', }, { command='pyxdo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_pydo3', }, { command='pythonx', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_python3', }, { command='pyxfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_py3file', }, { command='quit', - flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_WINDOWS', func='ex_quit', }, @@ -2175,13 +2176,13 @@ module.cmds = { }, { command='qall', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_quit_all', }, { command='read', - flags=bit.bor(BANG, RANGE, WHOLEFOLD, FILE1, ARGOPT, TRLBAR, ZEROR, CMDWIN, MODIFY), + flags=bit.bor(BANG, RANGE, WHOLEFOLD, FILE1, ARGOPT, TRLBAR, ZEROR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_read', }, @@ -2193,55 +2194,55 @@ module.cmds = { }, { command='redo', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_redo', }, { command='redir', - flags=bit.bor(BANG, FILES, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILES, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_redir', }, { command='redraw', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_redraw', }, { command='redrawstatus', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_redrawstatus', }, { command='redrawtabline', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_redrawtabline', }, { command='registers', - flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_display', }, { command='resize', - flags=bit.bor(RANGE, TRLBAR, WORD1, CMDWIN), + flags=bit.bor(RANGE, TRLBAR, WORD1, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_resize', }, { command='retab', - flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, DFLALL, BANG, WORD1, CMDWIN, MODIFY), + flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, DFLALL, BANG, WORD1, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_retab', }, { command='return', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_return', }, @@ -2253,7 +2254,7 @@ module.cmds = { }, { command='right', - flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), + flags=bit.bor(TRLBAR, RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_align', }, @@ -2265,13 +2266,13 @@ module.cmds = { }, { command='rshada', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_shada', }, { command='runtime', - flags=bit.bor(BANG, NEEDARG, FILES, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(BANG, NEEDARG, FILES, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_runtime', }, @@ -2283,31 +2284,31 @@ module.cmds = { }, { command='ruby', - flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_ruby', }, { command='rubydo', - flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_rubydo', }, { command='rubyfile', - flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_rubyfile', }, { command='rviminfo', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_shada', }, { command='substitute', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, PREVIEW), addr_type='ADDR_LINES', func='ex_substitute', preview_func='ex_substitute_preview', @@ -2338,7 +2339,7 @@ module.cmds = { }, { command='saveas', - flags=bit.bor(BANG, FILE1, ARGOPT, CMDWIN, TRLBAR), + flags=bit.bor(BANG, FILE1, ARGOPT, CMDWIN, LOCK_OK, TRLBAR), addr_type='ADDR_NONE', func='ex_write', }, @@ -2398,13 +2399,13 @@ module.cmds = { }, { command='scriptnames', - flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_scriptnames', }, { command='scriptencoding', - flags=bit.bor(WORD1, TRLBAR, CMDWIN), + flags=bit.bor(WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_scriptencoding', }, @@ -2416,25 +2417,25 @@ module.cmds = { }, { command='set', - flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, { command='setfiletype', - flags=bit.bor(TRLBAR, EXTRA, NEEDARG, CMDWIN), + flags=bit.bor(TRLBAR, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_setfiletype', }, { command='setglobal', - flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, { command='setlocal', - flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, @@ -2452,25 +2453,25 @@ module.cmds = { }, { command='simalt', - flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_ni', }, { command='sign', - flags=bit.bor(NEEDARG, RANGE, EXTRA, CMDWIN), + flags=bit.bor(NEEDARG, RANGE, EXTRA, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_sign', }, { command='silent', - flags=bit.bor(NEEDARG, EXTRA, BANG, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, BANG, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_wrongmodifier', }, { command='sleep', - flags=bit.bor(BANG, RANGE, COUNT, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, COUNT, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_sleep', }, @@ -2482,26 +2483,26 @@ module.cmds = { }, { command='smagic', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, PREVIEW), addr_type='ADDR_LINES', func='ex_submagic', preview_func='ex_submagic_preview', }, { command='smap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='smapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='smenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -2513,26 +2514,26 @@ module.cmds = { }, { command='snomagic', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, PREVIEW), addr_type='ADDR_LINES', func='ex_submagic', preview_func='ex_submagic_preview', }, { command='snoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='snoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='source', - flags=bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(RANGE, DFLALL, WHOLEFOLD, BANG, FILE1, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_source', }, @@ -2604,7 +2605,7 @@ module.cmds = { }, { command='stop', - flags=bit.bor(TRLBAR, BANG, CMDWIN), + flags=bit.bor(TRLBAR, BANG, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_stop', }, @@ -2616,25 +2617,25 @@ module.cmds = { }, { command='startinsert', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_startinsert', }, { command='startgreplace', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_startinsert', }, { command='startreplace', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_startinsert', }, { command='stopinsert', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_stopinsert', }, @@ -2658,19 +2659,19 @@ module.cmds = { }, { command='sunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='sunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='suspend', - flags=bit.bor(TRLBAR, BANG, CMDWIN), + flags=bit.bor(TRLBAR, BANG, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_stop', }, @@ -2682,19 +2683,19 @@ module.cmds = { }, { command='swapname', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_swapname', }, { command='syntax', - flags=bit.bor(EXTRA, NOTRLCOM, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_syntax', }, { command='syntime', - flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_syntime', }, @@ -2706,19 +2707,19 @@ module.cmds = { }, { command='t', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_copymove', }, { command='tcd', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_cd', }, { command='tchdir', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_cd', }, @@ -2736,7 +2737,7 @@ module.cmds = { }, { command='tags', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='do_tags', }, @@ -2748,7 +2749,7 @@ module.cmds = { }, { command='tabclose', - flags=bit.bor(BANG, RANGE, ZEROR, EXTRA, NOSPC, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, ZEROR, EXTRA, NOSPC, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_TABS', func='ex_tabclose', }, @@ -2802,7 +2803,7 @@ module.cmds = { }, { command='tabonly', - flags=bit.bor(BANG, RANGE, ZEROR, EXTRA, NOSPC, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, ZEROR, EXTRA, NOSPC, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_TABS', func='ex_tabonly', }, @@ -2826,31 +2827,31 @@ module.cmds = { }, { command='tabs', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_tabs', }, { command='tcl', - flags=bit.bor(RANGE,EXTRA,NEEDARG,CMDWIN), + flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_script_ni', }, { command='tcldo', - flags=bit.bor(RANGE,DFLALL,EXTRA,NEEDARG,CMDWIN), + flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_ni', }, { command='tclfile', - flags=bit.bor(RANGE,FILE1,NEEDARG,CMDWIN), + flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_ni', }, { command='terminal', - flags=bit.bor(BANG, FILES, CMDWIN), + flags=bit.bor(BANG, FILES, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_terminal', }, @@ -2862,7 +2863,7 @@ module.cmds = { }, { command='throw', - flags=bit.bor(EXTRA, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NEEDARG, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_throw', }, @@ -2879,20 +2880,38 @@ module.cmds = { func='ex_tag', }, { + command='tlmenu', + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), + addr_type='ADDR_OTHER', + func='ex_menu', + }, + { + command='tlnoremenu', + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), + addr_type='ADDR_OTHER', + func='ex_menu', + }, + { + command='tlunmenu', + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), + addr_type='ADDR_OTHER', + func='ex_menu', + }, + { command='tmenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='tmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='tmapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, @@ -2904,7 +2923,7 @@ module.cmds = { }, { command='tnoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, @@ -2928,7 +2947,7 @@ module.cmds = { }, { command='try', - flags=bit.bor(TRLBAR, SBOXOK, CMDWIN), + flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_try', }, @@ -2940,37 +2959,37 @@ module.cmds = { }, { command='tunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='tunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='undo', - flags=bit.bor(BANG, RANGE, COUNT, ZEROR, TRLBAR, CMDWIN), + flags=bit.bor(BANG, RANGE, COUNT, ZEROR, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_undo', }, { command='undojoin', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_undojoin', }, { command='undolist', - flags=bit.bor(TRLBAR, CMDWIN), + flags=bit.bor(TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_undolist', }, { command='unabbreviate', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_abbreviate', }, @@ -2982,31 +3001,31 @@ module.cmds = { }, { command='unlet', - flags=bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unlet', }, { command='unlockvar', - flags=bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN), + flags=bit.bor(BANG, EXTRA, NEEDARG, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_lockvar', }, { command='unmap', - flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='unmenu', - flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(BANG, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='unsilent', - flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_wrongmodifier', }, @@ -3018,19 +3037,19 @@ module.cmds = { }, { command='vglobal', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, DFLALL, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, DFLALL, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_global', }, { command='version', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_version', }, { command='verbose', - flags=bit.bor(NEEDARG, RANGE, EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(NEEDARG, RANGE, EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_wrongmodifier', }, @@ -3054,13 +3073,13 @@ module.cmds = { }, { command='vimgrep', - flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE), + flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE, LOCK_OK), addr_type='ADDR_OTHER', func='ex_vimgrep', }, { command='vimgrepadd', - flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE), + flags=bit.bor(RANGE, BANG, NEEDARG, EXTRA, NOTRLCOM, TRLBAR, XFILE, LOCK_OK), addr_type='ADDR_OTHER', func='ex_vimgrep', }, @@ -3072,25 +3091,25 @@ module.cmds = { }, { command='vmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='vmapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='vmenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='vnoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, @@ -3102,7 +3121,7 @@ module.cmds = { }, { command='vnoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, @@ -3114,19 +3133,19 @@ module.cmds = { }, { command='vunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='vunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='write', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILE1, ARGOPT, DFLALL, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILE1, ARGOPT, DFLALL, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_write', }, @@ -3138,13 +3157,13 @@ module.cmds = { }, { command='wall', - flags=bit.bor(BANG, TRLBAR, CMDWIN), + flags=bit.bor(BANG, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='do_wqall', }, { command='while', - flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN), + flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_while', }, @@ -3156,7 +3175,7 @@ module.cmds = { }, { command='wincmd', - flags=bit.bor(NEEDARG, WORD1, RANGE, CMDWIN), + flags=bit.bor(NEEDARG, WORD1, RANGE, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_wincmd', }, @@ -3168,7 +3187,7 @@ module.cmds = { }, { command='winpos', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_ni', }, @@ -3198,7 +3217,7 @@ module.cmds = { }, { command='wshada', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_shada', }, @@ -3210,13 +3229,13 @@ module.cmds = { }, { command='wviminfo', - flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, FILE1, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_shada', }, { command='xit', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILE1, ARGOPT, DFLALL, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILE1, ARGOPT, DFLALL, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_exit', }, @@ -3228,120 +3247,122 @@ module.cmds = { }, { command='xmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='xmapclear', - flags=bit.bor(EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_mapclear', }, { command='xmenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='xnoremap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_map', }, { command='xnoremenu', - flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_menu', }, { command='xunmap', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_unmap', }, { command='xunmenu', - flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', func='ex_menu', }, { command='yank', - flags=bit.bor(RANGE, WHOLEFOLD, REGSTR, COUNT, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, REGSTR, COUNT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_operators', }, { command='z', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, EXTRA, FLAGS, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, EXTRA, FLAGS, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_z', }, + -- commands that don't start with a letter { command='!', enum='CMD_bang', - flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILES, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, BANG, FILES, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_bang', }, { command='#', enum='CMD_pound', - flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_print', }, { command='&', enum='CMD_and', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_substitute', }, { command='<', enum='CMD_lshift', - flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_operators', }, { command='=', enum='CMD_equal', - flags=bit.bor(RANGE, TRLBAR, DFLALL, FLAGS, CMDWIN), + flags=bit.bor(RANGE, TRLBAR, DFLALL, FLAGS, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_equal', }, { command='>', enum='CMD_rshift', - flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, COUNT, FLAGS, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_operators', }, { command='@', enum='CMD_at', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_LINES', func='ex_at', }, { - command='Next', - flags=bit.bor(EXTRA, RANGE, COUNT, BANG, CMDARG, ARGOPT, TRLBAR), - addr_type='ADDR_OTHER', - func='ex_previous', - }, - { command='~', enum='CMD_tilde', - flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, MODIFY), + flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', func='ex_substitute', }, + -- commands that start with an uppercase letter + { + command='Next', + flags=bit.bor(EXTRA, RANGE, COUNT, BANG, CMDARG, ARGOPT, TRLBAR), + addr_type='ADDR_OTHER', + func='ex_previous', + }, } return module diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 621004103e..e57dc5d13f 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -562,7 +562,7 @@ void dialog_changed(buf_T *buf, bool checkall) if (ret == VIM_YES) { if (buf->b_fname != NULL - && check_overwrite(&ea, buf, buf->b_fname, (char *)buf->b_ffname, false) == OK) { + && check_overwrite(&ea, buf, buf->b_fname, buf->b_ffname, false) == OK) { // didn't hit Cancel (void)buf_write_all(buf, false); } @@ -578,8 +578,7 @@ void dialog_changed(buf_T *buf, bool checkall) set_bufref(&bufref, buf2); if (buf2->b_fname != NULL - && check_overwrite(&ea, buf2, buf2->b_fname, - (char *)buf2->b_ffname, false) == OK) { + && check_overwrite(&ea, buf2, buf2->b_fname, buf2->b_ffname, false) == OK) { // didn't hit Cancel (void)buf_write_all(buf2, false); } @@ -786,7 +785,7 @@ int buf_write_all(buf_T *buf, int forceit) int retval; buf_T *old_curbuf = curbuf; - retval = (buf_write(buf, (char *)buf->b_ffname, buf->b_fname, + retval = (buf_write(buf, buf->b_ffname, buf->b_fname, (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, forceit, true, false)); if (curbuf != old_curbuf) { @@ -929,7 +928,7 @@ static int do_arglist(char *str, int what, int after, bool will_edit) didone = false; for (match = 0; match < ARGCOUNT; match++) { - if (vim_regexec(®match, (char_u *)alist_name(&ARGLIST[match]), (colnr_T)0)) { + if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { didone = true; xfree(ARGLIST[match].ae_fname); memmove(ARGLIST + match, ARGLIST + match + 1, @@ -992,7 +991,7 @@ static bool editing_arg_idx(win_T *win) != WARGLIST(win)[win->w_arg_idx].ae_fnum && (win->w_buffer->b_ffname == NULL || !(path_full_compare(alist_name(&WARGLIST(win)[win->w_arg_idx]), - (char *)win->w_buffer->b_ffname, true, + win->w_buffer->b_ffname, true, true) & kEqualFiles)))); } @@ -1011,7 +1010,7 @@ void check_arg_idx(win_T *win) && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum || (win->w_buffer->b_ffname != NULL && (path_full_compare(alist_name(&GARGLIST[GARGCOUNT - 1]), - (char *)win->w_buffer->b_ffname, true, true) + win->w_buffer->b_ffname, true, true) & kEqualFiles)))) { arg_had_last = true; } @@ -1019,8 +1018,7 @@ void check_arg_idx(win_T *win) // We are editing the current entry in the argument list. // Set "arg_had_last" if it's also the last one win->w_arg_idx_invalid = false; - if (win->w_arg_idx == WARGCOUNT(win) - 1 - && win->w_alist == &global_alist) { + if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { arg_had_last = true; } } @@ -1138,7 +1136,7 @@ void do_argfile(exarg_T *eap, int argn) other = true; if (buf_hide(curbuf)) { p = fix_fname(alist_name(&ARGLIST[argn])); - other = otherfile((char_u *)p); + other = otherfile(p); xfree(p); } if ((!buf_hide(curbuf) || !other) @@ -1151,8 +1149,7 @@ void do_argfile(exarg_T *eap, int argn) } curwin->w_arg_idx = argn; - if (argn == ARGCOUNT - 1 - && curwin->w_alist == &global_alist) { + if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { arg_had_last = true; } @@ -1551,7 +1548,7 @@ static void alist_add_list(int count, char **files, int after, bool will_edit) for (int i = 0; i < count; i++) { const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); ARGLIST[after + i].ae_fname = (char_u *)files[i]; - ARGLIST[after + i].ae_fnum = buflist_add((char_u *)files[i], flags); + ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); } ALIST(curwin)->al_ga.ga_len += count; if (old_argcount > 0 && curwin->w_arg_idx >= after) { @@ -1641,7 +1638,7 @@ void ex_options(exarg_T *eap) bool multi_mods = 0; buf[0] = NUL; - (void)add_win_cmd_modifers(buf, &multi_mods); + (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); os_setenv("OPTWIN_CMD", buf, 1); cmd_source(SYS_OPTWIN_FILE, NULL); @@ -2157,31 +2154,23 @@ scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx) sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, .sc_lnum = 0, .sc_sid = 0 }; - FileID file_id; scriptitem_T *si = NULL; - bool file_id_ok = os_fileid((char *)fname, &file_id); assert(script_items.ga_len >= 0); - for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; - script_sctx.sc_sid--) { + for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) { + // We used to check inode here, but that doesn't work: + // - If a script is edited and written, it may get a different + // inode number, even though to the user it is the same script. + // - If a script is deleted and another script is written, with a + // different name, the inode may be re-used. si = &SCRIPT_ITEM(script_sctx.sc_sid); - // Compare dev/ino when possible, it catches symbolic links. - // Also compare file names, the inode may change when the file was edited. - bool file_id_equal = file_id_ok && si->file_id_valid - && os_fileid_equal(&(si->file_id), &file_id); - if (si->sn_name != NULL - && (file_id_equal || FNAMECMP(si->sn_name, fname) == 0)) { + if (si->sn_name != NULL && FNAMECMP(si->sn_name, fname) == 0) { + // Found it! break; } } if (script_sctx.sc_sid == 0) { si = new_script_item((char *)vim_strsave(fname), &script_sctx.sc_sid); - if (file_id_ok) { - si->file_id_valid = true; - si->file_id = file_id; - } else { - si->file_id_valid = false; - } } if (ret_sctx != NULL) { *ret_sctx = script_sctx; @@ -2265,7 +2254,7 @@ char_u *get_scriptname(LastSet last_set, bool *should_free) } *should_free = true; - return home_replace_save(NULL, (char_u *)sname); + return (char_u *)home_replace_save(NULL, sname); } } } @@ -2494,8 +2483,7 @@ void script_line_start(void) if (si->sn_prof_on && sourcing_lnum >= 1) { // Grow the array before starting the timer, so that the time spent // here isn't counted. - (void)ga_grow(&si->sn_prl_ga, - (int)(sourcing_lnum - si->sn_prl_ga.ga_len)); + (void)ga_grow(&si->sn_prl_ga, sourcing_lnum - si->sn_prl_ga.ga_len); si->sn_prl_idx = sourcing_lnum - 1; while (si->sn_prl_ga.ga_len <= si->sn_prl_idx && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index 74e52dfb4b..c463bfa5ab 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -15,12 +15,8 @@ #define CCGD_ALLBUF 8 // may write all buffers #define CCGD_EXCMD 16 // may suggest using ! -/// Also store the dev/ino, so that we don't have to stat() each -/// script when going through the list. typedef struct scriptitem_S { char_u *sn_name; - bool file_id_valid; - FileID file_id; bool sn_prof_on; ///< true when script is/was profiled bool sn_pr_force; ///< forceit: profile functions in this script proftime_T sn_pr_child; ///< time set when going into first child diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 6798f7693b..052926fa1f 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -57,11 +57,12 @@ #define EX_BUFUNL 0x10000 // accepts unlisted buffer too #define EX_ARGOPT 0x20000 // allow "++opt=val" argument #define EX_SBOXOK 0x40000 // allowed in the sandbox -#define EX_CMDWIN 0x80000 // allowed in cmdline window; when missing - // disallows editing another buffer when - // current buffer is locked +#define EX_CMDWIN 0x80000 // allowed in cmdline window #define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer #define EX_FLAGS 0x200000 // allow flags after count in argument +#define EX_LOCK_OK 0x1000000 // command can be executed when textlock is + // set; when missing disallows editing another + // buffer when current buffer is locked #define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked #define EX_PREVIEW 0x8000000 // allow incremental command preview #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index fee98a18dc..c4d2821e79 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -62,6 +62,7 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/popupmnu.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" @@ -226,6 +227,8 @@ void do_exmode(void) emsg(_(e_emptybuf)); } else { if (ex_pressedreturn) { + // Make sure the message overwrites the right line and isn't throttled. + msg_scroll_flush(); // go up one line, to overwrite the ":<CR>" line, so the // output doesn't contain empty lines. msg_row = prev_msg_row; @@ -907,6 +910,15 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) msg_list = saved_msg_list; + // Cleanup if "cs_emsg_silent_list" remains. + if (cstack.cs_emsg_silent_list != NULL) { + eslist_T *elem, *temp; + for (elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) { + temp = elem->next; + xfree(elem); + } + } + /* * If there was too much output to fit on the command line, ask the user to * hit return before redrawing the screen. With the ":global" command we do @@ -1432,10 +1444,13 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er eap->getline = NULL; eap->cookie = NULL; + const bool save_ex_pressedreturn = ex_pressedreturn; // Parse command modifiers if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) { + ex_pressedreturn = save_ex_pressedreturn; goto err; } + ex_pressedreturn = save_ex_pressedreturn; after_modifier = eap->cmd; // Save location after command modifiers @@ -1575,9 +1590,15 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) && !(curbuf->terminal && eap->cmdidx == CMD_put)) { ERROR(_(e_modifiable)); } - if (text_locked() && !(eap->argt & EX_CMDWIN) - && !IS_USER_CMDIDX(eap->cmdidx)) { - ERROR(_(get_text_locked_msg())); + if (!IS_USER_CMDIDX(eap->cmdidx)) { + if (cmdwin_type != 0 && !(eap->argt & EX_CMDWIN)) { + // Command not allowed in the command line window + ERROR(_(e_cmdwin)); + } + if (text_locked() && !(eap->argt & EX_LOCK_OK)) { + // Command not allowed when text is locked + ERROR(_(get_text_locked_msg())); + } } // Disallow editing another buffer when "curbuf->b_ro_locked" is set. // Do allow ":checktime" (it is postponed). @@ -1620,21 +1641,21 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) if (eap->cmdidx == CMD_bdelete || eap->cmdidx == CMD_bwipeout || eap->cmdidx == CMD_bunload) { - p = (char *)skiptowhite_esc((char_u *)eap->arg); + p = skiptowhite_esc(eap->arg); } else { p = eap->arg + STRLEN(eap->arg); while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } } - eap->line2 = buflist_findpat((char_u *)eap->arg, (char_u *)p, (eap->argt & EX_BUFUNL) != 0, + eap->line2 = buflist_findpat(eap->arg, p, (eap->argt & EX_BUFUNL) != 0, false, false); eap->addr_count = 1; eap->arg = skipwhite(p); } else { // If argument positions are specified, just use the first argument - eap->line2 = buflist_findpat((char_u *)eap->args[0], - (char_u *)(eap->args[0] + eap->arglens[0]), + eap->line2 = buflist_findpat(eap->args[0], + eap->args[0] + eap->arglens[0], (eap->argt & EX_BUFUNL) != 0, false, false); eap->addr_count = 1; // Shift each argument by 1 @@ -1952,11 +1973,17 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter goto doend; } - if (text_locked() && !(ea.argt & EX_CMDWIN) - && !IS_USER_CMDIDX(ea.cmdidx)) { - // Command not allowed when editing the command line. - errormsg = _(get_text_locked_msg()); - goto doend; + if (!IS_USER_CMDIDX(ea.cmdidx)) { + if (cmdwin_type != 0 && !(ea.argt & EX_CMDWIN)) { + // Command not allowed in the command line window + errormsg = _(e_cmdwin); + goto doend; + } + if (text_locked() && !(ea.argt & EX_LOCK_OK)) { + // Command not allowed when text is locked + errormsg = _(get_text_locked_msg()); + goto doend; + } } // Disallow editing another buffer when "curbuf->b_ro_locked" is set. @@ -2280,14 +2307,14 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter */ if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bwipeout || ea.cmdidx == CMD_bunload) { - p = (char *)skiptowhite_esc((char_u *)ea.arg); + p = skiptowhite_esc(ea.arg); } else { p = ea.arg + STRLEN(ea.arg); while (p > ea.arg && ascii_iswhite(p[-1])) { p--; } } - ea.line2 = buflist_findpat((char_u *)ea.arg, (char_u *)p, (ea.argt & EX_BUFUNL) != 0, + ea.line2 = buflist_findpat(ea.arg, p, (ea.argt & EX_BUFUNL) != 0, false, false); if (ea.line2 < 0) { // failed goto doend; @@ -2340,7 +2367,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter do_return(&ea, TRUE, FALSE, NULL); } } - need_rethrow = check_cstack = FALSE; + need_rethrow = check_cstack = false; doend: // can happen with zero line number @@ -2395,7 +2422,8 @@ char *ex_errmsg(const char *const msg, const char *const arg) /// - Set ex_pressedreturn for an empty command line. /// /// @param skip_only if false, undo_cmdmod() must be called later to free -/// any cmod_filter_pat and cmod_filter_regmatch.regprog. +/// any cmod_filter_pat and cmod_filter_regmatch.regprog, +/// and ex_pressedreturn may be set. /// @param[out] errormsg potential error message. /// /// Call apply_cmdmod() to get the side effects of the modifiers: @@ -2409,7 +2437,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool { char *p; - memset(cmod, 0, sizeof(*cmod)); + CLEAR_POINTER(cmod); // Repeat until no more command modifiers are found. for (;;) { @@ -2622,7 +2650,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool } if (ascii_isdigit(*eap->cmd)) { // zero means not set, one is verbose == 0, etc. - cmod->cmod_verbose = atoi((char *)eap->cmd) + 1; + cmod->cmod_verbose = atoi(eap->cmd) + 1; } else { cmod->cmod_verbose = 2; // default: verbose == 1 } @@ -2820,16 +2848,16 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent) eap->cmd++; if (!eap->skip) { - pos_T *fp = getmark('<', false); - if (check_mark(fp) == FAIL) { + fmark_T *fm = mark_get_visual(curbuf, '<'); + if (!mark_check(fm)) { goto theend; } - eap->line1 = fp->lnum; - fp = getmark('>', false); - if (check_mark(fp) == FAIL) { + eap->line1 = fm->mark.lnum; + fm = mark_get_visual(curbuf, '>'); + if (!mark_check(fm)) { goto theend; } - eap->line2 = fp->lnum; + eap->line2 = fm->mark.lnum; eap->addr_count++; } } @@ -2903,9 +2931,16 @@ int checkforcmd(char **pp, char *cmd, int len) /// invisible otherwise. static void append_command(char *cmd) { + size_t len = STRLEN(IObuff); char *s = cmd; char *d; + if (len > IOSIZE - 100) { + // Not enough space, truncate and put in "...". + d = (char *)IObuff + IOSIZE - 100; + d -= utf_head_off(IObuff, (const char_u *)d); + STRCPY(d, "..."); + } STRCAT(IObuff, ": "); d = (char *)IObuff + STRLEN(IObuff); while (*s != NUL && (char_u *)d - IObuff + 5 < IOSIZE) { @@ -3009,6 +3044,8 @@ char *find_ex_command(exarg_T *eap, int *full) if (ASCII_ISLOWER(c2)) { eap->cmdidx += cmdidxs2[CHAR_ORD_LOW(c1)][CHAR_ORD_LOW(c2)]; } + } else if (ASCII_ISUPPER(eap->cmd[0])) { + eap->cmdidx = CMD_Next; } else { eap->cmdidx = CMD_bang; } @@ -4059,6 +4096,9 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: + case CMD_tlmenu: + case CMD_tlnoremenu: + case CMD_tlunmenu: case CMD_tmenu: case CMD_tunmenu: case CMD_popup: @@ -4230,7 +4270,6 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int linenr_T n; char *cmd; pos_T pos; - pos_T *fp; linenr_T lnum; buf_T *buf; @@ -4340,17 +4379,18 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int } else { // Only accept a mark in another file when it is // used by itself: ":'M". - fp = getmark(*cmd, to_other_file && cmd[1] == NUL); - ++cmd; - if (fp == (pos_T *)-1) { + MarkGet flag = to_other_file && cmd[1] == NUL ? kMarkAll : kMarkBufLocal; + fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd); + cmd++; + if (fm != NULL && fm->fnum != curbuf->handle) { // Jumped to another file. lnum = curwin->w_cursor.lnum; } else { - if (check_mark(fp) == FAIL) { + if (!mark_check(fm)) { cmd = NULL; goto error; } - lnum = fp->lnum; + lnum = fm->mark.lnum; } } break; @@ -4594,8 +4634,9 @@ char *invalid_range(exarg_T *eap) } break; case ADDR_BUFFERS: - if (eap->line1 < firstbuf->b_fnum - || eap->line2 > lastbuf->b_fnum) { + // Only a boundary check, not whether the buffers actually + // exist. + if (eap->line1 < 1 || eap->line2 > get_highest_fnum()) { return _(e_invrange); } break; @@ -5313,7 +5354,7 @@ static void ex_bunload(exarg_T *eap) : eap->cmdidx == CMD_bwipeout ? DOBUF_WIPE : DOBUF_UNLOAD, - (char_u *)eap->arg, eap->addr_count, (int)eap->line1, (int)eap->line2, + eap->arg, eap->addr_count, (int)eap->line1, (int)eap->line2, eap->forceit); } @@ -6450,20 +6491,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar } case ct_MODS: - result = quote ? 2 : 0; - if (buf != NULL) { - if (quote) { - *buf++ = '"'; - } - *buf = '\0'; - } - - result += uc_mods(buf); - - if (quote && buf != NULL) { - buf += result - 2; - *buf = '"'; - } + result = uc_mods(buf, &cmdmod, quote); break; case ct_REGISTER: @@ -6503,43 +6531,45 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar return result; } -/// Add modifiers from "cmdmod.cmod_split" to "buf". Set "multi_mods" when one +/// Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one /// was added. /// /// @return the number of bytes added -size_t add_win_cmd_modifers(char *buf, bool *multi_mods) +size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) { size_t result = 0; // :aboveleft and :leftabove - if (cmdmod.cmod_split & WSP_ABOVE) { + if (cmod->cmod_split & WSP_ABOVE) { result += add_cmd_modifier(buf, "aboveleft", multi_mods); } // :belowright and :rightbelow - if (cmdmod.cmod_split & WSP_BELOW) { + if (cmod->cmod_split & WSP_BELOW) { result += add_cmd_modifier(buf, "belowright", multi_mods); } // :botright - if (cmdmod.cmod_split & WSP_BOT) { + if (cmod->cmod_split & WSP_BOT) { result += add_cmd_modifier(buf, "botright", multi_mods); } // :tab - if (cmdmod.cmod_tab > 0) { + if (cmod->cmod_tab > 0) { result += add_cmd_modifier(buf, "tab", multi_mods); } // :topleft - if (cmdmod.cmod_split & WSP_TOP) { + if (cmod->cmod_split & WSP_TOP) { result += add_cmd_modifier(buf, "topleft", multi_mods); } // :vertical - if (cmdmod.cmod_split & WSP_VERT) { + if (cmod->cmod_split & WSP_VERT) { result += add_cmd_modifier(buf, "vertical", multi_mods); } return result; } -size_t uc_mods(char *buf) +/// Generate text for the "cmod" command modifiers. +/// If "buf" is NULL just return the length. +size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote) { size_t result = 0; bool multi_mods = false; @@ -6557,32 +6587,51 @@ size_t uc_mods(char *buf) { CMOD_KEEPMARKS, "keepmarks" }, { CMOD_KEEPPATTERNS, "keeppatterns" }, { CMOD_LOCKMARKS, "lockmarks" }, - { CMOD_NOSWAPFILE, "noswapfile" } + { CMOD_NOSWAPFILE, "noswapfile" }, + { CMOD_UNSILENT, "unsilent" }, + { CMOD_NOAUTOCMD, "noautocmd" }, + { CMOD_SANDBOX, "sandbox" }, }; + + result = quote ? 2 : 0; + if (buf != NULL) { + if (quote) { + *buf++ = '"'; + } + *buf = '\0'; + } + // the modifiers that are simple flags for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { - if (cmdmod.cmod_flags & mod_entries[i].flag) { + if (cmod->cmod_flags & mod_entries[i].flag) { result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); } } - // TODO(vim): How to support :noautocmd? - // TODO(vim): How to support :sandbox? - // :silent - if (msg_silent > 0) { - result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", &multi_mods); + if (cmod->cmod_flags & CMOD_SILENT) { + result += add_cmd_modifier(buf, + (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!" : "silent", + &multi_mods); } - - // TODO(vim): How to support :unsilent? - // :verbose - if (p_verbose > 0) { - result += add_cmd_modifier(buf, "verbose", &multi_mods); + if (cmod->cmod_verbose > 0) { + int verbose_value = cmod->cmod_verbose - 1; + if (verbose_value == 1) { + result += add_cmd_modifier(buf, "verbose", &multi_mods); + } else { + char verbose_buf[NUMBUFLEN]; + snprintf(verbose_buf, NUMBUFLEN, "%dverbose", verbose_value); + result += add_cmd_modifier(buf, verbose_buf, &multi_mods); + } } - // flags from cmdmod.cmod_split - result += add_win_cmd_modifers(buf, &multi_mods); + // flags from cmod->cmod_split + result += add_win_cmd_modifers(buf, cmod, &multi_mods); + if (quote && buf != NULL) { + buf += result - 2; + *buf = '"'; + } return result; } @@ -7502,7 +7551,7 @@ void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_l /* May set buffer name of a buffer previously used for the * argument list, so that it's re-used by alist_add. */ if (fnum_list != NULL && i < fnum_len) { - buf_set_name(fnum_list[i], (char_u *)files[i]); + buf_set_name(fnum_list[i], files[i]); } alist_add(al, files[i], use_curbuf ? 2 : 1); @@ -7532,7 +7581,7 @@ void alist_add(alist_T *al, char *fname, int set_fnum) AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname; if (set_fnum > 0) { AARGLIST(al)[al->al_ga.ga_len].ae_fnum = - buflist_add((char_u *)fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); + buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); } ++al->al_ga.ga_len; } @@ -7622,7 +7671,7 @@ void ex_splitview(exarg_T *eap) if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), - FNAME_MESS, true, curbuf->b_ffname); + FNAME_MESS, true, (char_u *)curbuf->b_ffname); if (fname == NULL) { goto theend; } @@ -7824,14 +7873,14 @@ static void ex_find(exarg_T *eap) linenr_T count; fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), - FNAME_MESS, true, curbuf->b_ffname); + FNAME_MESS, true, (char_u *)curbuf->b_ffname); if (eap->addr_count > 0) { // Repeat finding the file "count" times. This matters when it // appears several times in the path. count = eap->line2; while (fname != NULL && --count > 0) { xfree(fname); - fname = (char *)find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname); + fname = (char *)find_file_in_path(NULL, 0, FNAME_MESS, false, (char_u *)curbuf->b_ffname); } } @@ -7880,9 +7929,11 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) need_wait_return = false; msg_scroll = 0; redraw_all_later(NOT_VALID); + pending_exmode_active = true; normal_enter(false, true); + pending_exmode_active = false; RedrawingDisabled = rd; no_wait_return = nwr; msg_scroll = ms; @@ -7984,6 +8035,11 @@ static void ex_nogui(exarg_T *eap) eap->errmsg = N_("E25: Nvim does not have a built-in GUI"); } +static void ex_popup(exarg_T *eap) +{ + pum_make_popup(eap->arg, eap->forceit); +} + static void ex_swapname(exarg_T *eap) { if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) { @@ -8077,11 +8133,11 @@ static void ex_read(exarg_T *eap) if (check_fname() == FAIL) { // check for no file name return; } - i = readfile((char *)curbuf->b_ffname, curbuf->b_fname, + i = readfile(curbuf->b_ffname, curbuf->b_fname, eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); } else { if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) { - (void)setaltfname((char_u *)eap->arg, (char_u *)eap->arg, (linenr_T)1); + (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); } i = readfile(eap->arg, NULL, eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); @@ -8129,10 +8185,10 @@ static char *get_prevdir(CdScope scope) { switch (scope) { case kCdScopeTabpage: - return (char *)curtab->tp_prevdir; + return curtab->tp_prevdir; break; case kCdScopeWindow: - return (char *)curwin->w_prevdir; + return curwin->w_prevdir; break; default: return prev_dir; @@ -8170,10 +8226,10 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged) XFREE_CLEAR(globaldir); break; case kCdScopeTabpage: - curtab->tp_localdir = (char_u *)xstrdup(cwd); + curtab->tp_localdir = xstrdup(cwd); break; case kCdScopeWindow: - curwin->w_localdir = (char_u *)xstrdup(cwd); + curwin->w_localdir = xstrdup(cwd); break; case kCdScopeInvalid: abort(); @@ -8239,10 +8295,10 @@ bool changedir_func(char *new_dir, CdScope scope) char **pp; switch (scope) { case kCdScopeTabpage: - pp = (char **)&curtab->tp_prevdir; + pp = &curtab->tp_prevdir; break; case kCdScopeWindow: - pp = (char **)&curwin->w_prevdir; + pp = &curwin->w_prevdir; break; default: pp = &prev_dir; @@ -8370,10 +8426,10 @@ static void ex_winsize(exarg_T *eap) semsg(_(e_invarg2), arg); return; } - int w = getdigits_int((char_u **)&arg, false, 10); + int w = getdigits_int(&arg, false, 10); arg = skipwhite(arg); char *p = arg; - int h = getdigits_int((char_u **)&arg, false, 10); + int h = getdigits_int(&arg, false, 10); if (*p != NUL && *arg == NUL) { screen_resize(w, h); } else { @@ -8549,7 +8605,7 @@ static void ex_join(exarg_T *eap) } ++eap->line2; } - do_join((size_t)(eap->line2 - eap->line1 + 1), !eap->forceit, true, true, true); + do_join((size_t)((ssize_t)eap->line2 - eap->line1 + 1), !eap->forceit, true, true, true); beginline(BL_WHITE | BL_FIX); ex_may_print(eap); } @@ -9468,7 +9524,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum if (*s == '<') { // "#<99" uses v:oldfiles. s++; } - i = getdigits_int((char_u **)&s, false, 0); + i = getdigits_int(&s, false, 0); if ((char_u *)s == src + 2 && src[1] == '-') { // just a minus sign, don't skip over it s--; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 01f16f3383..4c26cfe500 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -688,12 +688,12 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool /// @param init_ccline clear ccline first static uint8_t *command_line_enter(int firstc, long count, int indent, bool init_ccline) { - bool cmdheight0 = p_ch < 1 && !ui_has(kUIMessages) && vpeekc() == NUL; + bool cmdheight0 = p_ch < 1 && !ui_has(kUIMessages); if (cmdheight0) { // If cmdheight is 0, cmdheight must be set to 1 when we enter command line. set_option_value("ch", 1L, NULL, 0); - redraw_statuslines(); + update_screen(VALID); // redraw the screen NOW } // can be invoked recursively, identify each level @@ -985,6 +985,9 @@ theend: if (cmdheight0) { // Restore cmdheight set_option_value("ch", 0L, NULL, 0); + + // Redraw is needed for command line completion + redraw_all_later(CLEAR); } return p; @@ -2643,7 +2646,7 @@ char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_ /// @param[in] highlight_callback Callback used for highlighting user input. /// /// @return [allocated] Command line or NULL. -char *getcmdline_prompt(const char firstc, const char *const prompt, const int attr, +char *getcmdline_prompt(const int firstc, const char *const prompt, const int attr, const int xp_context, const char *const xp_arg, const Callback highlight_callback) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC @@ -2693,14 +2696,12 @@ char_u *get_cmdprompt(void) return ccline.cmdprompt; } -/* - * Return TRUE when the text must not be changed and we can't switch to - * another window or buffer. Used when editing the command line etc. - */ -int text_locked(void) +/// Return true when the text must not be changed and we can't switch to +/// another window or buffer. True when editing the command line etc. +bool text_locked(void) { if (cmdwin_type != 0) { - return TRUE; + return true; } return textlock != 0; } @@ -2719,10 +2720,21 @@ char *get_text_locked_msg(void) if (cmdwin_type != 0) { return e_cmdwin; } else { - return e_secure; + return e_textlock; } } +/// Check for text, window or buffer locked. +/// Give an error message and return true if something is locked. +bool text_or_buf_locked(void) +{ + if (text_locked()) { + text_locked_msg(); + return true; + } + return curbuf_locked(); +} + /// Check if "curbuf->b_ro_locked" or "allbuf_lock" is set and /// return true when it is and give an error message. bool curbuf_locked(void) @@ -3210,7 +3222,7 @@ static void draw_cmdline(int start, int len) int u8cc[MAX_MCO]; int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); mb_l = utfc_ptr2len_len(p, start + len - i); - if (arabic_char(u8c)) { + if (ARABIC_CHAR(u8c)) { do_arabicshape = true; break; } @@ -3246,7 +3258,7 @@ static void draw_cmdline(int start, int len) int u8cc[MAX_MCO]; int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); mb_l = utfc_ptr2len_len(p, start + len - i); - if (arabic_char(u8c)) { + if (ARABIC_CHAR(u8c)) { int pc; int pc1 = 0; int nc = 0; @@ -4320,6 +4332,7 @@ void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int o { int i; char_u *p; + const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; /* * May change home directory back to "~" @@ -4351,10 +4364,10 @@ void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int o #endif } #ifdef BACKSLASH_IN_FILENAME - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], false); + p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); #else p = (char_u *)vim_strsave_fnameescape((const char *)files[i], - xp->xp_shell); + xp->xp_shell ? VSE_SHELL : vse_what); #endif xfree(files[i]); files[i] = p; @@ -4386,25 +4399,30 @@ void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int o } } -/// Escape special characters in a file name for use as a command argument +/// Escape special characters in "fname", depending on "what": /// /// @param[in] fname File name to escape. -/// @param[in] shell What to escape for: if false, escapes for VimL command, -/// if true then it escapes for a shell command. +/// @param[in] what What to escape for: +/// - VSE_NONE: for when used as a file name argument after a Vim command. +/// - VSE_SHELL: for a shell command. +/// - VSE_BUFFER: for the ":buffer" command. /// /// @return [allocated] escaped file name. -char *vim_strsave_fnameescape(const char *const fname, const bool shell FUNC_ATTR_UNUSED) +char *vim_strsave_fnameescape(const char *const fname, const int what) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { #ifdef BACKSLASH_IN_FILENAME # define PATH_ESC_CHARS " \t\n*?[{`%#'\"|!<" +# define BUFFER_ESC_CHARS ((char_u *)" \t\n*?[`%#'\"|!<") char_u buf[sizeof(PATH_ESC_CHARS)]; int j = 0; - // Don't escape '[', '{' and '!' if they are in 'isfname'. - for (const char *s = PATH_ESC_CHARS; *s != NUL; s++) { - if ((*s != '[' && *s != '{' && *s != '!') || !vim_isfilec(*s)) { - buf[j++] = *s; + // Don't escape '[', '{' and '!' if they are in 'isfname' and for the + // ":buffer" command. + for (const char *p = what == VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS; + *p != NUL; p++) { + if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec(*p)) { + buf[j++] = *p; } } buf[j] = NUL; @@ -4413,9 +4431,12 @@ char *vim_strsave_fnameescape(const char *const fname, const bool shell FUNC_ATT #else # define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<") # define SHELL_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<>();&") +# define BUFFER_ESC_CHARS ((char_u *)" \t\n*?[`$\\%#'\"|!<") char *p = - (char *)vim_strsave_escaped((const char_u *)fname, (shell ? SHELL_ESC_CHARS : PATH_ESC_CHARS)); - if (shell && csh_like_shell()) { + (char *)vim_strsave_escaped((const char_u *)fname, + what == VSE_SHELL ? SHELL_ESC_CHARS + : what == VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS); + if (what == VSE_SHELL && csh_like_shell()) { // For csh and similar shells need to put two backslashes before '!'. // One is taken by Vim, one by the shell. char *s = (char *)vim_strsave_escaped((const char_u *)p, @@ -4452,14 +4473,13 @@ static void escape_fname(char_u **pp) */ void tilde_replace(char_u *orig_pat, int num_files, char_u **files) { - int i; - char_u *p; + char *p; if (orig_pat[0] == '~' && vim_ispathsep(orig_pat[1])) { - for (i = 0; i < num_files; ++i) { - p = home_replace_save(NULL, files[i]); + for (int i = 0; i < num_files; i++) { + p = home_replace_save(NULL, (char *)files[i]); xfree(files[i]); - files[i] = p; + files[i] = (char_u *)p; } } } @@ -5161,10 +5181,10 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ** return OK; } if (xp->xp_context == EXPAND_BUFFERS) { - return ExpandBufnames(pat, num_file, file, options); + return ExpandBufnames((char *)pat, num_file, (char ***)file, options); } if (xp->xp_context == EXPAND_DIFF_BUFFERS) { - return ExpandBufnames(pat, num_file, file, options | BUF_DIFF_FILTER); + return ExpandBufnames((char *)pat, num_file, (char ***)file, options | BUF_DIFF_FILTER); } if (xp->xp_context == EXPAND_TAGS || xp->xp_context == EXPAND_TAGS_LISTFILES) { @@ -5317,8 +5337,8 @@ static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, cha if (*str == NUL) { // skip empty strings continue; } - if (vim_regexec(regmatch, str, (colnr_T)0)) { - ++count; + if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { + count++; } } if (count == 0) { @@ -5338,7 +5358,7 @@ static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, cha if (*str == NUL) { // Skip empty strings. continue; } - if (vim_regexec(regmatch, str, (colnr_T)0)) { + if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { if (escaped) { str = vim_strsave_escaped(str, (char_u *)" \t\\."); } else { @@ -5570,7 +5590,7 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, *e = NUL; const bool skip = xp->xp_pattern[0] - && vim_regexec(regmatch, s, (colnr_T)0) == 0; + && vim_regexec(regmatch, (char *)s, (colnr_T)0) == 0; *e = keep; if (!skip) { GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); @@ -5811,7 +5831,7 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options) // Loop over all entries in {path}. while (*path != NUL) { // Copy one item of the path to buf[] and concatenate the file name. - copy_option_part(&path, buf, MAXPATHL, ","); + copy_option_part((char **)&path, (char *)buf, MAXPATHL, ","); if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { add_pathsep((char *)buf); STRCAT(buf, file); // NOLINT @@ -6290,7 +6310,7 @@ static int calc_hist_idx(int histype, int num) wrapped = TRUE; } } - if (hist[i].hisnum == num && hist[i].hisstr != NULL) { + if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) { return i; } } else if (-num <= hislen) { @@ -6367,7 +6387,7 @@ int del_history_entry(int histype, char_u *str) if (hisptr->hisstr == NULL) { break; } - if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { + if (vim_regexec(®match, (char *)hisptr->hisstr, (colnr_T)0)) { found = true; hist_free_entry(hisptr); } else { @@ -6535,7 +6555,7 @@ void ex_history(exarg_T *eap) snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', hist[i].hisnum); if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) { - trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff), + trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff), Columns - 10, IOSIZE - (int)STRLEN(IObuff)); } else { STRCAT(IObuff, hist[i].hisstr); @@ -6598,9 +6618,9 @@ static int open_cmdwin(void) bool save_exmode = exmode_active; int save_cmdmsg_rl = cmdmsg_rl; + // Can't do this when text or buffer is locked. // Can't do this recursively. Can't do it when typing a password. - if (cmdwin_type != 0 - || cmdline_star > 0) { + if (text_or_buf_locked() || cmdwin_type != 0 || cmdline_star > 0) { beep_flush(); return K_IGNORE; } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 5da9febe71..1cc6faf87c 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -34,6 +34,11 @@ #define WILD_BUFLASTUSED 0x1000 #define BUF_DIFF_FILTER 0x2000 +// flags used by vim_strsave_fnameescape() +#define VSE_NONE 0 +#define VSE_SHELL 1 ///< escape for a shell command +#define VSE_BUFFER 2 ///< escape for a ":buffer" command + /// Present history tables typedef enum { HIST_DEFAULT = -2, ///< Default (current) history. diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 08c232ea69..6ca6da9cd0 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -187,7 +187,7 @@ static int ses_do_win(win_T *wp) } if (wp->w_buffer->b_fname == NULL // When 'buftype' is "nofile" can't restore the window contents. - || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { + || (!wp->w_buffer->terminal && bt_nofilename(wp->w_buffer))) { return ssop_flags & SSOP_BLANK; } if (bt_help(wp->w_buffer)) { @@ -248,9 +248,9 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp) && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) && !p_acd && !did_lcd) { - return (char *)buf->b_sfname; + return buf->b_sfname; } - return (char *)buf->b_ffname; + return buf->b_ffname; } /// Write a buffer name to the session file. @@ -275,7 +275,7 @@ static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) static char *ses_escape_fname(char *name, unsigned *flagp) { char *p; - char *sname = (char *)home_replace_save(NULL, (char_u *)name); + char *sname = home_replace_save(NULL, name); // Always SSOP_SLASH: change all backslashes to forward slashes. for (p = sname; *p != NUL; MB_PTR_ADV(p)) { @@ -285,7 +285,7 @@ static char *ses_escape_fname(char *name, unsigned *flagp) } // Escape special characters. - p = vim_strsave_fnameescape(sname, false); + p = vim_strsave_fnameescape(sname, VSE_NONE); xfree(sname); return p; } @@ -363,7 +363,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr return FAIL; } } else if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)) { + && (!bt_nofilename(wp->w_buffer) || wp->w_buffer->terminal)) { // Load the file. // Editing a file in this buffer: use ":edit file". @@ -517,7 +517,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr if (wp->w_localdir != NULL && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { if (fputs("lcd ", fd) < 0 - || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL + || ses_put_fname(fd, (char_u *)wp->w_localdir, flagp) == FAIL || fprintf(fd, "\n") < 0) { return FAIL; } @@ -542,7 +542,7 @@ static int makeopens(FILE *fd, char_u *dirnow) int nr; int restore_size = true; win_T *wp; - char_u *sname; + char *sname; win_T *edited_win = NULL; int tabnr; win_T *tab_firstwin; @@ -575,8 +575,8 @@ static int makeopens(FILE *fd, char_u *dirnow) if (ssop_flags & SSOP_SESDIR) { PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); } else if (ssop_flags & SSOP_CURDIR) { - sname = home_replace_save(NULL, globaldir != NULL ? (char_u *)globaldir : dirnow); - char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags); + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : (char *)dirnow); + char *fname_esc = ses_escape_fname(sname, &ssop_flags); if (fprintf(fd, "cd %s\n", fname_esc) < 0) { xfree(fname_esc); xfree(sname); @@ -621,7 +621,7 @@ static int makeopens(FILE *fd, char_u *dirnow) if (fprintf(fd, "badd +%" PRId64 " ", buf->b_wininfo == NULL ? (int64_t)1L - : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + : (int64_t)buf->b_wininfo->wi_mark.mark.lnum) < 0 || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { return FAIL; } @@ -706,7 +706,7 @@ static int makeopens(FILE *fd, char_u *dirnow) if (ses_do_win(wp) && wp->w_buffer->b_ffname != NULL && !bt_help(wp->w_buffer) - && !bt_nofile(wp->w_buffer)) { + && !bt_nofilename(wp->w_buffer)) { if (need_tabnext && put_line(fd, "tabnext") == FAIL) { return FAIL; } @@ -821,7 +821,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // Take care of tab-local working directories if applicable if (tp->tp_localdir) { if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 - || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL + || ses_put_fname(fd, (char_u *)tp->tp_localdir, &ssop_flags) == FAIL || fputs(" | endif\n", fd) < 0) { return FAIL; } @@ -1001,7 +1001,7 @@ void ex_mkrc(exarg_T *eap) *dirnow = NUL; } if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { - if (vim_chdirfile((char_u *)fname, kCdCauseOther) == OK) { + if (vim_chdirfile(fname, kCdCauseOther) == OK) { shorten_fnames(true); } } else if (*dirnow != NUL @@ -1075,7 +1075,7 @@ static char *get_view_file(int c) emsg(_(e_noname)); return NULL; } - char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname); + char *sname = home_replace_save(NULL, curbuf->b_ffname); // We want a file name without separators, because we're not going to make // a directory. diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index e327b97fbe..ca276b8a40 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -893,8 +893,7 @@ char_u *vim_findfile(void *search_ctx_arg) break; } assert(MAXPATHL >= len); - copy_option_part(&suf, file_path + len, - MAXPATHL - len, ","); + copy_option_part((char **)&suf, (char *)file_path + len, MAXPATHL - len, ","); } } } else { @@ -1503,7 +1502,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } assert(MAXPATHL >= l); - copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ","); + copy_option_part((char **)&buf, (char *)NameBuff + l, MAXPATHL - l, ","); } } } @@ -1543,7 +1542,7 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first // copy next path buf[0] = 0; - copy_option_part(&dir, buf, MAXPATHL, " ,"); + copy_option_part((char **)&dir, (char *)buf, MAXPATHL, " ,"); // get the stopdir string r_ptr = vim_findfile_stopdir(buf); @@ -1653,12 +1652,12 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre /// Caller must call shorten_fnames()! /// /// @return OK or FAIL -int vim_chdirfile(char_u *fname, CdCause cause) +int vim_chdirfile(char *fname, CdCause cause) { char dir[MAXPATHL]; STRLCPY(dir, fname, MAXPATHL); - *path_tail_with_sep((char_u *)dir) = NUL; + *path_tail_with_sep(dir) = NUL; if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) { NameBuff[0] = NUL; @@ -1688,7 +1687,7 @@ int vim_chdirfile(char_u *fname, CdCause cause) int vim_chdir(char_u *new_dir) { char *dir_name = (char *)find_directory_in_path(new_dir, STRLEN(new_dir), - FNAME_MESS, curbuf->b_ffname); + FNAME_MESS, (char_u *)curbuf->b_ffname); if (dir_name == NULL) { return -1; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 9ba55befdd..6782465ef1 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -275,10 +275,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, // executing nasty autocommands. Also check if "fname" and "sfname" // point to one of these values. old_curbuf = curbuf; - old_b_ffname = (char *)curbuf->b_ffname; + old_b_ffname = curbuf->b_ffname; old_b_fname = curbuf->b_fname; - using_b_ffname = ((char_u *)fname == curbuf->b_ffname) - || ((char_u *)sfname == curbuf->b_ffname); + using_b_ffname = (fname == curbuf->b_ffname) || (sfname == curbuf->b_ffname); using_b_fname = (fname == curbuf->b_fname) || (sfname == curbuf->b_fname); // After reading a file the cursor line changes but we don't want to @@ -373,7 +372,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, && !S_ISFIFO(perm) // ... or fifo && !S_ISSOCK(perm) // ... or socket #ifdef OPEN_CHR_FILES - && !(S_ISCHR(perm) && is_dev_fd_file((char_u *)fname)) + && !(S_ISCHR(perm) && is_dev_fd_file(fname)) // ... or a character special file named /dev/fd/<n> #endif ) { @@ -466,7 +465,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, // SwapExists autocommand may mess things up if (curbuf != old_curbuf || (using_b_ffname - && ((char_u *)old_b_ffname != curbuf->b_ffname)) + && (old_b_ffname != curbuf->b_ffname)) || (using_b_fname && (old_b_fname != curbuf->b_fname))) { emsg(_(e_auchangedbuf)); @@ -538,7 +537,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, check_need_swap(newfile); if (!read_stdin && (curbuf != old_curbuf - || (using_b_ffname && ((char_u *)old_b_ffname != curbuf->b_ffname)) + || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) || (using_b_fname && (old_b_fname != curbuf->b_fname)))) { emsg(_(e_auchangedbuf)); if (!read_buffer) { @@ -645,7 +644,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, * (cd for example) if it invalidates fname or sfname. */ if (!read_stdin && (curbuf != old_curbuf - || (using_b_ffname && ((char_u *)old_b_ffname != curbuf->b_ffname)) + || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) || (using_b_fname && (old_b_fname != curbuf->b_fname)) || (fd = os_open(fname, O_RDONLY, 0)) < 0)) { no_wait_return--; @@ -1974,12 +1973,12 @@ failed: /// Do not accept "/dev/fd/[012]", opening these may hang Vim. /// /// @param fname file name to check -bool is_dev_fd_file(char_u *fname) +bool is_dev_fd_file(char *fname) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { return STRNCMP(fname, "/dev/fd/", 8) == 0 - && ascii_isdigit(fname[8]) - && *skipdigits((char *)fname + 9) == NUL + && ascii_isdigit((uint8_t)fname[8]) + && *skipdigits(fname + 9) == NUL && (fname[9] != NUL || (fname[8] != '0' && fname[8] != '1' && fname[8] != '2')); } @@ -2287,7 +2286,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && reset_changed && whole && buf == curbuf - && !bt_nofile(buf) + && !bt_nofilename(buf) && !filtering && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) { @@ -2342,16 +2341,16 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en * Set curbuf to the buffer to be written. * Careful: The autocommands may call buf_write() recursively! */ - if ((char_u *)ffname == buf->b_ffname) { + if (ffname == buf->b_ffname) { buf_ffname = true; } - if ((char_u *)sfname == buf->b_sfname) { + if (sfname == buf->b_sfname) { buf_sfname = true; } - if ((char_u *)fname == buf->b_ffname) { + if (fname == buf->b_ffname) { buf_fname_f = true; } - if ((char_u *)fname == buf->b_sfname) { + if (fname == buf->b_sfname) { buf_fname_s = true; } @@ -2361,22 +2360,22 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (append) { if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, - sfname, sfname, FALSE, curbuf, eap))) { - if (overwriting && bt_nofile(curbuf)) { - nofile_err = TRUE; + sfname, sfname, false, curbuf, eap))) { + if (overwriting && bt_nofilename(curbuf)) { + nofile_err = true; } else { apply_autocmds_exarg(EVENT_FILEAPPENDPRE, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); } } } else if (filtering) { apply_autocmds_exarg(EVENT_FILTERWRITEPRE, - NULL, sfname, FALSE, curbuf, eap); + NULL, sfname, false, curbuf, eap); } else if (reset_changed && whole) { int was_changed = curbufIsChanged(); did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); if (did_cmd) { if (was_changed && !curbufIsChanged()) { /* Written everything correctly and BufWriteCmd has reset @@ -2386,21 +2385,21 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en u_update_save_nr(curbuf); } } else { - if (overwriting && bt_nofile(curbuf)) { - nofile_err = TRUE; + if (overwriting && bt_nofilename(curbuf)) { + nofile_err = true; } else { apply_autocmds_exarg(EVENT_BUFWRITEPRE, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); } } } else { if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, - sfname, sfname, FALSE, curbuf, eap))) { - if (overwriting && bt_nofile(curbuf)) { - nofile_err = TRUE; + sfname, sfname, false, curbuf, eap))) { + if (overwriting && bt_nofilename(curbuf)) { + nofile_err = true; } else { apply_autocmds_exarg(EVENT_FILEWRITEPRE, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); } } } @@ -2491,16 +2490,16 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en * be kept in fname, ffname and sfname. */ if (buf_ffname) { - ffname = (char *)buf->b_ffname; + ffname = buf->b_ffname; } if (buf_sfname) { - sfname = (char *)buf->b_sfname; + sfname = buf->b_sfname; } if (buf_fname_f) { - fname = (char *)buf->b_ffname; + fname = buf->b_ffname; } if (buf_fname_s) { - fname = (char *)buf->b_sfname; + fname = buf->b_sfname; } } @@ -2762,7 +2761,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en /* * Isolate one directory name, using an entry in 'bdir'. */ - size_t dir_len = copy_option_part((char_u **)&dirp, IObuff, IOSIZE, ","); + size_t dir_len = copy_option_part(&dirp, (char *)IObuff, IOSIZE, ","); p = (char *)IObuff + dir_len; bool trailing_pathseps = after_pathsep((char *)IObuff, p) && p[-1] == p[-2]; if (trailing_pathseps) { @@ -2923,7 +2922,7 @@ nobackup: /* * Isolate one directory name and make the backup file name. */ - size_t dir_len = copy_option_part((char_u **)&dirp, IObuff, IOSIZE, ","); + size_t dir_len = copy_option_part(&dirp, (char *)IObuff, IOSIZE, ","); p = (char *)IObuff + dir_len; bool trailing_pathseps = after_pathsep((char *)IObuff, p) && p[-1] == p[-2]; if (trailing_pathseps) { @@ -4304,24 +4303,24 @@ static int make_bom(char_u *buf, char_u *name) /// name. void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) { - char_u *p; + char *p; if (buf->b_fname != NULL - && !bt_nofile(buf) + && !bt_nofilename(buf) && !path_with_url(buf->b_fname) && (force || buf->b_sfname == NULL - || path_is_absolute(buf->b_sfname))) { + || path_is_absolute((char_u *)buf->b_sfname))) { if (buf->b_sfname != buf->b_ffname) { XFREE_CLEAR(buf->b_sfname); } - p = path_shorten_fname(buf->b_ffname, dirname); + p = (char *)path_shorten_fname((char_u *)buf->b_ffname, dirname); if (p != NULL) { - buf->b_sfname = vim_strsave(p); - buf->b_fname = (char *)buf->b_sfname; + buf->b_sfname = xstrdup(p); + buf->b_fname = buf->b_sfname; } if (p == NULL) { - buf->b_fname = (char *)buf->b_ffname; + buf->b_fname = buf->b_ffname; } } } @@ -4918,7 +4917,7 @@ int buf_check_timestamp(buf_T *buf) bool file_info_ok; if (!(buf->b_flags & BF_NOTEDITED) && buf->b_mtime != 0 - && (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info)) + && (!(file_info_ok = os_fileinfo(buf->b_ffname, &file_info)) || time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns) || (int)file_info.stat.st_mode != buf->b_orig_mode)) { const long prev_b_mtime = buf->b_mtime; @@ -5016,7 +5015,7 @@ int buf_check_timestamp(buf_T *buf) } } } else if ((buf->b_flags & BF_NEW) && !(buf->b_flags & BF_NEW_W) - && os_path_exists(buf->b_ffname)) { + && os_path_exists((char_u *)buf->b_ffname)) { retval = 1; mesg = _("W13: Warning: File \"%s\" has been created after editing started"); buf->b_flags |= BF_NEW_W; @@ -5024,7 +5023,7 @@ int buf_check_timestamp(buf_T *buf) } if (mesg != NULL) { - path = home_replace_save(buf, (char_u *)buf->b_fname); + path = (char_u *)home_replace_save(buf, buf->b_fname); if (!helpmesg) { mesg2 = ""; } @@ -5169,7 +5168,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) if (saved == OK) { curbuf->b_flags |= BF_CHECK_RO; // check for RO again keep_filetype = true; // don't detect 'filetype' - if (readfile((char *)buf->b_ffname, buf->b_fname, (linenr_T)0, (linenr_T)0, + if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, &ea, flags, false) != OK) { if (!aborting()) { semsg(_("E321: Could not reload \"%s\""), buf->b_fname); @@ -5547,10 +5546,10 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname, */ if (regmatch.regprog != NULL && ((allow_dirs - && (vim_regexec(®match, (char_u *)fname, (colnr_T)0) + && (vim_regexec(®match, fname, (colnr_T)0) || (sfname != NULL - && vim_regexec(®match, (char_u *)sfname, (colnr_T)0)))) - || (!allow_dirs && vim_regexec(®match, (char_u *)tail, (colnr_T)0)))) { + && vim_regexec(®match, sfname, (colnr_T)0)))) + || (!allow_dirs && vim_regexec(®match, tail, (colnr_T)0)))) { result = true; } @@ -5586,7 +5585,7 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) // try all patterns in 'wildignore' p = list; while (*p) { - copy_option_part(&p, buf, ARRAY_SIZE(buf), ","); + copy_option_part((char **)&p, (char *)buf, ARRAY_SIZE(buf), ","); regpat = (char_u *)file_pat_to_reg_pat((char *)buf, NULL, &allow_dirs, false); if (regpat == NULL) { break; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 3c3aaa1031..8f26e03a94 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -3,9 +3,7 @@ // vim: set fdm=marker fdl=1 fdc=3 -/* - * fold.c: code for folding - */ +// fold.c: code for folding #include <inttypes.h> #include <string.h> @@ -41,12 +39,12 @@ // local declarations. {{{1 // typedef fold_T {{{2 -/* - * The toplevel folds for each window are stored in the w_folds growarray. - * Each toplevel fold can contain an array of second level folds in the - * fd_nested growarray. - * The info stored in both growarrays is the same: An array of fold_T. - */ + +// The toplevel folds for each window are stored in the w_folds growarray. +// Each toplevel fold can contain an array of second level folds in the +// fd_nested growarray. +// The info stored in both growarrays is the same: An array of fold_T. + typedef struct { linenr_T fd_top; // first line of fold; for nested fold // relative to parent @@ -93,20 +91,16 @@ typedef void (*LevelGetter)(fline_T *); #endif static char *e_nofold = N_("E490: No fold found"); -/* - * While updating the folds lines between invalid_top and invalid_bot have an - * undefined fold level. Only used for the window currently being updated. - */ +// While updating the folds lines between invalid_top and invalid_bot have an +// undefined fold level. Only used for the window currently being updated. static linenr_T invalid_top = (linenr_T)0; static linenr_T invalid_bot = (linenr_T)0; -/* - * When using 'foldexpr' we sometimes get the level of the next line, which - * calls foldlevel() to get the level of the current line, which hasn't been - * stored yet. To get around this chicken-egg problem the level of the - * previous line is stored here when available. prev_lnum is zero when the - * level is not available. - */ +// When using 'foldexpr' we sometimes get the level of the next line, which +// calls foldlevel() to get the level of the current line, which hasn't been +// stored yet. To get around this chicken-egg problem the level of the +// previous line is stored here when available. prev_lnum is zero when the +// level is not available. static linenr_T prev_lnum = 0; static int prev_lnum_lvl = -1; @@ -130,7 +124,7 @@ void copyFoldingState(win_T *wp_from, win_T *wp_to) } // hasAnyFolding() {{{2 -/// @return TRUE if there may be folded lines in the current window. +/// @return true if there may be folded lines in the current window. int hasAnyFolding(win_T *win) { // very simple now, but can become more complex later @@ -160,16 +154,6 @@ bool hasFolding(linenr_T lnum, linenr_T *firstp, linenr_T *lastp) bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp, linenr_T *const lastp, const bool cache, foldinfo_T *const infop) { - bool had_folded = false; - linenr_T first = 0; - linenr_T last = 0; - linenr_T lnum_rel = lnum; - fold_T *fp; - int level = 0; - bool use_level = false; - bool maybe_small = false; - int low_level = 0; - checkupdate(win); // Return quickly when there is no folding at all in this window. @@ -180,11 +164,13 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp return false; } + bool had_folded = false; + linenr_T first = 0; + linenr_T last = 0; + if (cache) { - /* - * First look in cached info for displayed lines. This is probably - * the fastest, but it can only be used if the entry is still valid. - */ + // First look in cached info for displayed lines. This is probably + // the fastest, but it can only be used if the entry is still valid. const int x = find_wl_entry(win, lnum); if (x >= 0) { first = win->w_lines[x].wl_lnum; @@ -193,10 +179,15 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp } } + linenr_T lnum_rel = lnum; + int level = 0; + int low_level = 0; + fold_T *fp; + bool maybe_small = false; + bool use_level = false; + if (first == 0) { - /* - * Recursively search for a fold that contains "lnum". - */ + // Recursively search for a fold that contains "lnum". garray_T *gap = &win->w_folds; for (;;) { if (!foldFind(gap, lnum_rel, &fp)) { @@ -224,7 +215,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp // relative to containing fold. gap = &fp->fd_nested; lnum_rel -= fp->fd_top; - ++level; + level++; } } @@ -311,42 +302,42 @@ foldinfo_T fold_info(win_T *win, linenr_T lnum) } // foldmethodIsManual() {{{2 -/// @return TRUE if 'foldmethod' is "manual" +/// @return true if 'foldmethod' is "manual" int foldmethodIsManual(win_T *wp) { return wp->w_p_fdm[3] == 'u'; } // foldmethodIsIndent() {{{2 -/// @return TRUE if 'foldmethod' is "indent" +/// @return true if 'foldmethod' is "indent" int foldmethodIsIndent(win_T *wp) { return wp->w_p_fdm[0] == 'i'; } // foldmethodIsExpr() {{{2 -/// @return TRUE if 'foldmethod' is "expr" +/// @return true if 'foldmethod' is "expr" int foldmethodIsExpr(win_T *wp) { return wp->w_p_fdm[1] == 'x'; } // foldmethodIsMarker() {{{2 -/// @return TRUE if 'foldmethod' is "marker" +/// @return true if 'foldmethod' is "marker" int foldmethodIsMarker(win_T *wp) { return wp->w_p_fdm[2] == 'r'; } // foldmethodIsSyntax() {{{2 -/// @return TRUE if 'foldmethod' is "syntax" +/// @return true if 'foldmethod' is "syntax" int foldmethodIsSyntax(win_T *wp) { return wp->w_p_fdm[0] == 's'; } // foldmethodIsDiff() {{{2 -/// @return TRUE if 'foldmethod' is "diff" +/// @return true if 'foldmethod' is "diff" int foldmethodIsDiff(win_T *wp) { return wp->w_p_fdm[0] == 'd'; @@ -372,18 +363,17 @@ void closeFoldRecurse(pos_T pos) /// Open or Close folds for current window in lines "first" to "last". /// Used for "zo", "zO", "zc" and "zC" in Visual mode. /// -/// @param opening TRUE to open, FALSE to close -/// @param recurse TRUE to do it recursively -/// @param had_visual TRUE when Visual selection used +/// @param opening true to open, false to close +/// @param recurse true to do it recursively +/// @param had_visual true when Visual selection used void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int had_visual) { int done = DONE_NOTHING; // avoid error messages linenr_T first = firstpos.lnum; linenr_T last = lastpos.lnum; - linenr_T lnum; linenr_T lnum_next; - for (lnum = first; lnum <= last; lnum = lnum_next + 1) { + for (linenr_T lnum = first; lnum <= last; lnum = lnum_next + 1) { pos_T temp = { lnum, 0, 0 }; lnum_next = lnum; // Opening one level only: next fold to open is after the one going to @@ -426,12 +416,10 @@ void openFoldRecurse(pos_T pos) /// Open folds until the cursor line is not in a closed fold. void foldOpenCursor(void) { - int done; - checkupdate(curwin); if (hasAnyFolding(curwin)) { for (;;) { - done = DONE_NOTHING; + int done = DONE_NOTHING; (void)setManualFold(curwin->w_cursor, true, false, &done); if (!(done & DONE_ACTION)) { break; @@ -447,9 +435,7 @@ void newFoldLevel(void) newFoldLevelWin(curwin); if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { - /* - * Set the same foldlevel in other windows in diff mode. - */ + // Set the same foldlevel in other windows in diff mode. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { wp->w_p_fdl = curwin->w_p_fdl; @@ -461,15 +447,13 @@ void newFoldLevel(void) static void newFoldLevelWin(win_T *wp) { - fold_T *fp; - checkupdate(wp); if (wp->w_fold_manual) { // Set all flags for the first level of folds to FD_LEVEL. Following // manual open/close will then change the flags to FD_OPEN or // FD_CLOSED for those folds that don't use 'foldlevel'. - fp = (fold_T *)wp->w_folds.ga_data; - for (int i = 0; i < wp->w_folds.ga_len; ++i) { + fold_T *fp = (fold_T *)wp->w_folds.ga_data; + for (int i = 0; i < wp->w_folds.ga_len; i++) { fp[i].fd_flags = FD_LEVEL; } wp->w_fold_manual = false; @@ -491,19 +475,18 @@ void foldCheckClose(void) } // checkCloseRec() {{{2 -static int checkCloseRec(garray_T *gap, linenr_T lnum, int level) +static bool checkCloseRec(garray_T *gap, linenr_T lnum, int level) { - fold_T *fp; - int retval = FALSE; + bool retval = false; - fp = (fold_T *)gap->ga_data; - for (int i = 0; i < gap->ga_len; ++i) { + fold_T *fp = (fold_T *)gap->ga_data; + for (int i = 0; i < gap->ga_len; i++) { // Only manually opened folds may need to be closed. if (fp[i].fd_flags == FD_OPEN) { if (level <= 0 && (lnum < fp[i].fd_top || lnum >= fp[i].fd_top + fp[i].fd_len)) { fp[i].fd_flags = FD_LEVEL; - retval = TRUE; + retval = true; } else { retval |= checkCloseRec(&fp[i].fd_nested, lnum - fp[i].fd_top, level - 1); @@ -514,19 +497,19 @@ static int checkCloseRec(garray_T *gap, linenr_T lnum, int level) } // foldCreateAllowed() {{{2 -/// @return TRUE if it's allowed to manually create or delete a fold or, -/// give an error message and return FALSE if not. +/// @return true if it's allowed to manually create or delete a fold or, +/// give an error message and return false if not. int foldManualAllowed(bool create) { if (foldmethodIsManual(curwin) || foldmethodIsMarker(curwin)) { - return TRUE; + return true; } if (create) { emsg(_("E350: Cannot create fold with current 'foldmethod'")); } else { emsg(_("E351: Cannot delete fold with current 'foldmethod'")); } - return FALSE; + return false; } // foldCreate() {{{2 @@ -534,13 +517,8 @@ int foldManualAllowed(bool create) /// window. void foldCreate(win_T *wp, pos_T start, pos_T end) { - fold_T *fp; - garray_T *gap; - garray_T fold_ga; - int i; - int cont; - int use_level = FALSE; - int closed = FALSE; + int use_level = false; + int closed = false; int level = 0; pos_T start_rel = start; pos_T end_rel = end; @@ -561,11 +539,14 @@ void foldCreate(win_T *wp, pos_T start, pos_T end) checkupdate(wp); + int i; + // Find the place to insert the new fold - gap = &wp->w_folds; + garray_T *gap = &wp->w_folds; if (gap->ga_len == 0) { i = 0; } else { + fold_T *fp; for (;;) { if (!foldFind(gap, start_rel.lnum, &fp)) { break; @@ -599,10 +580,12 @@ void foldCreate(win_T *wp, pos_T start, pos_T end) ga_grow(gap, 1); { - fp = (fold_T *)gap->ga_data + i; + fold_T *fp = (fold_T *)gap->ga_data + i; + garray_T fold_ga; ga_init(&fold_ga, (int)sizeof(fold_T), 10); // Count number of folds that will be contained in the new fold. + int cont; for (cont = 0; i + cont < gap->ga_len; cont++) { if (fp[cont].fd_top > end_rel.lnum) { break; @@ -668,7 +651,6 @@ void foldCreate(win_T *wp, pos_T start, pos_T end) void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const int recursive, const bool had_visual) { - fold_T *fp; fold_T *found_fp = NULL; linenr_T found_off = 0; bool maybe_small = false; @@ -687,6 +669,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const linenr_T lnum_off = 0; bool use_level = false; for (;;) { + fold_T *fp; if (!foldFind(gap, lnum - lnum_off, &fp)) { break; } @@ -704,10 +687,10 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const // check nested folds gap = &fp->fd_nested; lnum_off += fp->fd_top; - ++level; + level++; } if (found_ga == NULL) { - ++lnum; + lnum++; } else { lnum = found_fp->fd_top + found_fp->fd_len + found_off; @@ -808,7 +791,7 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) int save_got_int = got_int; // reset got_int here, otherwise it won't work - got_int = FALSE; + got_int = false; foldUpdateIEMS(wp, top, bot); got_int |= save_got_int; } @@ -944,7 +927,7 @@ int foldMoveTo(const bool updown, const int dir, const long count) // Check nested folds (if any). gap = &fp->fd_nested; lnum_off += fp->fd_top; - ++level; + level++; } if (lnum_found != curwin->w_cursor.lnum) { if (retval == FAIL) { @@ -970,15 +953,13 @@ void foldInitWin(win_T *new_win) // find_wl_entry() {{{2 /// Find an entry in the win->w_lines[] array for buffer line "lnum". -/// Only valid entries are considered (for entries where wl_valid is FALSE the +/// Only valid entries are considered (for entries where wl_valid is false the /// line number can be wrong). /// /// @return index of entry or -1 if not found. int find_wl_entry(win_T *win, linenr_T lnum) { - int i; - - for (i = 0; i < win->w_lines_valid; ++i) { + for (int i = 0; i < win->w_lines_valid; i++) { if (win->w_lines[i].wl_valid) { if (lnum < win->w_lines[i].wl_lnum) { return -1; @@ -995,13 +976,13 @@ int find_wl_entry(win_T *win, linenr_T lnum) /// Adjust the Visual area to include any fold at the start or end completely. void foldAdjustVisual(void) { - pos_T *start, *end; - char_u *ptr; - if (!VIsual_active || !hasAnyFolding(curwin)) { return; } + pos_T *start, *end; + char_u *ptr; + if (ltoreq(VIsual, curwin->w_cursor)) { start = &VIsual; end = &curwin->w_cursor; @@ -1035,9 +1016,6 @@ void foldAdjustCursor(void) /// Will "clone" (i.e deep copy) a garray_T of folds. void cloneFoldGrowArray(garray_T *from, garray_T *to) { - fold_T *from_p; - fold_T *to_p; - ga_init(to, from->ga_itemsize, from->ga_growsize); if (GA_EMPTY(from)) { @@ -1046,8 +1024,8 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to) ga_grow(to, from->ga_len); - from_p = (fold_T *)from->ga_data; - to_p = (fold_T *)to->ga_data; + fold_T *from_p = (fold_T *)from->ga_data; + fold_T *to_p = (fold_T *)to->ga_data; for (int i = 0; i < from->ga_len; i++) { to_p->fd_top = from_p->fd_top; @@ -1055,9 +1033,9 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to) to_p->fd_flags = from_p->fd_flags; to_p->fd_small = from_p->fd_small; cloneFoldGrowArray(&from_p->fd_nested, &to_p->fd_nested); - ++to->ga_len; - ++from_p; - ++to_p; + to->ga_len++; + from_p++; + to_p++; } } @@ -1069,22 +1047,17 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to) /// @return false when there is no fold that contains "lnum". static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) { - linenr_T low, high; - fold_T *fp; - if (gap->ga_len == 0) { *fpp = NULL; return false; } - /* - * Perform a binary search. - * "low" is lowest index of possible match. - * "high" is highest index of possible match. - */ - fp = (fold_T *)gap->ga_data; - low = 0; - high = gap->ga_len - 1; + // Perform a binary search. + // "low" is lowest index of possible match. + // "high" is highest index of possible match. + fold_T *fp = (fold_T *)gap->ga_data; + linenr_T low = 0; + linenr_T high = gap->ga_len - 1; while (low <= high) { linenr_T i = (low + high) / 2; if (fp[i].fd_top > lnum) { @@ -1110,10 +1083,9 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) fold_T *fp; linenr_T lnum_rel = lnum; int level = 0; - garray_T *gap; // Recursively search for a fold that contains "lnum". - gap = &wp->w_folds; + garray_T *gap = &wp->w_folds; for (;;) { if (!foldFind(gap, lnum_rel, &fp)) { break; @@ -1121,7 +1093,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) // Check nested folds. Line number is relative to containing fold. gap = &fp->fd_nested; lnum_rel -= fp->fd_top; - ++level; + level++; } return level; @@ -1142,11 +1114,8 @@ static void checkupdate(win_T *wp) /// Repeat "count" times. static void setFoldRepeat(pos_T pos, long count, int do_open) { - int done; - long n; - - for (n = 0; n < count; ++n) { - done = DONE_NOTHING; + for (int n = 0; n < count; n++) { + int done = DONE_NOTHING; (void)setManualFold(pos, do_open, false, &done); if (!(done & DONE_ACTION)) { // Only give an error message when no fold could be opened. @@ -1162,18 +1131,15 @@ static void setFoldRepeat(pos_T pos, long count, int do_open) /// Open or close the fold in the current window which contains "lnum". /// Also does this for other windows in diff mode when needed. /// -/// @param opening TRUE when opening, FALSE when closing -/// @param recurse TRUE when closing/opening recursive +/// @param opening true when opening, false when closing +/// @param recurse true when closing/opening recursive static linenr_T setManualFold(pos_T pos, int opening, int recurse, int *donep) { - linenr_T lnum = pos.lnum; if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { linenr_T dlnum; - /* - * Do the same operation in other windows in diff mode. Calculate the - * line number from the diffs. - */ + // Do the same operation in other windows in diff mode. Calculate the + // line number from the diffs. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { dlnum = diff_lnum_win(curwin->w_cursor.lnum, wp); @@ -1184,7 +1150,7 @@ static linenr_T setManualFold(pos_T pos, int opening, int recurse, int *donep) } } - return setManualFoldWin(curwin, lnum, opening, recurse, donep); + return setManualFoldWin(curwin, pos.lnum, opening, recurse, donep); } // setManualFoldWin() {{{2 @@ -1194,31 +1160,27 @@ static linenr_T setManualFold(pos_T pos, int opening, int recurse, int *donep) /// When "donep" is NULL give an error message when no fold was found for /// "lnum", but only if "wp" is "curwin". /// -/// @param opening TRUE when opening, FALSE when closing -/// @param recurse TRUE when closing/opening recursive +/// @param opening true when opening, false when closing +/// @param recurse true when closing/opening recursive /// /// @return the line number of the next line that could be closed. -/// It's only valid when "opening" is TRUE! +/// It's only valid when "opening" is true! static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recurse, int *donep) { fold_T *fp; fold_T *fp2; fold_T *found = NULL; - int j; int level = 0; - int use_level = FALSE; - int found_fold = FALSE; - garray_T *gap; + bool use_level = false; + bool found_fold = false; linenr_T next = MAXLNUM; linenr_T off = 0; int done = 0; checkupdate(wp); - /* - * Find the fold, open or close it. - */ - gap = &wp->w_folds; + // Find the fold, open or close it. + garray_T *gap = &wp->w_folds; for (;;) { if (!foldFind(gap, lnum, &fp)) { // If there is a following fold, continue there next time. @@ -1229,7 +1191,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu } // lnum is inside this fold - found_fold = TRUE; + found_fold = true; // If there is a following fold, continue there next time. if (fp + 1 < (fold_T *)gap->ga_data + gap->ga_len) { @@ -1238,14 +1200,14 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu // Change from level-dependent folding to manual. if (use_level || fp->fd_flags == FD_LEVEL) { - use_level = TRUE; + use_level = true; if (level >= wp->w_p_fdl) { fp->fd_flags = FD_CLOSED; } else { fp->fd_flags = FD_OPEN; } fp2 = (fold_T *)fp->fd_nested.ga_data; - for (j = 0; j < fp->fd_nested.ga_len; ++j) { + for (int j = 0; j < fp->fd_nested.ga_len; j++) { fp2[j].fd_flags = FD_LEVEL; } } @@ -1273,7 +1235,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu gap = &fp->fd_nested; lnum -= fp->fd_top; off += fp->fd_top; - ++level; + level++; } if (found_fold) { // When closing and not recurse, close deepest open fold. @@ -1301,10 +1263,8 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu /// Open all nested folds in fold "fpr" recursively. static void foldOpenNested(fold_T *fpr) { - fold_T *fp; - - fp = (fold_T *)fpr->fd_nested.ga_data; - for (int i = 0; i < fpr->fd_nested.ga_len; ++i) { + fold_T *fp = (fold_T *)fpr->fd_nested.ga_data; + for (int i = 0; i < fpr->fd_nested.ga_len; i++) { foldOpenNested(&fp[i]); fp[i].fd_flags = FD_OPEN; } @@ -1390,14 +1350,12 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount, static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after) { - fold_T *fp; - linenr_T last; - linenr_T top; - if (gap->ga_len == 0) { return; } + linenr_T top; + // In Insert mode an inserted line at the top of a fold is considered part // of the fold, otherwise it isn't. if ((State & MODE_INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) { @@ -1407,25 +1365,22 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line } // Find the fold containing or just below "line1". + fold_T *fp; (void)foldFind(gap, line1, &fp); - /* - * Adjust all folds below "line1" that are affected. - */ - for (int i = (int)(fp - (fold_T *)gap->ga_data); i < gap->ga_len; ++i, ++fp) { - /* - * Check for these situations: - * 1 2 3 - * 1 2 3 - * line1 2 3 4 5 - * 2 3 4 5 - * 2 3 4 5 - * line2 2 3 4 5 - * 3 5 6 - * 3 5 6 - */ - - last = fp->fd_top + fp->fd_len - 1; // last line of fold + // Adjust all folds below "line1" that are affected. + for (int i = (int)(fp - (fold_T *)gap->ga_data); i < gap->ga_len; i++, fp++) { + // Check for these situations: + // 1 2 3 + // 1 2 3 + // line1 2 3 4 5 + // 2 3 4 5 + // 2 3 4 5 + // line2 2 3 4 5 + // 3 5 6 + // 3 5 6 + + linenr_T last = fp->fd_top + fp->fd_len - 1; // last line of fold // 1. fold completely above line1: nothing to do if (last < line1) { @@ -1498,13 +1453,11 @@ int getDeepestNesting(win_T *wp) static int getDeepestNestingRecurse(garray_T *gap) { - int level; int maxlevel = 0; - fold_T *fp; - fp = (fold_T *)gap->ga_data; - for (int i = 0; i < gap->ga_len; ++i) { - level = getDeepestNestingRecurse(&fp[i].fd_nested) + 1; + fold_T *fp = (fold_T *)gap->ga_data; + for (int i = 0; i < gap->ga_len; i++) { + int level = getDeepestNestingRecurse(&fp[i].fd_nested) + 1; if (level > maxlevel) { maxlevel = level; } @@ -1622,14 +1575,13 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) static void foldAddMarker(buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) { char_u *cms = buf->b_p_cms; - char_u *line; char_u *newline; char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s"); bool line_is_comment = false; linenr_T lnum = pos.lnum; // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end - line = ml_get_buf(buf, lnum, false); + char_u *line = ml_get_buf(buf, lnum, false); size_t line_len = STRLEN(line); size_t added = 0; @@ -1681,14 +1633,12 @@ static void deleteFoldMarkers(win_T *wp, fold_T *fp, int recursive, linenr_T lnu /// close-marker. static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen) { - char_u *newline; - char_u *cms = buf->b_p_cms; - char_u *cms2; - // end marker may be missing and fold extends below the last line if (lnum > buf->b_ml.ml_line_count) { return; } + + char_u *cms = buf->b_p_cms; char_u *line = ml_get_buf(buf, lnum, false); for (char_u *p = line; *p != NUL; p++) { if (STRNCMP(p, marker, markerlen) != 0) { @@ -1697,11 +1647,11 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t mark // Found the marker, include a digit if it's there. size_t len = markerlen; if (ascii_isdigit(p[len])) { - ++len; + len++; } if (*cms != NUL) { // Also delete 'commentstring' if it matches. - cms2 = (char_u *)strstr((char *)cms, "%s"); + char_u *cms2 = (char_u *)strstr((char *)cms, "%s"); if (p - line >= cms2 - cms && STRNCMP(p - (cms2 - cms), cms, cms2 - cms) == 0 && STRNCMP(p + len, cms2 + 2, STRLEN(cms2 + 2)) == 0) { @@ -1711,7 +1661,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t mark } if (u_save(lnum - 1, lnum + 1) == OK) { // Make new line: text-before-marker + text-after-marker - newline = xmalloc(STRLEN(line) - len + 1); + char_u *newline = xmalloc(STRLEN(line) - len + 1); assert(p >= line); memcpy(newline, line, (size_t)(p - line)); STRCPY(newline + (p - line), p + len); @@ -1737,26 +1687,23 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin { char_u *text = NULL; // an error occurred when evaluating 'fdt' setting - static int got_fdt_error = FALSE; + static bool got_fdt_error = false; int save_did_emsg = did_emsg; static win_T *last_wp = NULL; static linenr_T last_lnum = 0; if (last_wp == NULL || last_wp != wp || last_lnum > lnum || last_lnum == 0) { // window changed, try evaluating foldtext setting once again - got_fdt_error = FALSE; + got_fdt_error = false; } if (!got_fdt_error) { // a previous error should not abort evaluating 'foldexpr' - did_emsg = FALSE; + did_emsg = false; } if (*wp->w_p_fdt != NUL) { char dashes[MAX_LEVEL + 2]; - win_T *save_curwin; - int level; - char_u *p; // Set "v:foldstart" and "v:foldend". set_vim_var_nr(VV_FOLDSTART, (varnumber_T)lnum); @@ -1764,7 +1711,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin // Set "v:folddashes" to a string of "level" dashes. // Set "v:foldlevel" to "level". - level = foldinfo.fi_level; + int level = foldinfo.fi_level; if (level > (int)sizeof(dashes) - 1) { level = (int)sizeof(dashes) - 1; } @@ -1775,7 +1722,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin // skip evaluating foldtext on errors if (!got_fdt_error) { - save_curwin = curwin; + win_T *save_curwin = curwin; curwin = wp; curbuf = wp->w_buffer; @@ -1786,7 +1733,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin emsg_silent--; if (text == NULL || did_emsg) { - got_fdt_error = TRUE; + got_fdt_error = true; } curwin = save_curwin; @@ -1803,6 +1750,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin if (text != NULL) { // Replace unprintable characters, if there are any. But // replace a TAB with a space. + char_u *p; for (p = text; *p != NUL; p++) { int len = utfc_ptr2len((char *)p); @@ -1840,11 +1788,6 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin /// Remove 'foldmarker' and 'commentstring' from "str" (in-place). void foldtext_cleanup(char_u *str) { - char_u *s; - char_u *p; - bool did1 = false; - bool did2 = false; - // Ignore leading and trailing white space in 'commentstring'. char_u *cms_start = (char_u *)skipwhite((char *)curbuf->b_p_cms); size_t cms_slen = STRLEN(cms_start); @@ -1865,13 +1808,16 @@ void foldtext_cleanup(char_u *str) } // skip "%s" and white space after it - s = (char_u *)skipwhite((char *)cms_end + 2); + char_u *s = (char_u *)skipwhite((char *)cms_end + 2); cms_elen -= (size_t)(s - cms_end); cms_end = s; } parseMarker(curwin); - for (s = str; *s != NUL;) { + bool did1 = false; + bool did2 = false; + + for (char_u *s = str; *s != NUL;) { size_t len = 0; if (STRNCMP(s, curwin->w_p_fmr, foldstartmarkerlen) == 0) { len = foldstartmarkerlen; @@ -1880,11 +1826,12 @@ void foldtext_cleanup(char_u *str) } if (len > 0) { if (ascii_isdigit(s[len])) { - ++len; + len++; } // May remove 'commentstring' start. Useful when it's a double // quote and we already removed a double quote. + char_u *p; for (p = s; p > str && ascii_iswhite(p[-1]); p--) {} if (p >= str + cms_slen && STRNCMP(p - cms_slen, cms_start, cms_slen) == 0) { @@ -1903,7 +1850,7 @@ void foldtext_cleanup(char_u *str) } if (len != 0) { while (ascii_iswhite(s[len])) { - ++len; + len++; } STRMOVE(s, s + len); } else { @@ -1920,10 +1867,6 @@ void foldtext_cleanup(char_u *str) /// IEMS = "Indent Expr Marker Syntax" static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) { - fline_T fline; - LevelGetter getlevel = NULL; - fold_T *fp; - // Avoid problems when being called recursively. if (invalid_top != (linenr_T)0) { return; @@ -1955,6 +1898,8 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) top = wp->w_buffer->b_ml.ml_line_count; } + fline_T fline; + fold_changed = false; fline.wp = wp; fline.off = 0; @@ -1967,6 +1912,8 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) invalid_top = top; invalid_bot = bot; + LevelGetter getlevel = NULL; + if (foldmethodIsMarker(wp)) { getlevel = foldlevelMarker; @@ -2032,13 +1979,11 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) } } - /* - * If folding is defined by the syntax, it is possible that a change in - * one line will cause all sub-folds of the current fold to change (e.g., - * closing a C-style comment can cause folds in the subsequent lines to - * appear). To take that into account we should adjust the value of "bot" - * to point to the end of the current fold: - */ + // If folding is defined by the syntax, it is possible that a change in + // one line will cause all sub-folds of the current fold to change (e.g., + // closing a C-style comment can cause folds in the subsequent lines to + // appear). To take that into account we should adjust the value of "bot" + // to point to the end of the current fold: if (foldlevelSyntax == getlevel) { garray_T *gap = &wp->w_folds; fold_T *fpn = NULL; @@ -2050,7 +1995,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) if (!foldFind(gap, lnum_rel, &fpn)) { break; } - ++current_fdl; + current_fdl++; fold_start_lnum += fpn->fd_top; gap = &fpn->fd_nested; @@ -2071,6 +2016,9 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) if (start > end && end < wp->w_buffer->b_ml.ml_line_count) { end = start; } + + fold_T *fp; + while (!got_int) { // Always stop at the end of the file ("end" can be past the end of // the file). @@ -2115,7 +2063,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) if (fline.lnum == wp->w_buffer->b_ml.ml_line_count) { break; } - ++fline.lnum; + fline.lnum++; fline.lvl = fline.lvl_next; getlevel(&fline); } @@ -2173,23 +2121,12 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, const linenr_T startlnum, fline_T *const flp, LevelGetter getlevel, linenr_T bot, const char topflags) { - linenr_T ll; fold_T *fp = NULL; - fold_T *fp2; - int lvl = level; - linenr_T startlnum2 = startlnum; - const linenr_T firstlnum = flp->lnum; // first lnum we got - int i; - bool finish = false; - const linenr_T linecount = flp->wp->w_buffer->b_ml.ml_line_count - flp->off; - int concat; - - /* - * If using the marker method, the start line is not the start of a fold - * at the level we're dealing with and the level is non-zero, we must use - * the previous fold. But ignore a fold that starts at or below - * startlnum, it must be deleted. - */ + + // If using the marker method, the start line is not the start of a fold + // at the level we're dealing with and the level is non-zero, we must use + // the previous fold. But ignore a fold that starts at or below + // startlnum, it must be deleted. if (getlevel == foldlevelMarker && flp->start <= flp->lvl - level && flp->lvl > 0) { (void)foldFind(gap, startlnum - 1, &fp); @@ -2200,17 +2137,22 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } } - /* - * Loop over all lines in this fold, or until "bot" is hit. - * Handle nested folds inside of this fold. - * "flp->lnum" is the current line. When finding the end of the fold, it - * is just below the end of the fold. - * "*flp" contains the level of the line "flp->lnum" or a following one if - * there are lines with an invalid fold level. "flp->lnum_save" is the - * line number that was used to get the fold level (below "flp->lnum" when - * it has an invalid fold level). When called the fold level is always - * valid, thus "flp->lnum_save" is equal to "flp->lnum". - */ + fold_T *fp2; + int lvl = level; + linenr_T startlnum2 = startlnum; + const linenr_T firstlnum = flp->lnum; // first lnum we got + bool finish = false; + const linenr_T linecount = flp->wp->w_buffer->b_ml.ml_line_count - flp->off; + + // Loop over all lines in this fold, or until "bot" is hit. + // Handle nested folds inside of this fold. + // "flp->lnum" is the current line. When finding the end of the fold, it + // is just below the end of the fold. + // "*flp" contains the level of the line "flp->lnum" or a following one if + // there are lines with an invalid fold level. "flp->lnum_save" is the + // line number that was used to get the fold level (below "flp->lnum" when + // it has an invalid fold level). When called the fold level is always + // valid, thus "flp->lnum_save" is equal to "flp->lnum". flp->lnum_save = flp->lnum; while (!got_int) { // Updating folds can be slow, check for CTRL-C. @@ -2240,15 +2182,15 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, && getlevel != foldlevelSyntax) { break; } - i = 0; + int i = 0; fp2 = fp; if (lvl >= level) { // Compute how deep the folds currently are, if it's deeper // than "lvl" then some must be deleted, need to update // at least one nested fold. - ll = flp->lnum - fp->fd_top; + int ll = flp->lnum - fp->fd_top; while (foldFind(&fp2->fd_nested, ll, &fp2)) { - ++i; + i++; ll -= fp2->fd_top; } } @@ -2273,13 +2215,12 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, || flp->start != 0 || flp->had_end <= MAX_LEVEL || flp->lnum == linecount)) { - /* - * Remove or update folds that have lines between startlnum and - * firstlnum. - */ + // Remove or update folds that have lines between startlnum and + // firstlnum. while (!got_int) { // set concat to 1 if it's allowed to concatenate this fold // with a previous one that touches it. + int concat; if (flp->start != 0 || flp->had_end <= MAX_LEVEL) { concat = 0; } else { @@ -2347,7 +2288,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } foldRemove(flp->wp, &fp->fd_nested, breakstart - fp->fd_top, breakend - fp->fd_top); - i = (int)(fp - (fold_T *)gap->ga_data); + int i = (int)(fp - (fold_T *)gap->ga_data); foldSplit(flp->wp->w_buffer, gap, i, breakstart, breakend - 1); fp = (fold_T *)gap->ga_data + i + 1; // If using the "marker" or "syntax" method, we @@ -2360,7 +2301,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } } if (fp->fd_top == startlnum && concat) { - i = (int)(fp - (fold_T *)gap->ga_data); + int i = (int)(fp - (fold_T *)gap->ga_data); if (i != 0) { fp2 = fp - 1; if (fp2->fd_top + fp2->fd_len == fp->fd_top) { @@ -2389,6 +2330,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } else { // Insert new fold. Careful: ga_data may be NULL and it // may change! + int i; if (gap->ga_len == 0) { i = 0; } else { @@ -2429,17 +2371,13 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } if (lvl < level || flp->lnum > linecount) { - /* - * Found a line with a lower foldlevel, this fold ends just above - * "flp->lnum". - */ + // Found a line with a lower foldlevel, this fold ends just above + // "flp->lnum". break; } - /* - * The fold includes the line "flp->lnum" and "flp->lnum_save". - * Check "fp" for safety. - */ + // The fold includes the line "flp->lnum" and "flp->lnum_save". + // Check "fp" for safety. if (lvl > level && fp != NULL) { // There is a nested fold, handle it recursively. // At least do one line (can happen when finish is true). @@ -2451,7 +2389,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, // this fold. flp->lnum = flp->lnum_save - fp->fd_top; flp->off += fp->fd_top; - i = (int)(fp - (fold_T *)gap->ga_data); + int i = (int)(fp - (fold_T *)gap->ga_data); bot = foldUpdateIEMSRecurse(&fp->fd_nested, level + 1, startlnum2 - fp->fd_top, flp, getlevel, bot - fp->fd_top, fp->fd_flags); @@ -2464,14 +2402,12 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, // This fold may end at the same line, don't incr. flp->lnum. } else { - /* - * Get the level of the next line, then continue the loop to check - * if it ends there. - * Skip over undefined lines, to find the foldlevel after it. - * For the last line in the file the foldlevel is always valid. - */ + // Get the level of the next line, then continue the loop to check + // if it ends there. + // Skip over undefined lines, to find the foldlevel after it. + // For the last line in the file the foldlevel is always valid. flp->lnum = flp->lnum_save; - ll = flp->lnum + 1; + int ll = flp->lnum + 1; while (!got_int) { // Make the previous level available to foldlevel(). prev_lnum = flp->lnum; @@ -2502,11 +2438,9 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, return bot; } - /* - * Get here when: - * lvl < level: the folds ends just above "flp->lnum" - * lvl >= level: fold continues below "bot" - */ + // Get here when: + // lvl < level: the folds ends just above "flp->lnum" + // lvl >= level: fold continues below "bot" // Current fold at least extends until lnum. if (fp->fd_len < flp->lnum - fp->fd_top) { @@ -2538,7 +2472,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } else { // indent or expr method: split fold to create a new one // below bot - i = (int)(fp - (fold_T *)gap->ga_data); + int i = (int)(fp - (fold_T *)gap->ga_data); foldSplit(flp->wp->w_buffer, gap, i, flp->lnum, bot); fp = (fold_T *)gap->ga_data + i; } @@ -2590,11 +2524,9 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, /// Insert a new fold in "gap" at position "i". static void foldInsert(garray_T *gap, int i) { - fold_T *fp; - ga_grow(gap, 1); - fp = (fold_T *)gap->ga_data + i; + fold_T *fp = (fold_T *)gap->ga_data + i; if (gap->ga_len > 0 && i < gap->ga_len) { memmove(fp + 1, fp, sizeof(fold_T) * (size_t)(gap->ga_len - i)); } @@ -2666,12 +2598,12 @@ static void foldSplit(buf_T *buf, garray_T *const gap, const int i, const linenr /// 6: not changed static void foldRemove(win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot) { - fold_T *fp = NULL; - if (bot < top) { return; // nothing to do } + fold_T *fp = NULL; + while (gap->ga_len > 0) { // Find fold that includes top or a following one. if (foldFind(gap, top, &fp) && fp->fd_top < top) { @@ -2881,7 +2813,6 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) { fold_T *fp3; fold_T *fp4; - int idx; garray_T *gap1 = &fp1->fd_nested; garray_T *gap2 = &fp2->fd_nested; @@ -2894,11 +2825,11 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) // Move nested folds in fp2 to the end of fp1. if (!GA_EMPTY(gap2)) { ga_grow(gap1, gap2->ga_len); - for (idx = 0; idx < gap2->ga_len; ++idx) { + for (int idx = 0; idx < gap2->ga_len; idx++) { ((fold_T *)gap1->ga_data)[gap1->ga_len] = ((fold_T *)gap2->ga_data)[idx]; ((fold_T *)gap1->ga_data)[gap1->ga_len].fd_top += fp1->fd_len; - ++gap1->ga_len; + gap1->ga_len++; } gap2->ga_len = 0; } @@ -2915,12 +2846,10 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) /// @return a level of -1 if the foldlevel depends on surrounding lines. static void foldlevelIndent(fline_T *flp) { - char_u *s; - buf_T *buf; linenr_T lnum = flp->lnum + flp->off; - buf = flp->wp->w_buffer; - s = (char_u *)skipwhite((char *)ml_get_buf(buf, lnum, false)); + buf_T *buf = flp->wp->w_buffer; + char_u *s = (char_u *)skipwhite((char *)ml_get_buf(buf, lnum, false)); // empty line or lines starting with a character in 'foldignore': level // depends on surrounding lines @@ -2958,11 +2887,9 @@ static void foldlevelDiff(fline_T *flp) /// @return a level of -1 if the foldlevel depends on surrounding lines. static void foldlevelExpr(fline_T *flp) { - win_T *win; - int c; linenr_T lnum = flp->lnum + flp->off; - win = curwin; + win_T *win = curwin; curwin = flp->wp; curbuf = flp->wp->w_buffer; set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); @@ -2977,6 +2904,8 @@ static void foldlevelExpr(fline_T *flp) // KeyTyped may be reset to 0 when calling a function which invokes // do_cmdline(). To make 'foldopen' work correctly restore KeyTyped. const bool save_keytyped = KeyTyped; + + int c; const int n = eval_foldexpr((char *)flp->wp->w_p_fde, &c); KeyTyped = save_keytyped; @@ -3070,31 +2999,26 @@ static void parseMarker(win_T *wp) /// Sets flp->start when a start marker was found. static void foldlevelMarker(fline_T *flp) { - char_u *startmarker; - int cstart; - int cend; int start_lvl = flp->lvl; - char_u *s; - int n; // cache a few values for speed - startmarker = flp->wp->w_p_fmr; - cstart = *startmarker; - ++startmarker; - cend = *foldendmarker; + char_u *startmarker = flp->wp->w_p_fmr; + int cstart = *startmarker; + startmarker++; + int cend = *foldendmarker; // Default: no start found, next level is same as current level flp->start = 0; flp->lvl_next = flp->lvl; - s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false); + char_u *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false); while (*s) { if (*s == cstart && STRNCMP(s + 1, startmarker, foldstartmarkerlen - 1) == 0) { // found startmarker: set flp->lvl s += foldstartmarkerlen; if (ascii_isdigit(*s)) { - n = atoi((char *)s); + int n = atoi((char *)s); if (n > 0) { flp->lvl = n; flp->lvl_next = n; @@ -3105,16 +3029,16 @@ static void foldlevelMarker(fline_T *flp) } } } else { - ++flp->lvl; - ++flp->lvl_next; - ++flp->start; + flp->lvl++; + flp->lvl_next++; + flp->start++; } } else if (*s == cend && STRNCMP(s + 1, foldendmarker + 1, foldendmarkerlen - 1) == 0) { // found endmarker: set flp->lvl_next s += foldendmarkerlen; if (ascii_isdigit(*s)) { - n = atoi((char *)s); + int n = atoi((char *)s); if (n > 0) { flp->lvl = n; flp->lvl_next = n - 1; @@ -3143,13 +3067,12 @@ static void foldlevelMarker(fline_T *flp) static void foldlevelSyntax(fline_T *flp) { linenr_T lnum = flp->lnum + flp->off; - int n; // Use the maximum fold level at the start of this line and the next. flp->lvl = syn_get_foldlevel(flp->wp, lnum); flp->start = 0; if (lnum < flp->wp->w_buffer->b_ml.ml_line_count) { - n = syn_get_foldlevel(flp->wp, lnum + 1); + int n = syn_get_foldlevel(flp->wp, lnum + 1); if (n > flp->lvl) { flp->start = n - flp->lvl; // fold(s) start here flp->lvl = n; @@ -3198,7 +3121,7 @@ static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) || put_eol(fd) == FAIL) { return FAIL; } - ++fp; + fp++; } return OK; } @@ -3209,8 +3132,6 @@ static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) /// @return FAIL when writing failed. static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off) { - int level; - fold_T *fp = (fold_T *)gap->ga_data; for (int i = 0; i < gap->ga_len; i++) { if (fp->fd_flags != FD_LEVEL) { @@ -3236,7 +3157,7 @@ static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off // Open or close the leaf according to the window foldlevel. // Do not close a leaf that is already closed, as it will close // the parent. - level = foldLevelWin(wp, off + fp->fd_top); + int level = foldLevelWin(wp, off + fp->fd_top); if ((fp->fd_flags == FD_CLOSED && wp->w_p_fdl >= level) || (fp->fd_flags != FD_CLOSED && wp->w_p_fdl < level)) { if (put_fold_open_close(fd, fp, off) == FAIL) { @@ -3245,7 +3166,7 @@ static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off } } } - ++fp; + fp++; } return OK; diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 0f7052d351..4cf282770d 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -292,7 +292,7 @@ for i = 1, #functions do if fn.check_textlock then output:write('\n if (textlock != 0) {') - output:write('\n api_set_error(error, kErrorTypeException, "%s", e_secure);') + output:write('\n api_set_error(error, kErrorTypeException, "%s", e_textlock);') output:write('\n goto cleanup;') output:write('\n }\n') end @@ -435,7 +435,7 @@ local function process_function(fn) if fn.check_textlock then write_shifted_output(output, [[ if (textlock != 0) { - api_set_error(&err, kErrorTypeException, "%s", e_secure); + api_set_error(&err, kErrorTypeException, "%s", e_textlock); goto exit_0; } ]]) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 6fdc10ae0b..00372d4f3d 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1545,12 +1545,16 @@ int vgetc(void) } break; + case K_KUP: case K_XUP: c = K_UP; break; + case K_KDOWN: case K_XDOWN: c = K_DOWN; break; + case K_KLEFT: case K_XLEFT: c = K_LEFT; break; + case K_KRIGHT: case K_XRIGHT: c = K_RIGHT; break; } @@ -1804,7 +1808,8 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) bool is_plug_map = false; // If typehead starts with <Plug> then remap, even for a "noremap" mapping. - if (typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL + if (typebuf.tb_len >= 3 + && typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL && typebuf.tb_buf[typebuf.tb_off + 1] == KS_EXTRA && typebuf.tb_buf[typebuf.tb_off + 2] == KE_PLUG) { is_plug_map = true; @@ -2470,6 +2475,11 @@ static int vgetorpeek(bool advance) } tc = c; + // return 0 in normal_check() + if (pending_exmode_active) { + exmode_active = true; + } + // no chars to block abbreviations for typebuf.tb_no_abbr_cnt = 0; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index a2862edd6b..8d896aef31 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -224,12 +224,12 @@ EXTERN dict_T vimvardict; // Dictionary with v: variables EXTERN dict_T globvardict; // Dictionary with g: variables /// g: value #define globvarht globvardict.dv_hashtab -EXTERN int did_emsg; // set by emsg() when the message +EXTERN bool did_emsg; // set by emsg() when the message // is displayed or thrown EXTERN bool called_vim_beep; // set if vim_beep() is called EXTERN bool did_emsg_syntax; // did_emsg set because of a // syntax error -EXTERN int called_emsg; // always set by emsg() +EXTERN int called_emsg; // always incremented by emsg() EXTERN int ex_exitval INIT(= 0); // exit value for ex mode EXTERN bool emsg_on_display INIT(= false); // there is an error message EXTERN bool rc_did_emsg INIT(= false); // vim_regcomp() called emsg() @@ -274,11 +274,11 @@ EXTERN except_T *current_exception; /// Set when a throw that cannot be handled in do_cmdline() must be propagated /// to the cstack of the previously called do_cmdline(). -EXTERN int need_rethrow INIT(= false); +EXTERN bool need_rethrow INIT(= false); /// Set when a ":finish" or ":return" that cannot be handled in do_cmdline() /// must be propagated to the cstack of the previously called do_cmdline(). -EXTERN int check_cstack INIT(= false); +EXTERN bool check_cstack INIT(= false); /// Number of nested try conditionals (across function calls and ":source" /// commands). @@ -503,14 +503,13 @@ EXTERN int stdout_isatty INIT(= true); EXTERN int stdin_fd INIT(= -1); // true when doing full-screen output, otherwise only writing some messages. -// volatile because it is used in a signal handler. -EXTERN volatile int full_screen INIT(= false); +EXTERN int full_screen INIT(= false); /// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or /// .vimrc in current directory. EXTERN int secure INIT(= 0); -/// Non-zero when changing text and jumping to another window/buffer is not +/// Non-zero when changing text and jumping to another window or editing another buffer is not /// allowed. EXTERN int textlock INIT(= 0); @@ -637,6 +636,10 @@ EXTERN int motion_force INIT(=0); // motion force for pending operator // Ex Mode (Q) state EXTERN bool exmode_active INIT(= false); // true if Ex mode is active + +/// Flag set when normal_check() should return 0 when entering Ex mode. +EXTERN bool pending_exmode_active INIT(= false); + EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. // 'inccommand' command preview state @@ -724,9 +727,9 @@ EXTERN bool need_highlight_changed INIT(= true); EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. -// volatile because it is used in a signal handler. -EXTERN volatile int got_int INIT(= false); // set to true when interrupt - // signal occurred +// Note that even when handling SIGINT, volatile is not necessary because the +// callback is not called directly from the signal handlers. +EXTERN bool got_int INIT(= false); // set to true when interrupt signal occurred EXTERN bool bangredo INIT(= false); // set to true with ! command EXTERN int searchcmdlen; // length of previous search cmd EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp: @@ -956,6 +959,7 @@ EXTERN char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); EXTERN char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox")); EXTERN char e_secure[] INIT(= N_("E523: Not allowed here")); +EXTERN char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window")); EXTERN char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported")); EXTERN char e_scroll[] INIT(= N_("E49: Invalid scroll size")); EXTERN char e_shellempty[] INIT(= N_("E91: 'shell' option is empty")); @@ -986,6 +990,7 @@ EXTERN char e_notset[] INIT(= N_("E764: Option '%s' is not set")); EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name")); EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\"")); EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior")); +EXTERN char e_menuothermode[] INIT(= N_("E328: Menu only exists in another mode")); EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window")); EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); @@ -1075,4 +1080,6 @@ typedef enum { // Only filled for Win32. EXTERN char windowsVersion[20] INIT(= { 0 }); +EXTERN int exit_need_delay INIT(= 0); + #endif // NVIM_GLOBALS_H diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 7d407bd3d1..1268f987e1 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -241,7 +241,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col u8c = utfc_ptr2char(ptr, u8cc); } mbyte_cells = utf_char2cells(u8c); - if (p_arshape && !p_tbidi && arabic_char(u8c)) { + if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { // Do Arabic shaping. if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) { // Past end of string to be displayed. diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index b96220d547..a4cf65e816 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -342,7 +342,7 @@ static char *parse_list_options(char_u *option_str, option_table_T *table, size_ break; } - table[idx].number = getdigits_int(&p, false, 0); + table[idx].number = getdigits_int((char **)&p, false, 0); } table[idx].string = p; @@ -659,7 +659,8 @@ void ex_hardcopy(exarg_T *eap) */ if (mch_print_init(&settings, curbuf->b_fname == NULL ? (char_u *)buf_spname(curbuf) : curbuf->b_sfname == - NULL ? (char_u *)curbuf->b_fname : curbuf->b_sfname, eap->forceit) == FAIL) { + NULL ? (char_u *)curbuf->b_fname : (char_u *)curbuf->b_sfname, + eap->forceit) == FAIL) { return; } diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index ca6f033c47..95ae7a152c 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -85,9 +85,9 @@ void hash_clear_all(hashtab_T *ht, unsigned int off) /// used for that key. /// WARNING: Returned pointer becomes invalid as soon as the hash table /// is changed in any way. -hashitem_T *hash_find(const hashtab_T *const ht, const char_u *const key) +hashitem_T *hash_find(const hashtab_T *const ht, const char *const key) { - return hash_lookup(ht, (const char *)key, STRLEN(key), hash_hash(key)); + return hash_lookup(ht, key, STRLEN(key), hash_hash((char_u *)key)); } /// Like hash_find, but key is not NUL-terminated diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 8a5b4cbf0f..271498d41a 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -400,7 +400,7 @@ int get_number_indent(linenr_T lnum) // In format_lines() (i.e. not insert mode), fo+=q is needed too... if ((State & MODE_INSERT) || has_format_option(FO_Q_COMS)) { - lead_len = get_leader_len(ml_get(lnum), NULL, false, true); + lead_len = get_leader_len((char *)ml_get(lnum), NULL, false, true); } regmatch.regprog = vim_regcomp((char *)curbuf->b_p_flp, RE_MAGIC); @@ -409,7 +409,7 @@ int get_number_indent(linenr_T lnum) // vim_regexec() expects a pointer to a line. This lets us // start matching for the flp beyond any comment leader... - if (vim_regexec(®match, ml_get(lnum) + lead_len, (colnr_T)0)) { + if (vim_regexec(®match, (char *)ml_get(lnum) + lead_len, (colnr_T)0)) { pos.lnum = lnum; pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum)); pos.coladd = 0; @@ -468,7 +468,7 @@ int get_breakindent_win(win_T *wp, char_u *line) if (regmatch.regprog != NULL) { regmatch.rm_ic = false; - if (vim_regexec(®match, line, 0)) { + if (vim_regexec(®match, (char *)line, 0)) { if (wp->w_briopt_list > 0) { bri += wp->w_briopt_list; } else { @@ -656,6 +656,9 @@ int get_lisp_indent(void) } } } + if (*that == NUL) { + break; + } } if ((*that == '(') || (*that == '[')) { parencount++; @@ -769,7 +772,7 @@ static int lisp_match(char_u *p) char_u *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords; while (*word != NUL) { - (void)copy_option_part(&word, buf, LSIZE, ","); + (void)copy_option_part((char **)&word, (char *)buf, LSIZE, ","); len = (int)STRLEN(buf); if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) { diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index cb807dec24..3c74b4bd8d 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -224,7 +224,7 @@ bool cin_is_cinword(const char_u *line) line = (char_u *)skipwhite((char *)line); for (char_u *cinw = curbuf->b_p_cinw; *cinw;) { - size_t len = copy_option_part(&cinw, cinw_buf, cinw_len, ","); + size_t len = copy_option_part((char **)&cinw, (char *)cinw_buf, cinw_len, ","); if (STRNCMP(line, cinw_buf, len) == 0 && (!vim_iswordc(line[len]) || !vim_iswordc(line[len - 1]))) { retval = true; @@ -520,7 +520,7 @@ bool cin_isscopedecl(const char_u *p) bool found = false; for (char_u *cinsd = curbuf->b_p_cinsd; *cinsd;) { - const size_t len = copy_option_part(&cinsd, cinsd_buf, cinsd_len, ","); + const size_t len = copy_option_part((char **)&cinsd, (char *)cinsd_buf, cinsd_len, ","); if (STRNCMP(s, cinsd_buf, len) == 0) { const char_u *skip = cin_skipcomment(s + len); if (*skip == ':' && skip[1] != ':') { @@ -1501,7 +1501,7 @@ retry: if ((trypos = findmatchlimit(NULL, c, 0, ind_maxp_wk)) != NULL) { // check if the ( is in a // comment if ((colnr_T)cin_skip2pos(trypos) > trypos->col) { - ind_maxp_wk = ind_maxparen - (int)(cursor_save.lnum - trypos->lnum); + ind_maxp_wk = ind_maxparen - (cursor_save.lnum - trypos->lnum); if (ind_maxp_wk > 0) { curwin->w_cursor = *trypos; curwin->w_cursor.col = 0; // XXX @@ -1515,7 +1515,7 @@ retry: trypos = &pos_copy; curwin->w_cursor = *trypos; if ((trypos_wk = ind_find_start_CORS(NULL)) != NULL) { // XXX - ind_maxp_wk = ind_maxparen - (int)(cursor_save.lnum - trypos_wk->lnum); + ind_maxp_wk = ind_maxparen - (cursor_save.lnum - trypos_wk->lnum); if (ind_maxp_wk > 0) { curwin->w_cursor = *trypos_wk; goto retry; @@ -1746,7 +1746,7 @@ void parse_cino(buf_T *buf) p++; } char_u *digits_start = p; // remember where the digits start - int n = getdigits_int(&p, true, 0); + int n = getdigits_int((char **)&p, true, 0); divider = 0; if (*p == '.') { // ".5s" means a fraction. fraction = atoi((char *)++p); @@ -2075,7 +2075,7 @@ int get_c_indent(void) } else if (*p == COM_LEFT || *p == COM_RIGHT) { align = *p++; } else if (ascii_isdigit(*p) || *p == '-') { - off = getdigits_int(&p, true, 0); + off = getdigits_int((char **)&p, true, 0); } else { p++; } @@ -2084,7 +2084,7 @@ int get_c_indent(void) if (*p == ':') { p++; } - (void)copy_option_part(&p, (char_u *)lead_end, COM_MAX_LEN, ","); + (void)copy_option_part((char **)&p, lead_end, COM_MAX_LEN, ","); if (what == COM_START) { STRCPY(lead_start, lead_end); lead_start_len = (int)STRLEN(lead_start); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 164542f4ab..ad03ebd1ed 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -36,6 +36,7 @@ #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" +#include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/undo.h" #include "nvim/version.h" @@ -47,6 +48,8 @@ static int in_fast_callback = 0; // Initialized in nlua_init(). static lua_State *global_lstate = NULL; +static LuaRef require_ref = LUA_REFNIL; + static uv_thread_t main_thread; typedef struct { @@ -645,6 +648,16 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL nlua_common_vim_init(lstate, false); + // patch require() (only for --startuptime) + if (time_fd != NULL) { + lua_getglobal(lstate, "require"); + // Must do this after nlua_common_vim_init where nlua_global_refs is initialized. + require_ref = nlua_ref_global(lstate, -1); + lua_pop(lstate, 1); + lua_pushcfunction(lstate, &nlua_require); + lua_setglobal(lstate, "require"); + } + // internal vim._treesitter... API nlua_add_treesitter(lstate); @@ -740,6 +753,7 @@ void nlua_free_all_mem(void) return; } lua_State *lstate = global_lstate; + nlua_unref_global(lstate, require_ref); nlua_common_free_all_mem(lstate); } @@ -870,6 +884,62 @@ nlua_print_error: return lua_error(lstate); } +/// require() for --startuptime +/// +/// @param lstate Lua interpreter state. +static int nlua_require(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ + const char *name = luaL_checkstring(lstate, 1); + lua_settop(lstate, 1); + // [ name ] + + // try cached module from package.loaded first + lua_getfield(lstate, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(lstate, 2, name); + // [ name package.loaded module ] + if (lua_toboolean(lstate, -1)) { + return 1; + } + lua_pop(lstate, 2); + // [ name ] + + // push original require below the module name + nlua_pushref(lstate, require_ref); + lua_insert(lstate, 1); + // [ require name ] + + if (time_fd == NULL) { + // after log file was closed, try to restore + // global require to the original function... + lua_getglobal(lstate, "require"); + // ...only if it's still referencing this wrapper, + // to not overwrite it in case someone happened to + // patch it in the meantime... + if (lua_iscfunction(lstate, -1) && lua_tocfunction(lstate, -1) == nlua_require) { + lua_pushvalue(lstate, 1); + lua_setglobal(lstate, "require"); + } + lua_pop(lstate, 1); + + // ...and then call require directly. + lua_call(lstate, 1, 1); + return 1; + } + + proftime_T rel_time; + proftime_T start_time; + time_push(&rel_time, &start_time); + int status = lua_pcall(lstate, 1, 1, 0); + if (status == 0) { + vim_snprintf((char *)IObuff, IOSIZE, "require('%s')", name); + time_msg((char *)IObuff, &start_time); + } + time_pop(rel_time); + + return status == 0 ? 1 : lua_error(lstate); +} + /// debug.debug: interaction with user while debugging. /// /// @param lstate Lua interpreter state. @@ -1915,7 +1985,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) // every possible modifier (with room to spare). If the list of possible // modifiers grows this may need to be updated. char buf[200] = { 0 }; - (void)uc_mods(buf); + (void)uc_mods(buf, &cmdmod, false); lua_pushstring(lstate, buf); lua_setfield(lstate, -2, "mods"); @@ -1946,6 +2016,8 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) lua_setfield(lstate, -2, "silent"); lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_ERRSILENT); lua_setfield(lstate, -2, "emsg_silent"); + lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_UNSILENT); + lua_setfield(lstate, -2, "unsilent"); lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SANDBOX); lua_setfield(lstate, -2, "sandbox"); lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_NOAUTOCMD); diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index b911eb8b59..8fde85b163 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -55,7 +55,7 @@ static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) regmatch_T rm; rm.regprog = *prog; rm.rm_ic = false; - bool match = vim_regexec(&rm, str, 0); + bool match = vim_regexec(&rm, (char *)str, 0); *prog = rm.regprog; if (match) { @@ -252,7 +252,7 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL if (offset < 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + offset - 1); + int tail_offset = mb_tail_off(s1, s1 + offset - 1); lua_pushinteger(lstate, tail_offset); return 1; } @@ -300,7 +300,9 @@ int nlua_regex(lua_State *lstate) }); if (ERROR_SET(&err)) { - return luaL_error(lstate, "couldn't parse regex: %s", err.msg); + nlua_push_errstr(lstate, "couldn't parse regex: %s", err.msg); + api_clear_error(&err); + return lua_error(lstate); } assert(prog); @@ -338,12 +340,14 @@ static dict_T *nlua_get_var_scope(lua_State *lstate) dict = tabpage->tp_vars; } } else { - luaL_error(lstate, "invalid scope", err.msg); + luaL_error(lstate, "invalid scope"); return NULL; } if (ERROR_SET(&err)) { - luaL_error(lstate, "FAIL: %s", err.msg); + nlua_push_errstr(lstate, "scoped variable: %s", err.msg); + api_clear_error(&err); + lua_error(lstate); return NULL; } return dict; @@ -537,3 +541,14 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) lua_cjson_new(lstate); lua_setfield(lstate, -2, "json"); } + +/// like luaL_error, but allow cleanup +void nlua_push_errstr(lua_State *L, const char *fmt, ...) +{ + va_list argp; + va_start(argp, fmt); + luaL_where(L, 1); + lua_pushvfstring(L, fmt, argp); + va_end(argp); + lua_concat(L, 2); +} diff --git a/src/nvim/main.c b/src/nvim/main.c index a7e39b7655..b06b9630e2 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -356,7 +356,14 @@ int main(int argc, char **argv) abort(); // unreachable } - init_default_mappings(); // Default mappings. + // Default mappings (incl. menus) + Error err = ERROR_INIT; + Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim._init_default_mappings()"), + (Array)ARRAY_DICT_INIT, &err); + assert(!ERROR_SET(&err)); + api_clear_error(&err); + assert(o.type == kObjectTypeNil); + api_free_object(o); TIME_MSG("init default mappings"); init_default_autocmds(); @@ -802,7 +809,7 @@ static void init_locale(void) char localepath[MAXPATHL] = { 0 }; snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); - char *tail = (char *)path_tail_with_sep((char_u *)localepath); + char *tail = path_tail_with_sep(localepath); *tail = NUL; tail = path_tail(localepath); xstrlcpy(tail, "share/locale", @@ -1354,11 +1361,11 @@ scripterror: // Add the file to the global argument list. ga_grow(&global_alist.al_ga, 1); - char_u *p = vim_strsave((char_u *)argv[0]); + char *p = xstrdup(argv[0]); - if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0 + if (parmp->diff_mode && os_isdir((char_u *)p) && GARGCOUNT > 0 && !os_isdir((char_u *)alist_name(&GARGLIST[0]))) { - char_u *r = (char_u *)concat_fnames((char *)p, path_tail(alist_name(&GARGLIST[0])), true); + char *r = concat_fnames(p, path_tail(alist_name(&GARGLIST[0])), true); xfree(p); p = r; } @@ -1371,7 +1378,7 @@ scripterror: int alist_fnum_flag = edit_stdin(had_stdin_file, parmp) ? 1 // add buffer nr after exp. : 2; // add buffer number now and use curbuf - alist_add(&global_alist, (char *)p, alist_fnum_flag); + alist_add(&global_alist, p, alist_fnum_flag); } // If there are no more letters after the current "-", go to next argument. diff --git a/src/nvim/map.c b/src/nvim/map.c index d27e40b4ee..d3058a5d52 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -30,6 +30,8 @@ #define int_eq kh_int_hash_equal #define handle_T_hash kh_int_hash_func #define handle_T_eq kh_int_hash_equal +#define KittyKey_hash kh_int_hash_func +#define KittyKey_eq kh_int_hash_equal #if defined(ARCH_64) # define ptr_t_hash(key) uint64_t_hash((uint64_t)(key)) @@ -162,6 +164,7 @@ static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2) } MAP_IMPL(int, int, DEFAULT_INITIALIZER) +MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, int, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) @@ -177,6 +180,8 @@ MAP_IMPL(int, String, DEFAULT_INITIALIZER) MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) +MAP_IMPL(KittyKey, cstr_t, DEFAULT_INITIALIZER) + /// Deletes a key:value pair from a string:pointer map, and frees the /// storage of both key and value. /// diff --git a/src/nvim/map.h b/src/nvim/map.h index 4f4aaa3552..845daac3f7 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -7,6 +7,7 @@ #include "nvim/extmark_defs.h" #include "nvim/highlight_defs.h" #include "nvim/map_defs.h" +#include "nvim/tui/input_defs.h" #include "nvim/ui_client.h" #if defined(__NetBSD__) @@ -34,6 +35,7 @@ // NOTE: Keys AND values must be allocated! khash.h does not make a copy. // MAP_DECLS(int, int) +MAP_DECLS(int, cstr_t) MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(cstr_t, int) MAP_DECLS(ptr_t, ptr_t) @@ -50,6 +52,8 @@ MAP_DECLS(int, String) MAP_DECLS(ColorKey, ColorItem) +MAP_DECLS(KittyKey, cstr_t) + #define MAP_INIT { { 0, 0, 0, 0, NULL, NULL, NULL } } #define map_init(k, v, map) do { *(map) = (Map(k, v)) MAP_INIT; } while (false) diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index ff7a3da5c3..5a11ac686e 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -1266,7 +1266,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) continue; } - if (vim_regexec(regmatch, p, (colnr_T)0)) { + if (vim_regexec(regmatch, (char *)p, (colnr_T)0)) { if (round == 1) { count++; } else { @@ -1289,7 +1289,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) for (; mp; mp = mp->m_next) { if (mp->m_mode & expand_mapmodes) { p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS); - if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) { + if (p != NULL && vim_regexec(regmatch, (char *)p, (colnr_T)0)) { if (round == 1) { count++; } else { @@ -2113,20 +2113,6 @@ void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_maparg(argvars, rettv, false); } -void init_default_mappings(void) -{ - add_map("Y", "y$", MODE_NORMAL, false); - - // Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O> - // See https://github.com/neovim/neovim/issues/17473 - add_map("<C-L>", "<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>", - MODE_NORMAL, false); - add_map("<C-U>", "<C-G>u<C-U>", MODE_INSERT, false); - add_map("<C-W>", "<C-G>u<C-W>", MODE_INSERT, false); - add_map("*", "y/\\\\V<C-R>\"<CR>", MODE_VISUAL, false); - add_map("#", "y?\\\\V<C-R>\"<CR>", MODE_VISUAL, false); -} - /// Add a mapping. Unlike @ref do_map this copies the string arguments, so /// static or read-only strings can be used. /// diff --git a/src/nvim/mark.c b/src/nvim/mark.c index a0fed19a98..66855c66b5 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -13,7 +13,9 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" +#include "nvim/cursor.h" #include "nvim/diff.h" +#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" @@ -24,6 +26,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -61,7 +64,8 @@ static xfmark_T namedfm[NGLOBALMARKS]; */ int setmark(int c) { - return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum); + fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor); + return setmark_pos(c, &curwin->w_cursor, curbuf->b_fnum, &view); } /// Free fmark_T item @@ -90,9 +94,10 @@ void clear_fmark(fmark_T *fm) * When "c" is upper case use file "fnum". * Returns OK on success, FAIL if bad name given. */ -int setmark_pos(int c, pos_T *pos, int fnum) +int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt) { int i; + fmarkv_T view = view_pt != NULL ? *view_pt : (fmarkv_T)INIT_FMARKV; // Check for a special key (may cause islower() to crash). if (c < 0) { @@ -117,7 +122,7 @@ int setmark_pos(int c, pos_T *pos, int fnum) } if (c == '"') { - RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum); + RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum, view); return OK; } @@ -147,7 +152,7 @@ int setmark_pos(int c, pos_T *pos, int fnum) if (ASCII_ISLOWER(c)) { i = c - 'a'; - RESET_FMARK(buf->b_namedm + i, *pos, fnum); + RESET_FMARK(buf->b_namedm + i, *pos, fnum, view); return OK; } if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { @@ -156,7 +161,7 @@ int setmark_pos(int c, pos_T *pos, int fnum) } else { i = c - 'A'; } - RESET_XFMARK(namedfm + i, *pos, fnum, NULL); + RESET_XFMARK(namedfm + i, *pos, fnum, view, NULL); return OK; } return FAIL; @@ -202,7 +207,8 @@ void setpcmark(void) curwin->w_jumplistidx = curwin->w_jumplistlen; fm = &curwin->w_jumplist[curwin->w_jumplistlen - 1]; - SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, NULL); + fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_pcmark); + SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, view, NULL); } /* @@ -221,245 +227,407 @@ void checkpcmark(void) curwin->w_prev_pcmark.lnum = 0; // it has been checked } -/* - * move "count" positions in the jump list (count may be negative) - */ -pos_T *movemark(int count) +/// Get mark in "count" position in the |jumplist| relative to the current index. +/// +/// If the mark is in a different buffer, it will be skipped unless the buffer exists. +/// +/// @note cleanup_jumplist() is run, which removes duplicate marks, and +/// changes win->w_jumplistidx. +/// @param[in] win window to get jumplist from. +/// @param[in] count count to move may be negative. +/// +/// @return mark, NULL if out of jumplist bounds. +fmark_T *get_jumplist(win_T *win, int count) { - pos_T *pos; - xfmark_T *jmp; + xfmark_T *jmp = NULL; - cleanup_jumplist(curwin, true); + cleanup_jumplist(win, true); - if (curwin->w_jumplistlen == 0) { // nothing to jump to - return (pos_T *)NULL; + if (win->w_jumplistlen == 0) { // nothing to jump to + return NULL; } for (;;) { - if (curwin->w_jumplistidx + count < 0 - || curwin->w_jumplistidx + count >= curwin->w_jumplistlen) { - return (pos_T *)NULL; + if (win->w_jumplistidx + count < 0 + || win->w_jumplistidx + count >= win->w_jumplistlen) { + return NULL; } - /* - * if first CTRL-O or CTRL-I command after a jump, add cursor position - * to list. Careful: If there are duplicates (CTRL-O immediately after - * starting Vim on a file), another entry may have been removed. - */ - if (curwin->w_jumplistidx == curwin->w_jumplistlen) { + // if first CTRL-O or CTRL-I command after a jump, add cursor position + // to list. Careful: If there are duplicates (CTRL-O immediately after + // starting Vim on a file), another entry may have been removed. + if (win->w_jumplistidx == win->w_jumplistlen) { setpcmark(); - --curwin->w_jumplistidx; // skip the new entry - if (curwin->w_jumplistidx + count < 0) { - return (pos_T *)NULL; + win->w_jumplistidx--; // skip the new entry + if (win->w_jumplistidx + count < 0) { + return NULL; } } - curwin->w_jumplistidx += count; + win->w_jumplistidx += count; - jmp = curwin->w_jumplist + curwin->w_jumplistidx; + jmp = win->w_jumplist + win->w_jumplistidx; if (jmp->fmark.fnum == 0) { + // Resolve the fnum (buff number) in the mark before returning it (shada) fname2fnum(jmp); } if (jmp->fmark.fnum != curbuf->b_fnum) { - // jump to other file - if (buflist_findnr(jmp->fmark.fnum) == NULL) { // Skip this one .. + // Needs to switch buffer, if it can't find it skip the mark + if (buflist_findnr(jmp->fmark.fnum) == NULL) { count += count < 0 ? -1 : 1; continue; } - if (buflist_getfile(jmp->fmark.fnum, jmp->fmark.mark.lnum, - 0, FALSE) == FAIL) { - return (pos_T *)NULL; - } - // Set lnum again, autocommands my have changed it - curwin->w_cursor = jmp->fmark.mark; - pos = (pos_T *)-1; - } else { - pos = &(jmp->fmark.mark); } - return pos; + break; } + return &jmp->fmark; } -/* - * Move "count" positions in the changelist (count may be negative). - */ -pos_T *movechangelist(int count) +/// Get mark in "count" position in the |changelist| relative to the current index. +/// +/// @note Changes the win->w_changelistidx. +/// @param[in] win window to get jumplist from. +/// @param[in] count count to move may be negative. +/// +/// @return mark, NULL if out of bounds. +fmark_T *get_changelist(buf_T *buf, win_T *win, int count) { int n; + fmark_T *fm; - if (curbuf->b_changelistlen == 0) { // nothing to jump to - return (pos_T *)NULL; + if (buf->b_changelistlen == 0) { // nothing to jump to + return NULL; } - n = curwin->w_changelistidx; + n = win->w_changelistidx; if (n + count < 0) { if (n == 0) { - return (pos_T *)NULL; + return NULL; } n = 0; - } else if (n + count >= curbuf->b_changelistlen) { - if (n == curbuf->b_changelistlen - 1) { - return (pos_T *)NULL; + } else if (n + count >= buf->b_changelistlen) { + if (n == buf->b_changelistlen - 1) { + return NULL; } - n = curbuf->b_changelistlen - 1; + n = buf->b_changelistlen - 1; } else { n += count; } - curwin->w_changelistidx = n; - return &(curbuf->b_changelist[n].mark); + win->w_changelistidx = n; + fm = &(buf->b_changelist[n]); + // Changelist marks are always buffer local, Shada does not set it when loading + fm->fnum = curbuf->handle; + return &(buf->b_changelist[n]); } -/* - * Find mark "c" in buffer pointed to by "buf". - * If "changefile" is TRUE it's allowed to edit another file for '0, 'A, etc. - * If "fnum" is not NULL store the fnum there for '0, 'A etc., don't edit - * another file. - * Returns: - * - pointer to pos_T if found. lnum is 0 when mark not set, -1 when mark is - * in another file which can't be gotten. (caller needs to check lnum!) - * - NULL if there is no mark called 'c'. - * - -1 if mark is in other file and jumped there (only if changefile is TRUE) - */ -pos_T *getmark_buf(buf_T *buf, int c, bool changefile) +/// Get a named mark. +/// +/// All types of marks, even those that are not technically a mark will be returned as such. Use +/// mark_move_to() to move to the mark. +/// @note Some of the pointers are statically allocated, if in doubt make a copy. For more +/// information read mark_get_local(). +/// @param buf Buffer to get the mark from. +/// @param win Window to get or calculate the mark from (motion type marks, context mark). +/// @param fmp[out] Optional pointer to store the result in, as a workaround for the note above. +/// @param flag MarkGet value +/// @param name Name of the mark. +/// +/// @return Mark if found, otherwise NULL. For @c kMarkBufLocal, NULL is returned +/// when no mark is found in @a buf. +fmark_T *mark_get(buf_T *buf, win_T *win, fmark_T *fmp, MarkGet flag, int name) { - return getmark_buf_fnum(buf, c, changefile, NULL); + fmark_T *fm = NULL; + if (ASCII_ISUPPER(name) || ascii_isdigit(name)) { + // Global marks + xfmark_T *xfm = mark_get_global(!(flag & kMarkAllNoResolve), name); + fm = &xfm->fmark; + // Only wanted marks belonging to the buffer + if ((flag & kMarkBufLocal) && xfm->fmark.fnum != buf->handle) { + return NULL; + } + } else if (name > 0 && name < NMARK_LOCAL_MAX) { + // Local Marks + fm = mark_get_local(buf, win, name); + } + if (fmp != NULL && fm != NULL) { + *fmp = *fm; + return fmp; + } + return fm; } -pos_T *getmark(int c, bool changefile) +/// Get a global mark {A-Z0-9}. +/// +/// @param name the name of the mark. +/// @param resolve Whether to try resolving the mark fnum (i.e., load the buffer stored in +/// the mark fname and update the xfmark_T (expensive)). +/// +/// @return Mark +xfmark_T *mark_get_global(bool resolve, int name) { - return getmark_buf_fnum(curbuf, c, changefile, NULL); + xfmark_T *mark; + + if (ascii_isdigit(name)) { + name = name - '0' + NMARKS; + } else if (ASCII_ISUPPER(name)) { + name -= 'A'; + } else { + // Not a valid mark name + assert(false); + } + mark = &namedfm[name]; + + if (resolve && mark->fmark.fnum == 0) { + // Resolve filename to fnum (SHADA marks) + fname2fnum(mark); + } + return mark; } -pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum) +/// Get a local mark (lowercase and symbols). +/// +/// Some marks are not actually marks, but positions that are never adjusted or motions presented as +/// marks. Search first for marks and fallback to finding motion type marks. If it's known +/// ahead of time that the mark is actually a motion use the mark_get_motion() directly. +/// +/// @note Lowercase, last_cursor '"', last insert '^', last change '.' are not statically +/// allocated, everything else is. +/// @param name the name of the mark. +/// @param win window to retrieve marks that belong to it (motions and context mark). +/// @param buf buf to retrieve marks that belong to it. +/// +/// @return Mark, NULL if not found. +fmark_T *mark_get_local(buf_T *buf, win_T *win, int name) { - pos_T *posp; - pos_T *startp, *endp; - static pos_T pos_copy; + fmark_T *mark = NULL; + if (ASCII_ISLOWER(name)) { + // normal named mark + mark = &buf->b_namedm[name - 'a']; + // to start of previous operator + } else if (name == '[') { + mark = pos_to_mark(buf, NULL, buf->b_op_start); + // to end of previous operator + } else if (name == ']') { + mark = pos_to_mark(buf, NULL, buf->b_op_end); + // visual marks + } else if (name == '<' || name == '>') { + mark = mark_get_visual(buf, name); + // previous context mark + } else if (name == '\'' || name == '`') { + // TODO(muniter): w_pcmark should be stored as a mark, but causes a nasty bug. + mark = pos_to_mark(curbuf, NULL, win->w_pcmark); + // to position when leaving buffer + } else if (name == '"') { + mark = &(buf->b_last_cursor); + // to where last Insert mode stopped + } else if (name == '^') { + mark = &(buf->b_last_insert); + // to where last change was made + } else if (name == '.') { + mark = &buf->b_last_change; + // Mark that are actually not marks but motions, e.g {, }, (, ), ... + } else { + mark = mark_get_motion(buf, win, name); + } - posp = NULL; + if (mark) { + mark->fnum = buf->b_fnum; + } - // Check for special key, can't be a mark name and might cause islower() - // to crash. - if (c < 0) { - return posp; - } - if (c > '~') { // check for islower()/isupper() - } else if (c == '\'' || c == '`') { // previous context mark - pos_copy = curwin->w_pcmark; // need to make a copy because - posp = &pos_copy; // w_pcmark may be changed soon - } else if (c == '"') { // to pos when leaving buffer - posp = &(buf->b_last_cursor.mark); - } else if (c == '^') { // to where Insert mode stopped - posp = &(buf->b_last_insert.mark); - } else if (c == '.') { // to where last change was made - posp = &(buf->b_last_change.mark); - } else if (c == '[') { // to start of previous operator - posp = &(buf->b_op_start); - } else if (c == ']') { // to end of previous operator - posp = &(buf->b_op_end); - } else if (c == '{' || c == '}') { // to previous/next paragraph - pos_T pos; + return mark; +} + +/// Get marks that are actually motions but return them as marks +/// +/// Gets the following motions as marks: '{', '}', '(', ')' +/// @param name name of the mark +/// @param win window to retrieve the cursor to calculate the mark. +/// @param buf buf to wrap motion marks with it's buffer number (fm->fnum). +/// +/// @return[static] Mark. +fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name) +{ + fmark_T *mark = NULL; + const pos_T pos = curwin->w_cursor; + const bool slcb = listcmd_busy; + listcmd_busy = true; // avoid that '' is changed + if (name == '{' || name == '}') { // to previous/next paragraph oparg_T oa; - bool slcb = listcmd_busy; - - pos = curwin->w_cursor; - listcmd_busy = true; // avoid that '' is changed - if (findpar(&oa.inclusive, - c == '}' ? FORWARD : BACKWARD, 1L, NUL, FALSE)) { - pos_copy = curwin->w_cursor; - posp = &pos_copy; + if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) { + mark = pos_to_mark(buf, NULL, win->w_cursor); } - curwin->w_cursor = pos; - listcmd_busy = slcb; - } else if (c == '(' || c == ')') { // to previous/next sentence - pos_T pos; - bool slcb = listcmd_busy; - - pos = curwin->w_cursor; - listcmd_busy = true; // avoid that '' is changed - if (findsent(c == ')' ? FORWARD : BACKWARD, 1L)) { - pos_copy = curwin->w_cursor; - posp = &pos_copy; + } else if (name == '(' || name == ')') { // to previous/next sentence + if (findsent(name == ')' ? FORWARD : BACKWARD, 1L)) { + mark = pos_to_mark(buf, NULL, win->w_cursor); } - curwin->w_cursor = pos; - listcmd_busy = slcb; - } else if (c == '<' || c == '>') { // start/end of visual area - startp = &buf->b_visual.vi_start; - endp = &buf->b_visual.vi_end; - if (((c == '<') == lt(*startp, *endp) || endp->lnum == 0) - && startp->lnum != 0) { - posp = startp; + } + curwin->w_cursor = pos; + listcmd_busy = slcb; + return mark; +} + +/// Get visual marks '<', '>' +/// +/// This marks are different to normal marks: +/// 1. Never adjusted. +/// 2. Different behavior depending on editor state (visual mode). +/// 3. Not saved in shada. +/// 4. Re-ordered when defined in reverse. +/// @param buf Buffer to get the mark from. +/// @param name Mark name '<' or '>'. +/// +/// @return[static] Mark +fmark_T *mark_get_visual(buf_T *buf, int name) +{ + fmark_T *mark = NULL; + if (name == '<' || name == '>') { + // start/end of visual area + pos_T startp = buf->b_visual.vi_start; + pos_T endp = buf->b_visual.vi_end; + if (((name == '<') == lt(startp, endp) || endp.lnum == 0) + && startp.lnum != 0) { + mark = pos_to_mark(buf, NULL, startp); } else { - posp = endp; + mark = pos_to_mark(buf, NULL, endp); } - // For Visual line mode, set mark at begin or end of line - if (buf->b_visual.vi_mode == 'V') { - pos_copy = *posp; - posp = &pos_copy; - if (c == '<') { - pos_copy.col = 0; + if (mark != NULL && buf->b_visual.vi_mode == 'V') { + if (name == '<') { + mark->mark.col = 0; } else { - pos_copy.col = MAXCOL; + mark->mark.col = MAXCOL; } - pos_copy.coladd = 0; - } - } else if (ASCII_ISLOWER(c)) { // normal named mark - posp = &(buf->b_namedm[c - 'a'].mark); - } else if (ASCII_ISUPPER(c) || ascii_isdigit(c)) { // named file mark - if (ascii_isdigit(c)) { - c = c - '0' + NMARKS; - } else { - c -= 'A'; + mark->mark.coladd = 0; } - posp = &(namedfm[c].fmark.mark); + } + return mark; +} + +/// Wrap a pos_T into an fmark_T, used to abstract marks handling. +/// +/// Pass an fmp if multiple c +/// @note view fields are set to 0. +/// @param buf for fmark->fnum. +/// @param pos for fmrak->mark. +/// @param fmp pointer to save the mark. +/// +/// @return[static] Mark with the given information. +fmark_T *pos_to_mark(buf_T *buf, fmark_T *fmp, pos_T pos) +{ + static fmark_T fms = INIT_FMARK; + fmark_T *fm = fmp == NULL ? &fms : fmp; + fm->fnum = buf->handle; + fm->mark = pos; + return fm; +} + +/// Attempt to switch to the buffer of the given global mark +/// +/// @param fm +/// @param pcmark_on_switch leave a context mark when switching buffer. +/// @return whether the buffer was switched or not. +static MarkMoveRes switch_to_mark_buf(fmark_T *fm, bool pcmark_on_switch) +{ + bool res; + if (fm->fnum != curbuf->b_fnum) { + // Switch to another file. + int getfile_flag = pcmark_on_switch ? GETF_SETMARK : 0; + res = buflist_getfile(fm->fnum, (linenr_T)1, getfile_flag, false) == OK; + return res == true ? kMarkSwitchedBuf : kMarkMoveFailed; + } + return 0; +} - if (namedfm[c].fmark.fnum == 0) { - fname2fnum(&namedfm[c]); +/// Move to the given file mark, changing the buffer and cursor position. +/// +/// Validate the mark, switch to the buffer, and move the cursor. +/// @param fm Mark, can be NULL will raise E78: Unknown mark +/// @param flags MarkMove flags to configure the movement to the mark. +/// +/// @return MarkMovekRes flags representing the outcome +MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) +{ + static fmark_T fm_copy = INIT_FMARK; + MarkMoveRes res = kMarkMoveSuccess; + if (!mark_check(fm)) { + res = kMarkMoveFailed; + goto end; + } + + if (fm->fnum != curbuf->handle) { + // Need to change buffer + fm_copy = *fm; // Copy, autocommand may change it + fm = &fm_copy; + res |= switch_to_mark_buf(fm, !(flags & kMarkJumpList)); + // Failed switching buffer + if (res & kMarkMoveFailed) { + goto end; + } + // Check line count now that the **destination buffer is loaded**. + if (!mark_check_line_bounds(curbuf, fm)) { + res |= kMarkMoveFailed; + goto end; } + } else if (flags & kMarkContext) { + // Doing it in this condition avoids double context mark when switching buffer. + setpcmark(); + } + // Move the cursor while keeping track of what changed for the caller + pos_T prev_pos = curwin->w_cursor; + pos_T pos = fm->mark; + curwin->w_cursor = fm->mark; + if (flags & kMarkBeginLine) { + beginline(BL_WHITE | BL_FIX); + } + res = prev_pos.lnum != pos.lnum ? res | kMarkChangedLine | kMarkChangedCursor : res; + res = prev_pos.col != pos.col ? res | kMarkChangedCol | kMarkChangedCursor : res; + if (flags & kMarkSetView) { + mark_view_restore(fm); + } - if (fnum != NULL) { - *fnum = namedfm[c].fmark.fnum; - } else if (namedfm[c].fmark.fnum != buf->b_fnum) { - // mark is in another file - posp = &pos_copy; - - if (namedfm[c].fmark.mark.lnum != 0 - && changefile && namedfm[c].fmark.fnum) { - if (buflist_getfile(namedfm[c].fmark.fnum, - (linenr_T)1, GETF_SETMARK, FALSE) == OK) { - // Set the lnum now, autocommands could have changed it - curwin->w_cursor = namedfm[c].fmark.mark; - return (pos_T *)-1; - } - pos_copy.lnum = -1; // can't get file - } else { - pos_copy.lnum = 0; // mark exists, but is not valid in current buffer - } + if (res & kMarkSwitchedBuf || res & kMarkChangedCursor) { + check_cursor(); + } +end: + return res; +} + +/// Restore the mark view. +/// By remembering the offset between topline and mark lnum at the time of +/// definition, this function restores the "view". +/// @note Assumes the mark has been checked, is valid. +/// @param fm the named mark. +void mark_view_restore(fmark_T *fm) +{ + if (fm != NULL && fm->view.topline_offset >= 0) { + linenr_T topline = fm->mark.lnum - fm->view.topline_offset; + // If the mark does not have a view, topline_offset is MAXLNUM, + // and this check can prevent restoring mark view in that case. + if (topline >= 1) { + set_topline(curwin, topline); } } +} - return posp; +fmarkv_T mark_view_make(linenr_T topline, pos_T pos) +{ + return (fmarkv_T){ pos.lnum - topline }; } -/// Search for the next named mark in the current file. +/// Search for the next named mark in the current file from a start position. /// -/// @param startpos where to start -/// @param dir direction for search +/// @param startpos where to start. +/// @param dir direction for search. /// -/// @return pointer to pos_T of the next mark or NULL if no mark is found. -pos_T *getnextmark(pos_T *startpos, int dir, int begin_line) +/// @return next mark or NULL if no mark is found. +fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line) { int i; - pos_T *result = NULL; + fmark_T *result = NULL; pos_T pos; pos = *startpos; - // When searching backward and leaving the cursor on the first non-blank, - // position must be in a previous line. - // When searching forward and leaving the cursor on the first non-blank, - // position must be in a next line. if (dir == BACKWARD && begin_line) { pos.col = 0; } else if (dir == FORWARD && begin_line) { @@ -469,14 +637,14 @@ pos_T *getnextmark(pos_T *startpos, int dir, int begin_line) for (i = 0; i < NMARKS; i++) { if (curbuf->b_namedm[i].mark.lnum > 0) { if (dir == FORWARD) { - if ((result == NULL || lt(curbuf->b_namedm[i].mark, *result)) + if ((result == NULL || lt(curbuf->b_namedm[i].mark, result->mark)) && lt(pos, curbuf->b_namedm[i].mark)) { - result = &curbuf->b_namedm[i].mark; + result = &curbuf->b_namedm[i]; } } else { - if ((result == NULL || lt(*result, curbuf->b_namedm[i].mark)) + if ((result == NULL || lt(result->mark, curbuf->b_namedm[i].mark)) && lt(curbuf->b_namedm[i].mark, pos)) { - result = &curbuf->b_namedm[i].mark; + result = &curbuf->b_namedm[i]; } } } @@ -518,7 +686,7 @@ static void fname2fnum(xfmark_T *fm) p = path_shorten_fname(NameBuff, IObuff); // buflist_new() will call fmarks_check_names() - (void)buflist_new(NameBuff, p, (linenr_T)1, 0); + (void)buflist_new((char *)NameBuff, (char *)p, (linenr_T)1, 0); } } @@ -529,7 +697,7 @@ static void fname2fnum(xfmark_T *fm) */ void fmarks_check_names(buf_T *buf) { - char_u *name = buf->b_ffname; + char_u *name = (char_u *)buf->b_ffname; int i; if (buf->b_ffname == NULL) { @@ -557,29 +725,48 @@ static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf) } } -/* - * Check a if a position from a mark is valid. - * Give and error message and return FAIL if not. - */ -int check_mark(pos_T *pos) +/// Check the position in @a fm is valid. +/// +/// Emit error message and return accordingly. +/// +/// Checks for: +/// - NULL raising unknown mark error. +/// - Line number <= 0 raising mark not set. +/// - Line number > buffer line count, raising invalid mark. +/// @param fm[in] File mark to check. +/// +/// @return true if the mark passes all the above checks, else false. +bool mark_check(fmark_T *fm) { - if (pos == NULL) { + if (fm == NULL) { emsg(_(e_umark)); - return FAIL; - } - if (pos->lnum <= 0) { - // lnum is negative if mark is in another file can can't get that - // file, error message already give then. - if (pos->lnum == 0) { + return false; + } else if (fm->mark.lnum <= 0) { + // In both cases it's an error but only raise when equals to 0 + if (fm->mark.lnum == 0) { emsg(_(e_marknotset)); } - return FAIL; + return false; } - if (pos->lnum > curbuf->b_ml.ml_line_count) { + // Only check for valid line number if the buffer is loaded. + if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm)) { + return false; + } + return true; +} + +/// Check if a mark line number is greater than the buffer line count, and set e_markinval. +/// @note Should be done after the buffer is loaded into memory. +/// @param buf Buffer where the mark is set. +/// @param fm Mark to check. +/// @return true if below line count else false. +bool mark_check_line_bounds(buf_T *buf, fmark_T *fm) +{ + if (buf != NULL && fm->mark.lnum > buf->b_ml.ml_line_count) { emsg(_(e_markinval)); - return FAIL; + return false; } - return OK; + return true; } /// Clear all marks and change list in the given buffer @@ -615,7 +802,7 @@ char_u *fm_getname(fmark_T *fmark, int lead_len) if (fmark->fnum == curbuf->b_fnum) { // current buffer return mark_line(&(fmark->mark), lead_len); } - return buflist_nr2name(fmark->fnum, FALSE, TRUE); + return (char_u *)buflist_nr2name(fmark->fnum, false, true); } /* @@ -1318,7 +1505,7 @@ void copy_jumplist(win_T *from, win_T *to) /// Iterate over jumplist items /// -/// @warning No jumplist-editing functions must be run while iteration is in +/// @warning No jumplist-editing functions must be called while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. @@ -1331,7 +1518,7 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, x FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { if (iter == NULL && win->w_jumplistlen == 0) { - *fm = (xfmark_T) { { { 0, 0, 0 }, 0, 0, NULL }, NULL }; + *fm = (xfmark_T)INIT_XFMARK; return NULL; } const xfmark_T *const iter_mark = @@ -1348,7 +1535,7 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, x /// Iterate over global marks /// -/// @warning No mark-editing functions must be run while iteration is in +/// @warning No mark-editing functions must be called while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. @@ -1422,7 +1609,7 @@ static inline const fmark_T *next_buffer_mark(const buf_T *const buf, char *cons /// Iterate over buffer marks /// -/// @warning No mark-editing functions must be run while iteration is in +/// @warning No mark-editing functions must be called while iteration is in /// progress. /// /// @param[in] iter Iterator. Pass NULL to start iteration. @@ -1539,7 +1726,7 @@ void free_jumplist(win_T *wp) void set_last_cursor(win_T *win) { if (win->w_buffer != NULL) { - RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0); + RESET_FMARK(&win->w_buffer->b_last_cursor, win->w_cursor, 0, ((fmarkv_T)INIT_FMARKV)); } } @@ -1640,9 +1827,10 @@ void get_buf_local_marks(const buf_T *buf, list_T *l) /// Get a global mark /// +/// @note Mark might not have it's fnum resolved. /// @param[in] Name of named mark /// @param[out] Global/file mark -xfmark_T get_global_mark(char name) +xfmark_T get_raw_global_mark(char name) { return namedfm[mark_global_index(name)]; } @@ -1659,7 +1847,7 @@ void get_global_marks(list_T *l) // Marks 'A' to 'Z' and '0' to '9' for (int i = 0; i < NMARKS + EXTRA_MARKS; i++) { if (namedfm[i].fmark.fnum != 0) { - name = (char *)buflist_nr2name(namedfm[i].fmark.fnum, true, true); + name = buflist_nr2name(namedfm[i].fmark.fnum, true, true); } else { name = namedfm[i].fname; } diff --git a/src/nvim/mark.h b/src/nvim/mark.h index a55f733d9a..6da976e8d3 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -13,42 +13,43 @@ #include "nvim/pos.h" /// Set fmark using given value -#define SET_FMARK(fmarkp_, mark_, fnum_) \ +#define SET_FMARK(fmarkp_, mark_, fnum_, view_) \ do { \ fmark_T *const fmarkp__ = fmarkp_; \ fmarkp__->mark = mark_; \ fmarkp__->fnum = fnum_; \ fmarkp__->timestamp = os_time(); \ + fmarkp__->view = view_; \ fmarkp__->additional_data = NULL; \ } while (0) /// Free and set fmark using given value -#define RESET_FMARK(fmarkp_, mark_, fnum_) \ +#define RESET_FMARK(fmarkp_, mark_, fnum_, view_) \ do { \ fmark_T *const fmarkp___ = fmarkp_; \ free_fmark(*fmarkp___); \ - SET_FMARK(fmarkp___, mark_, fnum_); \ + SET_FMARK(fmarkp___, mark_, fnum_, view_); \ } while (0) /// Clear given fmark #define CLEAR_FMARK(fmarkp_) \ - RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0) + RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0, ((fmarkv_T) { 0 })) /// Set given extended mark (regular mark + file name) -#define SET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \ +#define SET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \ do { \ xfmark_T *const xfmarkp__ = xfmarkp_; \ xfmarkp__->fname = fname_; \ - SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \ + SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_, view_); \ } while (0) /// Free and set given extended mark (regular mark + file name) -#define RESET_XFMARK(xfmarkp_, mark_, fnum_, fname_) \ +#define RESET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \ do { \ xfmark_T *const xfmarkp__ = xfmarkp_; \ free_xfmark(*xfmarkp__); \ xfmarkp__->fname = fname_; \ - SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_); \ + SET_FMARK(&(xfmarkp__->fmark), mark_, fnum_, view_); \ } while (0) /// Convert mark name to the offset diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index 994ad30633..a78056c5f9 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -10,6 +10,33 @@ * (a normal mark is a lnum/col pair, the same as a file position) */ +/// Flags for outcomes when moving to a mark. +typedef enum { + kMarkMoveSuccess = 1, ///< Successful move. + kMarkMoveFailed = 2, ///< Failed to move. + kMarkSwitchedBuf = 4, ///< Switched curbuf. + kMarkChangedCol = 8, ///< Changed the cursor col. + kMarkChangedLine = 16, ///< Changed the cursor line. + kMarkChangedCursor = 32, ///< Changed the cursor. + kMarkChangedView = 64, ///< Changed the view. +} MarkMoveRes; + +/// Flags to configure the movement to a mark. +typedef enum { + kMarkBeginLine = 1, ///< Move cursor to the beginning of the line. + kMarkContext = 2, ///< Leave context mark when moving the cursor. + KMarkNoContext = 4, ///< Don't leave a context mark. + kMarkSetView = 8, ///< Set the mark view after moving + kMarkJumpList = 16, ///< Special case, don't leave context mark when switching buffer +} MarkMove; + +/// Options when getting a mark +typedef enum { + kMarkBufLocal, ///< Only return marks that belong to the buffer. + kMarkAll, ///< Return all types of marks. + kMarkAllNoResolve, ///< Return all types of marks but don't resolve fnum (global marks). +} MarkGet; + /// Number of possible numbered global marks #define EXTRA_MARKS ('9' - '0' + 1) @@ -25,24 +52,40 @@ /// but they are not saved in ShaDa files. #define NLOCALMARKS (NMARKS + 3) +/// Max value of local mark +#define NMARK_LOCAL_MAX 126 // Index of '~' + /// Maximum number of marks in jump list #define JUMPLISTSIZE 100 /// Maximum number of tags in tag stack #define TAGSTACKSIZE 20 +/// Represents view in which the mark was created +typedef struct fmarkv { + linenr_T topline_offset; ///< Amount of lines from the mark lnum to the top of the window. + ///< Use MAXLNUM to indicate that the mark does not have a view. +} fmarkv_T; + +#define INIT_FMARKV { MAXLNUM } + /// Structure defining single local mark typedef struct filemark { pos_T mark; ///< Cursor position. int fnum; ///< File number. Timestamp timestamp; ///< Time when this mark was last set. + fmarkv_T view; ///< View the mark was created on dict_T *additional_data; ///< Additional data from ShaDa file. } fmark_T; +#define INIT_FMARK { { 0, 0, 0 }, 0, 0, INIT_FMARKV, NULL } + /// Structure defining extended mark (mark with file name attached) typedef struct xfilemark { fmark_T fmark; ///< Actual mark. char *fname; ///< File name, used when fnum == 0. } xfmark_T; +#define INIT_XFMARK { INIT_FMARK, NULL } + #endif // NVIM_MARK_DEFS_H diff --git a/src/nvim/match.c b/src/nvim/match.c index 3aa82527aa..e17a95569c 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -86,7 +86,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in m = xcalloc(1, sizeof(matchitem_T)); m->id = id; m->priority = prio; - m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat); + m->pattern = pat == NULL ? NULL: xstrdup(pat); m->hlg_id = hlg_id; m->match.regprog = regprog; m->match.rmm_ic = false; @@ -398,7 +398,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ linenr_T l; colnr_T matchcol; long nmatched = 0; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; // for :{range}s/pat only highlight inside the range if (lnum < search_first_line || lnum > search_last_line) { @@ -421,7 +421,6 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ // Repeat searching for a match until one is found that includes "mincol" // or none is found in this line. - called_emsg = false; for (;;) { // Stop searching after passing the time limit. if (profile_passed_limit(shl->tm)) { @@ -468,7 +467,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ if (regprog_is_copy) { cur->match.regprog = cur->hl.rm.regprog; } - if (called_emsg || got_int || timed_out) { + if (called_emsg > called_emsg_before || got_int || timed_out) { // Error while handling regexp: stop using this regexp. if (shl == search_hl) { // don't free regprog in the match list, it's a copy @@ -494,9 +493,6 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ shl->lnum += shl->rm.startpos[0].lnum; break; // useful match found } - - // Restore called_emsg for assert_fails(). - called_emsg = save_called_emsg; } } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 1032986a02..a9792cf1b9 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1840,8 +1840,9 @@ int mb_off_next(const char_u *base, const char_u *p) /// Return the offset from "p" to the last byte of the character it points /// into. Can start anywhere in a stream of bytes. /// Composing characters are not included. -int mb_tail_off(const char_u *base, const char_u *p) +int mb_tail_off(const char *base, const char *p_in) { + const uint8_t *p = (uint8_t *)p_in; int i; int j; @@ -1853,7 +1854,7 @@ int mb_tail_off(const char_u *base, const char_u *p) for (i = 0; (p[i + 1] & 0xc0) == 0x80; i++) {} // Check for illegal sequence. - for (j = 0; p - j > base; j++) { + for (j = 0; p_in - j > base; j++) { if ((p[-j] & 0xc0) != 0x80) { break; } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index eb6389a760..1ea5e2ccdc 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -665,7 +665,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) * First replace home dir path with "~/" with home_replace(). * Then insert the user name to get "~user/". */ - home_replace(NULL, (char *)buf->b_ffname, (char *)b0p->b0_fname, + home_replace(NULL, buf->b_ffname, (char *)b0p->b0_fname, B0_FNAME_SIZE_CRYPT, true); if (b0p->b0_fname[0] == '~') { // If there is no user name or it is too long, don't use "~/" @@ -680,7 +680,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) } } FileInfo file_info; - if (os_fileinfo((char *)buf->b_ffname, &file_info)) { + if (os_fileinfo(buf->b_ffname, &file_info)) { long_to_char(file_info.stat.st_mtim.tv_sec, b0p->b0_mtime); long_to_char((long)os_fileinfo_inode(&file_info), b0p->b0_ino); buf_store_file_info(buf, &file_info); @@ -708,7 +708,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) /// not set. static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) { - if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) { + if (same_directory(buf->b_ml.ml_mfp->mf_fname, (char_u *)buf->b_ffname)) { b0p->b0_flags |= B0_SAME_DIR; } else { b0p->b0_flags &= (uint8_t) ~B0_SAME_DIR; @@ -943,7 +943,7 @@ void ml_recover(bool checkext) if (buf_spname(curbuf) != NULL) { STRLCPY(NameBuff, buf_spname(curbuf), MAXPATHL); } else { - home_replace(NULL, (char *)curbuf->b_ffname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, curbuf->b_ffname, (char *)NameBuff, MAXPATHL, true); } smsg(_("Original file \"%s\""), NameBuff); msg_putchar('\n'); @@ -955,7 +955,7 @@ void ml_recover(bool checkext) FileInfo swp_file_info; mtime = char_to_long(b0p->b0_mtime); if (curbuf->b_ffname != NULL - && os_fileinfo((char *)curbuf->b_ffname, &org_file_info) + && os_fileinfo(curbuf->b_ffname, &org_file_info) && ((os_fileinfo((char *)mfp->mf_fname, &swp_file_info) && org_file_info.stat.st_mtim.tv_sec > swp_file_info.stat.st_mtim.tv_sec) @@ -989,7 +989,7 @@ void ml_recover(bool checkext) * 'fileencoding', etc. Ignore errors. The text itself is not used. */ if (curbuf->b_ffname != NULL) { - orig_file_status = readfile((char *)curbuf->b_ffname, NULL, (linenr_T)0, + orig_file_status = readfile(curbuf->b_ffname, NULL, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW, false); } @@ -1063,7 +1063,7 @@ void ml_recover(bool checkext) */ if (!cannot_open) { line_count = pp->pb_pointer[idx].pe_line_count; - if (readfile((char *)curbuf->b_ffname, NULL, lnum, + if (readfile(curbuf->b_ffname, NULL, lnum, pp->pb_pointer[idx].pe_old_lnum - 1, line_count, NULL, 0, false) != OK) { cannot_open = true; @@ -1304,7 +1304,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) // Isolate a directory name from *dirp and put it in dir_name (we know // it is large enough, so use 31000 for length). // Advance dirp to next directory name. - (void)copy_option_part(&dirp, dir_name, 31000, ","); + (void)copy_option_part((char **)&dirp, (char *)dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir if (fname == NULL) { @@ -1693,7 +1693,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) * call ml_preserve() to get rid of all negative numbered blocks. */ FileInfo file_info; - if (!os_fileinfo((char *)buf->b_ffname, &file_info) + if (!os_fileinfo(buf->b_ffname, &file_info) || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read || file_info.stat.st_mtim.tv_nsec != buf->b_mtime_read_ns || os_fileinfo_size(&file_info) != buf->b_orig_size) { @@ -1833,6 +1833,7 @@ char_u *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) DATA_BL *dp; char_u *ptr; static int recursive = 0; + static char_u questions[4]; if (lnum > buf->b_ml.ml_line_count) { // invalid line number if (recursive == 0) { @@ -1842,9 +1843,12 @@ char_u *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) siemsg(_("E315: ml_get: invalid lnum: %" PRId64), (int64_t)lnum); recursive--; } + ml_flush_line(buf); + buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; errorret: - STRCPY(IObuff, "???"); - return IObuff; + STRCPY(questions, "???"); + buf->b_ml.ml_line_lnum = lnum; + return questions; } if (lnum <= 0) { // pretend line 0 is line 1 lnum = 1; @@ -3416,12 +3420,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ */ const size_t dir_len = strlen(*dirp) + 1; dir_name = xmalloc(dir_len); - (void)copy_option_part((char_u **)dirp, (char_u *)dir_name, dir_len, ","); + (void)copy_option_part(dirp, dir_name, dir_len, ","); /* * we try different names until we find one that does not exist yet */ - fname = (char *)makeswapname((char_u *)buf_fname, buf->b_ffname, buf, + fname = (char *)makeswapname((char_u *)buf_fname, (char_u *)buf->b_ffname, buf, (char_u *)dir_name); for (;;) { @@ -3469,12 +3473,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (b0.b0_flags & B0_SAME_DIR) { if (FNAMECMP(path_tail((char *)buf->b_ffname), path_tail((char *)b0.b0_fname)) != 0 - || !same_directory((char_u *)fname, buf->b_ffname)) { + || !same_directory((char_u *)fname, (char_u *)buf->b_ffname)) { // Symlinks may point to the same file even // when the name differs, need to check the // inode too. expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino(buf->b_ffname, NameBuff, + if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, char_to_long(b0.b0_ino))) { differ = TRUE; } @@ -3483,7 +3487,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // The name in the swap file may be // "~user/path/file". Expand it first. expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino(buf->b_ffname, NameBuff, + if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, char_to_long(b0.b0_ino))) { differ = TRUE; } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 3bb33f5c48..4d5cf047f9 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -687,6 +687,7 @@ void free_all_mem(void) // Clear menus. do_cmdline_cmd("aunmenu *"); + do_cmdline_cmd("tlunmenu *"); do_cmdline_cmd("menutranslate clear"); // Clear mappings, abbreviations, breakpoints. diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 80f8406ab0..018c62d604 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -11,6 +11,7 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" @@ -22,6 +23,7 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" +#include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -36,10 +38,9 @@ #endif /// The character for each menu mode -static char menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' }; +static char *menu_mode_chars[] = { "n", "v", "s", "o", "i", "c", "tl", "t" }; static char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu"); -static char e_othermode[] = N_("E328: Menu only exists in another mode"); static char e_nomenu[] = N_("E329: No menu \"%s\""); // Return true if "name" is a window toolbar menu name. @@ -571,7 +572,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent) } } else if (*name != NUL) { if (!silent) { - emsg(_(e_othermode)); + emsg(_(e_menuothermode)); } return FAIL; } @@ -723,7 +724,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) (menu->noremap[bit] & REMAP_NONE) ? 1 : 0); tv_dict_add_nr(impl, S_LEN("sid"), (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0); - tv_dict_add_dict(commands, &menu_mode_chars[bit], 1, impl); + tv_dict_add_dict(commands, menu_mode_chars[bit], 1, impl); } } } else { @@ -785,7 +786,7 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes) emsg(_(e_notsubmenu)); return NULL; } else if ((menu->modes & modes) == 0x0) { - emsg(_(e_othermode)); + emsg(_(e_menuothermode)); return NULL; } else if (*p == NUL) { // found a full match return menu; @@ -859,7 +860,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) for (i = 0; i < depth + 2; i++) { msg_puts(" "); } - msg_putchar(menu_mode_chars[bit]); + msg_puts(menu_mode_chars[bit]); if (menu->noremap[bit] == REMAP_NONE) { msg_putchar('*'); } else if (menu->noremap[bit] == REMAP_SCRIPT) { @@ -1213,6 +1214,11 @@ int get_menu_cmd_modes(const char *cmd, bool forceit, int *noremap, int *unmenu) modes = MENU_INSERT_MODE; break; case 't': + if (*cmd == 'l') { // tlmenu, tlunmenu, tlnoremenu + modes = MENU_TERMINAL_MODE; + cmd++; + break; + } modes = MENU_TIP_MODE; // tmenu break; case 'c': // cmenu @@ -1259,9 +1265,13 @@ static char *popup_mode_name(char *name, int idx) size_t len = STRLEN(name); assert(len >= 4); - char *p = xstrnsave(name, len + 1); - memmove(p + 6, p + 5, len - 4); - p[5] = menu_mode_chars[idx]; + char *mode_chars = menu_mode_chars[idx]; + size_t mode_chars_len = strlen(mode_chars); + char *p = xstrnsave(name, len + mode_chars_len); + memmove(p + 5 + mode_chars_len, p + 5, len - 4); + for (size_t i = 0; i < mode_chars_len; i++) { + p[5 + i] = menu_mode_chars[idx][i]; + } return p; } @@ -1355,77 +1365,142 @@ static int menu_is_hidden(char *name) || (menu_is_popup(name) && name[5] != NUL); } -// Execute "menu". Use by ":emenu" and the window toolbar. -// "eap" is NULL for the window toolbar. -static void execute_menu(const exarg_T *eap, vimmenu_T *menu) - FUNC_ATTR_NONNULL_ARG(2) +static int get_menu_mode(void) { - int idx = -1; - char *mode; - - // Use the Insert mode entry when returning to Insert mode. - if (((State & MODE_INSERT) || restart_edit) && !current_sctx.sc_sid) { - mode = "Insert"; - idx = MENU_INDEX_INSERT; - } else if (State & MODE_CMDLINE) { - mode = "Command"; - idx = MENU_INDEX_CMDLINE; - } else if (get_real_state() & MODE_VISUAL) { - // Detect real visual mode -- if we are really in visual mode we - // don't need to do any guesswork to figure out what the selection - // is. Just execute the visual binding for the menu. - mode = "Visual"; - idx = MENU_INDEX_VISUAL; - } else if (eap != NULL && eap->addr_count) { - pos_T tpos; - - mode = "Visual"; - idx = MENU_INDEX_VISUAL; - - // GEDDES: This is not perfect - but it is a - // quick way of detecting whether we are doing this from a - // selection - see if the range matches up with the visual - // select start and end. - if ((curbuf->b_visual.vi_start.lnum == eap->line1) - && (curbuf->b_visual.vi_end.lnum) == eap->line2) { - // Set it up for visual mode - equivalent to gv. - VIsual_mode = curbuf->b_visual.vi_mode; - tpos = curbuf->b_visual.vi_end; - curwin->w_cursor = curbuf->b_visual.vi_start; - curwin->w_curswant = curbuf->b_visual.vi_curswant; - } else { - // Set it up for line-wise visual mode - VIsual_mode = 'V'; - curwin->w_cursor.lnum = eap->line1; - curwin->w_cursor.col = 1; - tpos.lnum = eap->line2; - tpos.col = MAXCOL; - tpos.coladd = 0; + if (State & MODE_TERMINAL) { + return MENU_INDEX_TERMINAL; + } + if (VIsual_active) { + if (VIsual_select) { + return MENU_INDEX_SELECT; } + return MENU_INDEX_VISUAL; + } + if (State & MODE_INSERT) { + return MENU_INDEX_INSERT; + } + if ((State & MODE_CMDLINE) || State == MODE_ASKMORE || State == MODE_HITRETURN) { + return MENU_INDEX_CMDLINE; + } + if (finish_op) { + return MENU_INDEX_OP_PENDING; + } + if (State & MODE_NORMAL) { + return MENU_INDEX_NORMAL; + } + if (State & MODE_LANGMAP) { // must be a "r" command, like Insert mode + return MENU_INDEX_INSERT; + } + return MENU_INDEX_INVALID; +} + +int get_menu_mode_flag(void) +{ + int mode = get_menu_mode(); + + if (mode == MENU_INDEX_INVALID) { + return 0; + } + return 1 << mode; +} - // Activate visual mode - VIsual_active = TRUE; - VIsual_reselect = TRUE; - check_cursor(); - VIsual = curwin->w_cursor; - curwin->w_cursor = tpos; +/// Display the Special "PopUp" menu as a pop-up at the current mouse +/// position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode, +/// etc. +void show_popupmenu(void) +{ + int menu_mode = get_menu_mode(); + if (menu_mode == MENU_INDEX_INVALID) { + return; + } + char *mode = menu_mode_chars[menu_mode]; + size_t mode_len = strlen(mode); - check_cursor(); + apply_autocmds(EVENT_MENUPOPUP, mode, NULL, false, curbuf); - // Adjust the cursor to make sure it is in the correct pos - // for exclusive mode - if (*p_sel == 'e' && gchar_cursor() != NUL) { - curwin->w_cursor.col++; + vimmenu_T *menu; + + for (menu = root_menu; menu != NULL; menu = menu->next) { + if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0) { + break; } } - if (idx == -1 || eap == NULL) { - mode = "Normal"; + // Only show a popup when it is defined and has entries + if (menu != NULL && menu->children != NULL) { + pum_show_popupmenu(menu); + } +} + +/// Execute "menu". Use by ":emenu" and the window toolbar. +/// @param eap NULL for the window toolbar. +/// @param mode_idx specify a MENU_INDEX_ value, use -1 to depend on the current state +void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) + FUNC_ATTR_NONNULL_ARG(2) +{ + int idx = mode_idx; + + if (idx < 0) { + // Use the Insert mode entry when returning to Insert mode. + if (((State & MODE_INSERT) || restart_edit) && !current_sctx.sc_sid) { + idx = MENU_INDEX_INSERT; + } else if (State & MODE_CMDLINE) { + idx = MENU_INDEX_CMDLINE; + } else if (State & MODE_TERMINAL) { + idx = MENU_INDEX_TERMINAL; + } else if (get_real_state() & MODE_VISUAL) { + // Detect real visual mode -- if we are really in visual mode we + // don't need to do any guesswork to figure out what the selection + // is. Just execute the visual binding for the menu. + idx = MENU_INDEX_VISUAL; + } else if (eap != NULL && eap->addr_count) { + pos_T tpos; + + idx = MENU_INDEX_VISUAL; + + // GEDDES: This is not perfect - but it is a + // quick way of detecting whether we are doing this from a + // selection - see if the range matches up with the visual + // select start and end. + if ((curbuf->b_visual.vi_start.lnum == eap->line1) + && (curbuf->b_visual.vi_end.lnum) == eap->line2) { + // Set it up for visual mode - equivalent to gv. + VIsual_mode = curbuf->b_visual.vi_mode; + tpos = curbuf->b_visual.vi_end; + curwin->w_cursor = curbuf->b_visual.vi_start; + curwin->w_curswant = curbuf->b_visual.vi_curswant; + } else { + // Set it up for line-wise visual mode + VIsual_mode = 'V'; + curwin->w_cursor.lnum = eap->line1; + curwin->w_cursor.col = 1; + tpos.lnum = eap->line2; + tpos.col = MAXCOL; + tpos.coladd = 0; + } + + // Activate visual mode + VIsual_active = true; + VIsual_reselect = true; + check_cursor(); + VIsual = curwin->w_cursor; + curwin->w_cursor = tpos; + + check_cursor(); + + // Adjust the cursor to make sure it is in the correct pos + // for exclusive mode + if (*p_sel == 'e' && gchar_cursor() != NUL) { + curwin->w_cursor.col++; + } + } + } + + if (idx == MENU_INDEX_INVALID || eap == NULL) { idx = MENU_INDEX_NORMAL; } - assert(idx != MENU_INDEX_INVALID); - if (menu->strings[idx] != NULL) { + if (menu->strings[idx] != NULL && (menu->modes & (1 << idx))) { // When executing a script or function execute the commands right now. // Also for the window toolbar // Otherwise put them in the typeahead buffer. @@ -1444,6 +1519,30 @@ static void execute_menu(const exarg_T *eap, vimmenu_T *menu) menu->silent[idx]); } } else if (eap != NULL) { + char *mode; + switch (idx) { + case MENU_INDEX_VISUAL: + mode = "Visual"; + break; + case MENU_INDEX_SELECT: + mode = "Select"; + break; + case MENU_INDEX_OP_PENDING: + mode = "Op-pending"; + break; + case MENU_INDEX_TERMINAL: + mode = "Terminal"; + break; + case MENU_INDEX_INSERT: + mode = "Insert"; + break; + case MENU_INDEX_CMDLINE: + mode = "Cmdline"; + break; + // case MENU_INDEX_TIP: cannot happen + default: + mode = "Normal"; + } semsg(_("E335: Menu not defined for %s mode"), mode); } } @@ -1452,9 +1551,43 @@ static void execute_menu(const exarg_T *eap, vimmenu_T *menu) // execute it. void ex_emenu(exarg_T *eap) { - char *saved_name = xstrdup(eap->arg); + char *arg = eap->arg; + int mode_idx = -1; + + if (arg[0] && ascii_iswhite(arg[1])) { + switch (arg[0]) { + case 'n': + mode_idx = MENU_INDEX_NORMAL; + break; + case 'v': + mode_idx = MENU_INDEX_VISUAL; + break; + case 's': + mode_idx = MENU_INDEX_SELECT; + break; + case 'o': + mode_idx = MENU_INDEX_OP_PENDING; + break; + case 't': + mode_idx = MENU_INDEX_TERMINAL; + break; + case 'i': + mode_idx = MENU_INDEX_INSERT; + break; + case 'c': + mode_idx = MENU_INDEX_CMDLINE; + break; + default: + semsg(_(e_invarg2), arg); + return; + } + arg = skipwhite(arg + 2); + } + + char *saved_name = xstrdup(arg); vimmenu_T *menu = *get_root_menu(saved_name); char *name = saved_name; + bool gave_emsg = false; while (*name) { // Find in the menu hierarchy char *p = menu_name_skip(name); @@ -1463,6 +1596,7 @@ void ex_emenu(exarg_T *eap) if (menu_name_equal(name, menu)) { if (*p == NUL && menu->children != NULL) { emsg(_("E333: Menu path must lead to a menu item")); + gave_emsg = true; menu = NULL; } else if (*p != NUL && menu->children == NULL) { emsg(_(e_notsubmenu)); @@ -1480,12 +1614,60 @@ void ex_emenu(exarg_T *eap) } xfree(saved_name); if (menu == NULL) { - semsg(_("E334: Menu not found: %s"), eap->arg); + if (!gave_emsg) { + semsg(_("E334: Menu not found: %s"), arg); + } return; } // Found the menu, so execute. - execute_menu(eap, menu); + execute_menu(eap, menu, mode_idx); +} + +/// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy. +vimmenu_T *menu_find(const char *path_name) +{ + vimmenu_T *menu = *get_root_menu(path_name); + char *saved_name = xstrdup(path_name); + char *name = saved_name; + while (*name) { + // find the end of one dot-separated name and put a NUL at the dot + char *p = menu_name_skip(name); + + while (menu != NULL) { + if (menu_name_equal(name, menu)) { + if (menu->children == NULL) { + // found a menu item instead of a sub-menu + if (*p == NUL) { + emsg(_("E336: Menu path must lead to a sub-menu")); + } else { + emsg(_(e_notsubmenu)); + } + menu = NULL; + goto theend; + } + if (*p == NUL) { // found a full match + goto theend; + } + break; + } + menu = menu->next; + } + if (menu == NULL) { // didn't find it + break; + } + + // Found a match, search the sub-menu. + menu = menu->children; + name = p; + } + + if (menu == NULL) { + emsg(_("E337: Menu not found - check menu names")); + } +theend: + xfree(saved_name); + return menu; } /* diff --git a/src/nvim/menu.h b/src/nvim/menu.h index 5c65918d79..9a60ebfb83 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -18,6 +18,7 @@ #define MENU_OP_PENDING_MODE (1 << MENU_INDEX_OP_PENDING) #define MENU_INSERT_MODE (1 << MENU_INDEX_INSERT) #define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) +#define MENU_TERMINAL_MODE (1 << MENU_INDEX_TERMINAL) #define MENU_TIP_MODE (1 << MENU_INDEX_TIP) #define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 1) /// @} diff --git a/src/nvim/message.c b/src/nvim/message.c index 733b4808fe..2c96613bb3 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -387,7 +387,7 @@ char_u *msg_strtrunc(char_u *s, int force) // composing chars) len = (room + 2) * 18; buf = xmalloc((size_t)len); - trunc_string(s, buf, room, len); + trunc_string((char *)s, (char *)buf, room, len); } } return buf; @@ -395,7 +395,7 @@ char_u *msg_strtrunc(char_u *s, int force) /// Truncate a string "s" to "buf" with cell width "room". /// "s" and "buf" may be equal. -void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) +void trunc_string(char *s, char *buf, int room_in, int buflen) { int room = room_in - 3; // "..." takes 3 chars int half; @@ -423,13 +423,13 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) buf[e] = NUL; return; } - n = ptr2cells((char *)s + e); + n = ptr2cells(s + e); if (len + n > half) { break; } len += n; buf[e] = s[e]; - for (n = utfc_ptr2len((char *)s + e); --n > 0;) { + for (n = utfc_ptr2len(s + e); --n > 0;) { if (++e == buflen) { break; } @@ -441,9 +441,9 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) half = i = (int)STRLEN(s); for (;;) { do { - half = half - utf_head_off(s, s + half - 1) - 1; - } while (half > 0 && utf_iscomposing(utf_ptr2char((char *)s + half))); - n = ptr2cells((char *)s + half); + half = half - utf_head_off((char_u *)s, (char_u *)s + half - 1) - 1; + } while (half > 0 && utf_iscomposing(utf_ptr2char(s + half))); + n = ptr2cells(s + half); if (len + n > room || half == 0) { break; } @@ -637,7 +637,7 @@ static bool emsg_multiline(const char *s, bool multiline) return true; } - called_emsg = true; + called_emsg++; // If "emsg_severe" is true: When an error exception is to be thrown, // prefer this message over previous messages for the same command. @@ -652,7 +652,7 @@ static bool emsg_multiline(const char *s, bool multiline) // interrupt message). if (cause_errthrow(s, severe, &ignore)) { if (!ignore) { - did_emsg++; + did_emsg = true; } return true; } @@ -717,7 +717,7 @@ static bool emsg_multiline(const char *s, bool multiline) } else { flush_buffers(FLUSH_MINIMAL); // flush internal buffers } - did_emsg++; // flag for DoOneCmd() + did_emsg = true; // flag for DoOneCmd() } emsg_on_display = true; // remember there is an error message @@ -891,7 +891,7 @@ char_u *msg_may_trunc(bool force, char_u *s) room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) - && (int)STRLEN(s) - room > 0) { + && (int)STRLEN(s) - room > 0 && p_ch > 0) { int size = vim_strsize((char *)s); // There may be room anyway when there are multibyte chars. @@ -1477,10 +1477,8 @@ void msg_home_replace_hl(char_u *fname) static void msg_home_replace_attr(char_u *fname, int attr) { - char_u *name; - - name = home_replace_save(NULL, fname); - msg_outtrans_attr(name, attr); + char *name = home_replace_save(NULL, (char *)fname); + msg_outtrans_attr((char_u *)name, attr); xfree(name); } @@ -2329,7 +2327,7 @@ bool message_filtered(char_u *msg) return false; } - bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, (colnr_T)0); + bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, (char *)msg, (colnr_T)0); return cmdmod.cmod_filter_force ? match : !match; } @@ -3083,10 +3081,11 @@ void msg_clr_eos_force(void) msg_row = msg_grid_pos; } - grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, - ' ', ' ', HL_ATTR(HLF_MSG)); if (p_ch > 0) { - grid_fill(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, ' ', ' ', HL_ATTR(HLF_MSG)); + grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, + ' ', ' ', HL_ATTR(HLF_MSG)); + grid_fill(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, + ' ', ' ', HL_ATTR(HLF_MSG)); } redraw_cmdline = true; // overwritten the command line diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index fc5ecbc6a0..a4a521fa80 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -30,6 +30,49 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; +/// Translate window coordinates to buffer position without any side effects +int get_fpos_of_mouse(pos_T *mpos) +{ + int grid = mouse_grid; + int row = mouse_row; + int col = mouse_col; + + if (row < 0 || col < 0) { // check if it makes sense + return IN_UNKNOWN; + } + + // find the window where the row is in + win_T *wp = mouse_find_win(&grid, &row, &col); + if (wp == NULL) { + return IN_UNKNOWN; + } + + // winpos and height may change in win_enter()! + if (row + wp->w_winbar_height >= wp->w_height) { // In (or below) status line + return IN_STATUS_LINE; + } + if (col >= wp->w_width) { // In vertical separator line + return IN_SEP_LINE; + } + + if (wp != curwin) { + return IN_UNKNOWN; + } + + // compute the position in the buffer line from the posn on the screen + if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum)) { + return IN_STATUS_LINE; // past bottom + } + + mpos->col = vcol2col(wp, mpos->lnum, col); + + if (mpos->col > 0) { + mpos->col--; + } + mpos->coladd = 0; + return IN_BUFFER; +} + /// Return true if "c" is a mouse key. bool is_mouse_key(int c) { @@ -85,8 +128,10 @@ bool is_mouse_key(int c) /// @param which_button MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE int jump_to_mouse(int flags, bool *inclusive, int which_button) { - static int on_status_line = 0; // #lines below bottom of window - static int on_sep_line = 0; // on separator right of window + static int status_line_offset = 0; // #lines offset from status line + static int sep_line_offset = 0; // #cols offset from sep line + static bool on_status_line = false; + static bool on_sep_line = false; static bool on_winbar = false; static int prev_row = -1; static int prev_col = -1; @@ -101,6 +146,7 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button) int col = mouse_col; int grid = mouse_grid; int fdc = 0; + bool keep_focus = flags & MOUSE_FOCUS; mouse_past_bottom = false; mouse_past_eol = false; @@ -121,10 +167,10 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button) retnomove: // before moving the cursor for a left click which is NOT in a status // line, stop Visual mode - if (on_status_line) { + if (status_line_offset) { return IN_STATUS_LINE; } - if (on_sep_line) { + if (sep_line_offset) { return IN_SEP_LINE; } if (on_winbar) { @@ -146,49 +192,78 @@ retnomove: old_curwin = curwin; old_cursor = curwin->w_cursor; - if (!(flags & MOUSE_FOCUS)) { - if (row < 0 || col < 0) { // check if it makes sense - return IN_UNKNOWN; + if (row < 0 || col < 0) { // check if it makes sense + return IN_UNKNOWN; + } + + // find the window where the row is in + wp = mouse_find_win(&grid, &row, &col); + if (wp == NULL) { + return IN_UNKNOWN; + } + + on_status_line = (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height) + ? row + wp->w_winbar_height - wp->w_height + 1 == 1 + : false; + + on_winbar = (row == -1) + ? wp->w_winbar_height != 0 + : false; + + on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width + ? col - wp->w_width + 1 == 1 + : false; + + // The rightmost character of the status line might be a vertical + // separator character if there is no connecting window to the right. + if (on_status_line && on_sep_line) { + if (stl_connected(wp)) { + on_sep_line = false; + } else { + on_status_line = false; } + } - // find the window where the row is in - wp = mouse_find_win(&grid, &row, &col); - if (wp == NULL) { - return IN_UNKNOWN; + if (keep_focus) { + // If we can't change focus, set the value of row, col and grid back to absolute values + // since the values relative to the window are only used when keep_focus is false + row = mouse_row; + col = mouse_col; + grid = mouse_grid; + } + + if (!keep_focus) { + if (on_winbar) { + return IN_OTHER_WIN | MOUSE_WINBAR; } + fdc = win_fdccol_count(wp); dragwin = NULL; - if (row == -1) { - on_winbar = wp->w_winbar_height != 0; - return IN_OTHER_WIN | (on_winbar ? MOUSE_WINBAR : 0); - } - on_winbar = false; - // winpos and height may change in win_enter()! if (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height) { // In (or below) status line - on_status_line = row + wp->w_winbar_height - wp->w_height + 1; + status_line_offset = row + wp->w_winbar_height - wp->w_height + 1; dragwin = wp; } else { - on_status_line = 0; + status_line_offset = 0; } if (grid == DEFAULT_GRID_HANDLE && col >= wp->w_width) { // In separator line - on_sep_line = col - wp->w_width + 1; + sep_line_offset = col - wp->w_width + 1; dragwin = wp; } else { - on_sep_line = 0; + sep_line_offset = 0; } // The rightmost character of the status line might be a vertical // separator character if there is no connecting window to the right. - if (on_status_line && on_sep_line) { + if (status_line_offset && sep_line_offset) { if (stl_connected(wp)) { - on_sep_line = 0; + sep_line_offset = 0; } else { - on_status_line = 0; + status_line_offset = 0; } } @@ -196,8 +271,8 @@ retnomove: // click, stop Visual mode. if (VIsual_active && (wp->w_buffer != curwin->w_buffer - || (!on_status_line - && !on_sep_line + || (!status_line_offset + && !sep_line_offset && (wp->w_p_rl ? col < wp->w_width_inner - fdc : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1)) @@ -208,7 +283,7 @@ retnomove: if (cmdwin_type != 0 && wp != curwin) { // A click outside the command-line window: Use modeless // selection if possible. Allow dragging the status lines. - on_sep_line = 0; + sep_line_offset = 0; row = 0; col += wp->w_wincol; wp = curwin; @@ -223,7 +298,7 @@ retnomove: if (curwin != old_curwin) { set_mouse_topline(curwin); } - if (on_status_line) { // In (or below) status line + if (status_line_offset) { // In (or below) status line // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_STATUS_LINE; @@ -231,7 +306,7 @@ retnomove: return IN_STATUS_LINE | CURSOR_MOVED; } } - if (on_sep_line) { // In (or below) status line + if (sep_line_offset) { // In (or below) status line // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_SEP_LINE; @@ -241,25 +316,27 @@ retnomove: } curwin->w_cursor.lnum = curwin->w_topline; - } else if (on_status_line) { + } else if (status_line_offset) { if (which_button == MOUSE_LEFT && dragwin != NULL) { // Drag the status line count = row - dragwin->w_winrow - dragwin->w_height + 1 - - on_status_line; + - status_line_offset; win_drag_status_line(dragwin, count); did_drag |= count; } return IN_STATUS_LINE; // Cursor didn't move - } else if (on_sep_line && which_button == MOUSE_LEFT) { + } else if (sep_line_offset && which_button == MOUSE_LEFT) { if (dragwin != NULL) { // Drag the separator column count = col - dragwin->w_wincol - dragwin->w_width + 1 - - on_sep_line; + - sep_line_offset; win_drag_vsep_line(dragwin, count); did_drag |= count; } return IN_SEP_LINE; // Cursor didn't move - } else if (on_winbar) { + } else if (on_status_line && which_button == MOUSE_RIGHT) { + return IN_STATUS_LINE; + } else if (on_winbar && which_button == MOUSE_RIGHT) { // After a click on the window bar don't start Visual mode. return IN_OTHER_WIN | MOUSE_WINBAR; } else { @@ -645,7 +722,7 @@ bool mouse_scroll_horiz(int dir) return false; } - int step = 6; + int step = (int)p_mousescroll_hor; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { step = curwin->w_width_inner; } diff --git a/src/nvim/move.c b/src/nvim/move.c index 99ca5060cd..bd68ad6f97 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -786,8 +786,7 @@ void curs_columns(win_T *wp, int may_scroll) } else { wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows; } - } else if (wp->w_p_wrap - && wp->w_width_inner != 0) { + } else if (wp->w_p_wrap && wp->w_width_inner != 0) { width = textwidth + win_col_off2(wp); // long line wrapping, adjust wp->w_wrow @@ -1083,8 +1082,7 @@ bool scrolldown(long line_count, int byfold) * and move the cursor onto the displayed part of the window. */ int wrow = curwin->w_wrow; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { + if (curwin->w_p_wrap && curwin->w_width_inner != 0) { validate_virtcol(); validate_cheight(); wrow += curwin->w_cline_height - 1 - diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 388fa2584c..de01443313 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -532,8 +532,19 @@ void rpc_close(Channel *channel) } } +static void exit_delay_cb(uv_timer_t *handle) +{ + uv_timer_stop(&main_loop.exit_delay_timer); + multiqueue_put(main_loop.fast_events, exit_event, 0); +} + static void exit_event(void **argv) { + if (exit_need_delay) { + uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); + return; + } + if (!exiting) { os_exit(0); } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index aeb85eba1c..b675abfb7d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -432,6 +432,18 @@ static int find_command(int cmdchar) return idx; } +/// If currently editing a cmdline or text is locked: beep and give an error +/// message, return true. +static bool check_text_locked(oparg_T *oap) +{ + if (text_locked()) { + clearopbeep(oap); + text_locked_msg(); + return true; + } + return false; +} + /// Normal state entry point. This is called on: /// /// - Startup, In this case the function never returns. @@ -559,6 +571,14 @@ static bool normal_need_additional_char(NormalState *s) static bool normal_need_redraw_mode_message(NormalState *s) { + // In Visual mode and with "^O" in Insert mode, a short message will be + // overwritten by the mode message. Wait a bit, until a key is hit. + // In Visual mode, it's more important to keep the Visual area updated + // than keeping a message (e.g. from a /pat search). + // Only do this if the command was typed, not from a mapping. + // Don't wait when emsg_silent is non-zero. + // Also wait a bit after an error message, e.g. for "^O:". + // Don't redraw the screen, it would remove the message. return ( // 'showmode' is set and messages can be printed ((p_smd && msg_silent == 0 @@ -892,14 +912,6 @@ static void normal_finish_command(NormalState *s) // Wait for a moment when a message is displayed that will be overwritten // by the mode message. - // In Visual mode and with "^O" in Insert mode, a short message will be - // overwritten by the mode message. Wait a bit, until a key is hit. - // In Visual mode, it's more important to keep the Visual area updated - // than keeping a message (e.g. from a /pat search). - // Only do this if the command was typed, not from a mapping. - // Don't wait when emsg_silent is non-zero. - // Also wait a bit after an error message, e.g. for "^O:". - // Don't redraw the screen, it would remove the message. if (normal_need_redraw_mode_message(s)) { normal_redraw_mode_message(s); } @@ -1079,15 +1091,9 @@ static int normal_execute(VimState *state, int key) goto finish; } - if (text_locked() && (nv_cmds[s->idx].cmd_flags & NV_NCW)) { - // This command is not allowed while editing a cmdline: beep. - clearopbeep(&s->oa); - text_locked_msg(); - s->command_finished = true; - goto finish; - } - - if ((nv_cmds[s->idx].cmd_flags & NV_NCW) && curbuf_locked()) { + if ((nv_cmds[s->idx].cmd_flags & NV_NCW) + && (check_text_locked(&s->oa) || curbuf_locked())) { + // this command is not allowed now s->command_finished = true; goto finish; } @@ -1493,12 +1499,12 @@ static void call_click_def_func(StlClickDefinition *click_defs, int col, int whi /// Do the appropriate action for the current mouse click in the current mode. /// Not used for Command-line mode. /// -/// Normal Mode: +/// Normal and Visual Mode: /// event modi- position visual change action /// fier cursor window /// left press - yes end yes /// left press C yes end yes "^]" (2) -/// left press S yes end yes "*" (2) +/// left press S yes end (popup: extend) yes "*" (2) /// left drag - yes start if moved no /// left relse - yes start if moved no /// middle press - yes if not active no put register @@ -1787,9 +1793,52 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) if (mouse_model_popup()) { if (which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - // NOTE: Ignore right button down and drag mouse events. Windows only - // shows the popup menu on the button up event. - return false; + if (!is_click) { + // Ignore right button release events, only shows the popup + // menu on the button down event. + return false; + } + jump_flags = 0; + if (STRCMP(p_mousem, "popup_setpos") == 0) { + // First set the cursor position before showing the popup + // menu. + if (VIsual_active) { + pos_T m_pos; + // set MOUSE_MAY_STOP_VIS if we are outside the + // selection or the current window (might have false + // negative here) + if (mouse_row < curwin->w_winrow + || mouse_row > (curwin->w_winrow + curwin->w_height)) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else { + if ((lt(curwin->w_cursor, VIsual) + && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) + || (lt(VIsual, curwin->w_cursor) + && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); + getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); + if (m_pos.col < leftcol || m_pos.col > rightcol) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + } + } else { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + if (jump_flags) { + jump_flags = jump_to_mouse(jump_flags, NULL, which_button); + update_curbuf(VIsual_active ? INVERTED : VALID); + setcursor(); + ui_flush(); // Update before showing popup menu + } + show_popupmenu(); + got_click = false; // ignore release events + return (jump_flags & CURSOR_MOVED) != 0; } if (which_button == MOUSE_LEFT && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { @@ -2035,8 +2084,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } else if ((mod_mask & MOD_MASK_SHIFT)) { // Shift-Mouse click searches for the next occurrence of the word under // the mouse pointer - if (State & MODE_INSERT - || (VIsual_active && VIsual_select)) { + if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { stuffcharReadbuff(Ctrl_O); } if (which_button == MOUSE_LEFT) { @@ -2451,21 +2499,30 @@ static void prep_redo_cmd(cmdarg_T *cap) /// Note that only the last argument can be a multi-byte char. void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) { + prep_redo_num2(regname, num, cmd1, cmd2, 0L, cmd3, cmd4, cmd5); +} + +/// Prepare for redo of any command with extra count after "cmd2". +void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int cmd3, int cmd4, + int cmd5) +{ ResetRedobuff(); if (regname != 0) { // yank from specified buffer AppendCharToRedobuff('"'); AppendCharToRedobuff(regname); } - if (num) { - AppendNumberToRedobuff(num); + if (num1 != 0) { + AppendNumberToRedobuff(num1); } - if (cmd1 != NUL) { AppendCharToRedobuff(cmd1); } if (cmd2 != NUL) { AppendCharToRedobuff(cmd2); } + if (num2 != 0) { + AppendNumberToRedobuff(num2); + } if (cmd3 != NUL) { AppendCharToRedobuff(cmd3); } @@ -2494,8 +2551,7 @@ static bool checkclearop(oparg_T *oap) /// @return true if operator or Visual was active. static bool checkclearopq(oparg_T *oap) { - if (oap->op_type == OP_NOP - && !VIsual_active) { + if (oap->op_type == OP_NOP && !VIsual_active) { return false; } clearopbeep(oap); @@ -3100,7 +3156,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ } break; } - if (get_leader_len(get_cursor_line_ptr(), NULL, false, true) > 0) { + if (get_leader_len((char *)get_cursor_line_ptr(), NULL, false, true) > 0) { // Ignore this line, continue at start of next line. curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -3340,8 +3396,8 @@ static void nv_mousescroll(cmdarg_T *cap) if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L); } else { - cap->count1 = 3; - cap->count0 = 3; + cap->count1 = p_mousescroll_vert; + cap->count0 = p_mousescroll_vert; nv_scroll_line(cap); } } else { @@ -3422,6 +3478,108 @@ void scroll_redraw(int up, long count) redraw_later(curwin, VALID); } +/// Get the count specified after a 'z' command. +/// @return true to process the 'z' command and false to skip it. +static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg) +{ + int nchar = *nchar_arg; + + // "z123{nchar}": edit the count before obtaining {nchar} + if (checkclearop(cap->oap)) { + return false; + } + long n = nchar - '0'; + + for (;;) { + no_mapping++; + allow_keys++; // no mapping for nchar, but allow key codes + nchar = plain_vgetc(); + LANGMAP_ADJUST(nchar, true); + no_mapping--; + allow_keys--; + (void)add_to_showcmd(nchar); + if (nchar == K_DEL || nchar == K_KDEL) { + n /= 10; + } else if (ascii_isdigit(nchar)) { + n = n * 10 + (nchar - '0'); + } else if (nchar == CAR) { + win_setheight((int)n); + break; + } else if (nchar == 'l' + || nchar == 'h' + || nchar == K_LEFT + || nchar == K_RIGHT) { + cap->count1 = n ? n * cap->count1 : cap->count1; + *nchar_arg = nchar; + return true; + } else { + clearopbeep(cap->oap); + break; + } + } + cap->oap->op_type = OP_NOP; + return false; +} + +/// "zug" and "zuw": undo "zg" and "zw" +/// "zg": add good word to word list +/// "zw": add wrong word to word list +/// "zG": add good word to temp word list +/// "zW": add wrong word to temp word list +static int nv_zg_zw(cmdarg_T *cap, int nchar) +{ + bool undo = false; + + if (nchar == 'u') { + no_mapping++; + allow_keys++; // no mapping for nchar, but allow key codes + nchar = plain_vgetc(); + LANGMAP_ADJUST(nchar, true); + no_mapping--; + allow_keys--; + (void)add_to_showcmd(nchar); + if (vim_strchr("gGwW", nchar) == NULL) { + clearopbeep(cap->oap); + return OK; + } + undo = true; + } + + if (checkclearop(cap->oap)) { + return OK; + } + char_u *ptr = NULL; + size_t len; + if (VIsual_active && !get_visual_text(cap, &ptr, &len)) { + return FAIL; + } + if (ptr == NULL) { + pos_T pos = curwin->w_cursor; + + // Find bad word under the cursor. When 'spell' is + // off this fails and find_ident_under_cursor() is + // used below. + emsg_off++; + len = spell_move_to(curwin, FORWARD, true, true, NULL); + emsg_off--; + if (len != 0 && curwin->w_cursor.col <= pos.col) { + ptr = ml_get_pos(&curwin->w_cursor); + } + curwin->w_cursor = pos; + } + + if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { + return FAIL; + } + assert(len <= INT_MAX); + spell_add_word(ptr, (int)len, + nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD, + (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, + undo); + + return OK; +} + /// Commands that start with "z". static void nv_zet(cmdarg_T *cap) { @@ -3430,47 +3588,13 @@ static void nv_zet(cmdarg_T *cap) int nchar = cap->nchar; long old_fdl = curwin->w_p_fdl; int old_fen = curwin->w_p_fen; - bool undo = false; int l_p_siso = (int)get_sidescrolloff_value(curwin); - if (ascii_isdigit(nchar)) { - // "z123{nchar}": edit the count before obtaining {nchar} - if (checkclearop(cap->oap)) { - return; - } - n = nchar - '0'; - for (;;) { - no_mapping++; - allow_keys++; // no mapping for nchar, but allow key codes - nchar = plain_vgetc(); - LANGMAP_ADJUST(nchar, true); - no_mapping--; - allow_keys--; - (void)add_to_showcmd(nchar); - if (nchar == K_DEL || nchar == K_KDEL) { - n /= 10; - } else if (ascii_isdigit(nchar)) { - n = n * 10 + (nchar - '0'); - } else if (nchar == CAR) { - win_setheight(n); - break; - } else if (nchar == 'l' - || nchar == 'h' - || nchar == K_LEFT - || nchar == K_RIGHT) { - cap->count1 = n ? n * cap->count1 : cap->count1; - goto dozet; - } else { - clearopbeep(cap->oap); - break; - } - } - cap->oap->op_type = OP_NOP; + if (ascii_isdigit(nchar) && !nv_z_get_count(cap, &nchar)) { return; } -dozet: // "zf" and "zF" are always an operator, "zd", "zo", "zO", "zc" // and "zC" only in Visual mode. "zj" and "zk" are motion // commands. @@ -3820,60 +3944,14 @@ dozet: break; case 'u': // "zug" and "zuw": undo "zg" and "zw" - no_mapping++; - allow_keys++; // no mapping for nchar, but allow key codes - nchar = plain_vgetc(); - LANGMAP_ADJUST(nchar, true); - no_mapping--; - allow_keys--; - (void)add_to_showcmd(nchar); - if (vim_strchr("gGwW", nchar) == NULL) { - clearopbeep(cap->oap); - break; - } - undo = true; - FALLTHROUGH; - case 'g': // "zg": add good word to word list case 'w': // "zw": add wrong word to word list case 'G': // "zG": add good word to temp word list case 'W': // "zW": add wrong word to temp word list - { - char_u *ptr = NULL; - size_t len; - - if (checkclearop(cap->oap)) { - break; - } - if (VIsual_active && !get_visual_text(cap, &ptr, &len)) { - return; - } - if (ptr == NULL) { - pos_T pos = curwin->w_cursor; - - // Find bad word under the cursor. When 'spell' is - // off this fails and find_ident_under_cursor() is - // used below. - emsg_off++; - len = spell_move_to(curwin, FORWARD, true, true, NULL); - emsg_off--; - if (len != 0 && curwin->w_cursor.col <= pos.col) { - ptr = ml_get_pos(&curwin->w_cursor); - } - curwin->w_cursor = pos; - } - - if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { + if (nv_zg_zw(cap, nchar) == FAIL) { return; } - assert(len <= INT_MAX); - spell_add_word(ptr, (int)len, - nchar == 'w' || nchar == 'W' - ? SPELL_ADD_BAD : SPELL_ADD_GOOD, - (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, - undo); - } - break; + break; case '=': // "z=": suggestions for a badly spelled word if (!checkclearop(cap->oap)) { @@ -4068,6 +4146,69 @@ void do_nv_ident(int c1, int c2) nv_ident(&ca); } +/// 'K' normal-mode command. Get the command to lookup the keyword under the +/// cursor. +static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char_u **ptr_arg, + size_t n, char *buf, size_t buf_size) +{ + if (kp_help) { + // in the help buffer + STRCPY(buf, "he! "); + return n; + } + + if (kp_ex) { + // 'keywordprg' is an ex command + if (cap->count0 != 0) { // Send the count to the ex command. + snprintf(buf, buf_size, "%" PRId64, (int64_t)(cap->count0)); + } + STRCAT(buf, kp); + STRCAT(buf, " "); + return n; + } + + char_u *ptr = *ptr_arg; + + // An external command will probably use an argument starting + // with "-" as an option. To avoid trouble we skip the "-". + while (*ptr == '-' && n > 0) { + ptr++; + n--; + } + if (n == 0) { + // found dashes only + emsg(_(e_noident)); + xfree(buf); + *ptr_arg = ptr; + return 0; + } + + // When a count is given, turn it into a range. Is this + // really what we want? + bool isman = (STRCMP(kp, "man") == 0); + bool isman_s = (STRCMP(kp, "man -s") == 0); + if (cap->count0 != 0 && !(isman || isman_s)) { + snprintf(buf, buf_size, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); + } + + do_cmdline_cmd("tabnew"); + STRCAT(buf, "terminal "); + if (cap->count0 == 0 && isman_s) { + STRCAT(buf, "man"); + } else { + STRCAT(buf, kp); + } + STRCAT(buf, " "); + if (cap->count0 != 0 && (isman || isman_s)) { + snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64, + (int64_t)cap->count0); + STRCAT(buf, " "); + } + + *ptr_arg = ptr; + return n; +} + /// Handle the commands that use the word under the cursor. /// [g] CTRL-] :ta to current identifier /// [g] 'K' run program for current identifier @@ -4147,48 +4288,9 @@ static void nv_ident(cmdarg_T *cap) break; case 'K': - if (kp_help) { - STRCPY(buf, "he! "); - } else if (kp_ex) { - if (cap->count0 != 0) { // Send the count to the ex command. - snprintf(buf, buf_size, "%" PRId64, (int64_t)(cap->count0)); - } - STRCAT(buf, kp); - STRCAT(buf, " "); - } else { - // An external command will probably use an argument starting - // with "-" as an option. To avoid trouble we skip the "-". - while (*ptr == '-' && n > 0) { - ptr++; - n--; - } - if (n == 0) { - emsg(_(e_noident)); // found dashes only - xfree(buf); - return; - } - - // When a count is given, turn it into a range. Is this - // really what we want? - bool isman = (STRCMP(kp, "man") == 0); - bool isman_s = (STRCMP(kp, "man -s") == 0); - if (cap->count0 != 0 && !(isman || isman_s)) { - snprintf(buf, buf_size, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); - } - - do_cmdline_cmd("tabnew"); - STRCAT(buf, "terminal "); - if (cap->count0 == 0 && isman_s) { - STRCAT(buf, "man"); - } else { - STRCAT(buf, kp); - } - STRCAT(buf, " "); - if (cap->count0 != 0 && (isman || isman_s)) { - snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64, - (int64_t)cap->count0); - STRCAT(buf, " "); - } + n = nv_K_getcmd(cap, kp, kp_help, kp_ex, &ptr, n, buf, buf_size); + if (n == 0) { + return; } break; @@ -4219,7 +4321,7 @@ static void nv_ident(cmdarg_T *cap) ptr = vim_strnsave(ptr, n); if (kp_ex) { // Escape the argument properly for an Ex command - p = (char_u *)vim_strsave_fnameescape((const char *)ptr, false); + p = (char_u *)vim_strsave_fnameescape((const char *)ptr, VSE_NONE); } else { // Escape the argument properly for a shell command p = vim_strsave_shellescape(ptr, true, true); @@ -4420,7 +4522,6 @@ static void nv_scroll(cmdarg_T *cap) static void nv_right(cmdarg_T *cap) { long n; - int PAST_LINE; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { // <C-Right> and <S-Right> move a word or WORD right @@ -4433,20 +4534,19 @@ static void nv_right(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - PAST_LINE = (VIsual_active && *p_sel != 'o'); + bool past_line = (VIsual_active && *p_sel != 'o'); - // In virtual mode, there's no such thing as "PAST_LINE", as lines are - // (theoretically) infinitely long. + // In virtual edit mode, there's no such thing as "past_line", as lines + // are (theoretically) infinitely long. if (virtual_active()) { - PAST_LINE = 0; + past_line = false; } for (n = cap->count1; n > 0; n--) { - if ((!PAST_LINE && oneright() == false) - || (PAST_LINE - && *get_cursor_pos_ptr() == NUL)) { - // <Space> wraps to next line if 'whichwrap' has 's'. - // 'l' wraps to next line if 'whichwrap' has 'l'. + if ((!past_line && oneright() == false) + || (past_line && *get_cursor_pos_ptr() == NUL)) { + // <Space> wraps to next line if 'whichwrap' has 's'. + // 'l' wraps to next line if 'whichwrap' has 'l'. // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. if (((cap->cmdchar == ' ' && vim_strchr((char *)p_ww, 's') != NULL) || (cap->cmdchar == 'l' && vim_strchr((char *)p_ww, 'l') != NULL) @@ -4479,7 +4579,7 @@ static void nv_right(cmdarg_T *cap) } } break; - } else if (PAST_LINE) { + } else if (past_line) { curwin->w_set_curswant = true; if (virtual_active()) { oneright(); @@ -4610,9 +4710,7 @@ static void nv_gotofile(cmdarg_T *cap) char_u *ptr; linenr_T lnum = -1; - if (text_locked()) { - clearopbeep(cap->oap); - text_locked_msg(); + if (check_text_locked(cap->oap)) { return; } if (curbuf_locked()) { @@ -4795,18 +4893,131 @@ static void nv_csearch(cmdarg_T *cap) } } +/// "[{", "[(", "]}" or "])": go to Nth unclosed '{', '(', '}' or ')' +/// "[#", "]#": go to start/end of Nth innermost #if..#endif construct. +/// "[/", "[*", "]/", "]*": go to Nth comment start/end. +/// "[m" or "]m" search for prev/next start of (Java) method. +/// "[M" or "]M" search for prev/next end of (Java) method. +static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos) +{ + pos_T new_pos = { 0, 0, 0 }; + pos_T *pos = NULL; // init for GCC + pos_T prev_pos; + long n; + int findc; + int c; + + if (cap->nchar == '*') { + cap->nchar = '/'; + } + prev_pos.lnum = 0; + if (cap->nchar == 'm' || cap->nchar == 'M') { + if (cap->cmdchar == '[') { + findc = '{'; + } else { + findc = '}'; + } + n = 9999; + } else { + findc = cap->nchar; + n = cap->count1; + } + for (; n > 0; n--) { + if ((pos = findmatchlimit(cap->oap, findc, + (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, 0)) == NULL) { + if (new_pos.lnum == 0) { // nothing found + if (cap->nchar != 'm' && cap->nchar != 'M') { + clearopbeep(cap->oap); + } + } else { + pos = &new_pos; // use last one found + } + break; + } + prev_pos = new_pos; + curwin->w_cursor = *pos; + new_pos = *pos; + } + curwin->w_cursor = *old_pos; + + // Handle "[m", "]m", "[M" and "[M". The findmatchlimit() only + // brought us to the match for "[m" and "]M" when inside a method. + // Try finding the '{' or '}' we want to be at. + // Also repeat for the given count. + if (cap->nchar == 'm' || cap->nchar == 'M') { + // norm is true for "]M" and "[m" + int norm = ((findc == '{') == (cap->nchar == 'm')); + + n = cap->count1; + // found a match: we were inside a method + if (prev_pos.lnum != 0) { + pos = &prev_pos; + curwin->w_cursor = prev_pos; + if (norm) { + n--; + } + } else { + pos = NULL; + } + while (n > 0) { + for (;;) { + if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0) { + // if not found anything, that's an error + if (pos == NULL) { + clearopbeep(cap->oap); + } + n = 0; + break; + } + c = gchar_cursor(); + if (c == '{' || c == '}') { + // Must have found end/start of class: use it. + // Or found the place to be at. + if ((c == findc && norm) || (n == 1 && !norm)) { + new_pos = curwin->w_cursor; + pos = &new_pos; + n = 0; + } else if (new_pos.lnum == 0) { + // if no match found at all, we started outside of the + // class and we're inside now. Just go on. + new_pos = curwin->w_cursor; + pos = &new_pos; + } else if ((pos = findmatchlimit(cap->oap, findc, + (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, + 0)) == NULL) { + // found start/end of other method: go to match + n = 0; + } else { + curwin->w_cursor = *pos; + } + break; + } + } + n--; + } + curwin->w_cursor = *old_pos; + if (pos == NULL && new_pos.lnum != 0) { + clearopbeep(cap->oap); + } + } + if (pos != NULL) { + setpcmark(); + curwin->w_cursor = *pos; + curwin->w_set_curswant = true; + if ((fdo_flags & FDO_BLOCK) && KeyTyped + && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); + } + } +} + /// "[" and "]" commands. /// cap->arg is BACKWARD for "[" and FORWARD for "]". static void nv_brackets(cmdarg_T *cap) { - pos_T new_pos = { 0, 0, 0 }; - pos_T prev_pos; - pos_T *pos = NULL; // init for GCC pos_T old_pos; // cursor position before command int flag; long n; - int findc; - int c; cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; @@ -4822,7 +5033,7 @@ static void nv_brackets(cmdarg_T *cap) // // search list jump // fwd bwd fwd bwd fwd bwd - // identifier "]i" "[i" "]I" "[I" "]^I" "[^I" + // identifier "]i" "[i" "]I" "[I" "]^I" "[^I" // define "]d" "[d" "]D" "[D" "]^D" "[^D" char_u *ptr; size_t len; @@ -4855,108 +5066,7 @@ static void nv_brackets(cmdarg_T *cap) // "[/", "[*", "]/", "]*": go to Nth comment start/end. // "[m" or "]m" search for prev/next start of (Java) method. // "[M" or "]M" search for prev/next end of (Java) method. - if (cap->nchar == '*') { - cap->nchar = '/'; - } - prev_pos.lnum = 0; - if (cap->nchar == 'm' || cap->nchar == 'M') { - if (cap->cmdchar == '[') { - findc = '{'; - } else { - findc = '}'; - } - n = 9999; - } else { - findc = cap->nchar; - n = cap->count1; - } - for (; n > 0; n--) { - if ((pos = findmatchlimit(cap->oap, findc, - (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, 0)) == NULL) { - if (new_pos.lnum == 0) { // nothing found - if (cap->nchar != 'm' && cap->nchar != 'M') { - clearopbeep(cap->oap); - } - } else { - pos = &new_pos; // use last one found - } - break; - } - prev_pos = new_pos; - curwin->w_cursor = *pos; - new_pos = *pos; - } - curwin->w_cursor = old_pos; - - // Handle "[m", "]m", "[M" and "[M". The findmatchlimit() only - // brought us to the match for "[m" and "]M" when inside a method. - // Try finding the '{' or '}' we want to be at. - // Also repeat for the given count. - if (cap->nchar == 'm' || cap->nchar == 'M') { - // norm is true for "]M" and "[m" - int norm = ((findc == '{') == (cap->nchar == 'm')); - - n = cap->count1; - // found a match: we were inside a method - if (prev_pos.lnum != 0) { - pos = &prev_pos; - curwin->w_cursor = prev_pos; - if (norm) { - n--; - } - } else { - pos = NULL; - } - while (n > 0) { - for (;;) { - if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0) { - // if not found anything, that's an error - if (pos == NULL) { - clearopbeep(cap->oap); - } - n = 0; - break; - } - c = gchar_cursor(); - if (c == '{' || c == '}') { - // Must have found end/start of class: use it. - // Or found the place to be at. - if ((c == findc && norm) || (n == 1 && !norm)) { - new_pos = curwin->w_cursor; - pos = &new_pos; - n = 0; - } else if (new_pos.lnum == 0) { - // if no match found at all, we started outside of the - // class and we're inside now. Just go on. - new_pos = curwin->w_cursor; - pos = &new_pos; - } else if ((pos = findmatchlimit(cap->oap, findc, - (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, - 0)) == NULL) { - // found start/end of other method: go to match - n = 0; - } else { - curwin->w_cursor = *pos; - } - break; - } - } - n--; - } - curwin->w_cursor = old_pos; - if (pos == NULL && new_pos.lnum != 0) { - clearopbeep(cap->oap); - } - } - if (pos != NULL) { - setpcmark(); - curwin->w_cursor = *pos; - curwin->w_set_curswant = true; - if ((fdo_flags & FDO_BLOCK) && KeyTyped - && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } - } + nv_bracket_block(cap, &old_pos); } else if (cap->nchar == '[' || cap->nchar == ']') { // "[[", "[]", "]]" and "][": move to start or end of function if (cap->nchar == cap->cmdchar) { // "]]" or "[[" @@ -4983,19 +5093,22 @@ static void nv_brackets(cmdarg_T *cap) nv_put_opt(cap, true); } else if (cap->nchar == '\'' || cap->nchar == '`') { // "['", "[`", "]'" and "]`": jump to next mark - pos = &curwin->w_cursor; + fmark_T *fm = pos_to_mark(curbuf, NULL, curwin->w_cursor); + fmark_T *prev_fm; for (n = cap->count1; n > 0; n--) { - prev_pos = *pos; - pos = getnextmark(pos, cap->cmdchar == '[' ? BACKWARD : FORWARD, - cap->nchar == '\''); - if (pos == NULL) { + prev_fm = fm; + fm = getnextmark(&fm->mark, cap->cmdchar == '[' ? BACKWARD : FORWARD, + cap->nchar == '\''); + if (fm == NULL) { break; } } - if (pos == NULL) { - pos = &prev_pos; + if (fm == NULL) { + fm = prev_fm; } - nv_cursormark(cap, cap->nchar == '\'', pos); + MarkMove flags = kMarkContext; + flags |= cap->nchar == '\'' ? kMarkBeginLine: 0; + nv_mark_move_to(cap, flags, fm); } else if (cap->nchar >= K_RIGHTRELEASE && cap->nchar <= K_LEFTMOUSE) { // [ or ] followed by a middle mouse click: put selected text with // indent adjustment. Any other button just does as usual. @@ -5465,31 +5578,28 @@ static void n_swapchar(cmdarg_T *cap) } } -/// Move cursor to mark. -static void nv_cursormark(cmdarg_T *cap, int flag, pos_T *pos) -{ - if (check_mark(pos) == false) { +/// Move the cursor to the mark position +/// +/// Wrapper to mark_move_to() that also handles normal mode command arguments. +/// @note It will switch the buffer if neccesarry, move the cursor and set the +/// view depending on the given flags. +/// @param cap command line arguments +/// @param flags for mark_move_to() +/// @param mark mark +/// @return The result of calling mark_move_to() +static MarkMoveRes nv_mark_move_to(cmdarg_T *cap, MarkMove flags, fmark_T *fm) +{ + MarkMoveRes res = mark_move_to(fm, flags); + if (res & kMarkMoveFailed) { clearop(cap->oap); - } else { - if (cap->cmdchar == '\'' - || cap->cmdchar == '`' - || cap->cmdchar == '[' - || cap->cmdchar == ']') { - setpcmark(); - } - curwin->w_cursor = *pos; - if (flag) { - beginline(BL_WHITE | BL_FIX); - } else { - check_cursor(); - } } - cap->oap->motion_type = flag ? kMTLineWise : kMTCharWise; + cap->oap->motion_type = flags & kMarkBeginLine ? kMTLineWise : kMTCharWise; if (cap->cmdchar == '`') { cap->oap->use_reg_one = true; } cap->oap->inclusive = false; // ignored if not kMTCharWise curwin->w_set_curswant = true; + return res; } /// Handle commands that are operators in Visual mode. @@ -5564,36 +5674,32 @@ static void nv_optrans(cmdarg_T *cap) /// cap->arg is true for "'" and "g'". static void nv_gomark(cmdarg_T *cap) { - pos_T *pos; - int c; - pos_T old_cursor = curwin->w_cursor; - const bool old_KeyTyped = KeyTyped; // getting file may reset it + int name; + MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + MarkMoveRes move_res = 0; // Result from moving to the mark + const bool old_KeyTyped = KeyTyped; // getting file may reset it if (cap->cmdchar == 'g') { - c = cap->extra_char; + name = cap->extra_char; + flags |= KMarkNoContext; } else { - c = cap->nchar; - } - pos = getmark(c, (cap->oap->op_type == OP_NOP)); - if (pos == (pos_T *)-1) { // jumped to other file - if (cap->arg) { - check_cursor_lnum(); - beginline(BL_WHITE | BL_FIX); - } else { - check_cursor(); - } - } else { - nv_cursormark(cap, cap->arg, pos); + name = cap->nchar; + flags |= kMarkContext; } + flags |= cap->arg ? kMarkBeginLine : 0; + flags |= cap->count0 ? kMarkSetView : 0; + + fmark_T *fm = mark_get(curbuf, curwin, NULL, kMarkAll, name); + move_res = nv_mark_move_to(cap, flags, fm); // May need to clear the coladd that a mark includes. if (!virtual_active()) { curwin->w_cursor.coladd = 0; } - check_cursor_col(); + if (cap->oap->op_type == OP_NOP - && pos != NULL - && (pos == (pos_T *)-1 || !equalpos(old_cursor, *pos)) + && move_res & kMarkMoveSuccess + && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedCursor) && (fdo_flags & FDO_MARK) && old_KeyTyped) { foldOpenCursor(); @@ -5601,11 +5707,13 @@ static void nv_gomark(cmdarg_T *cap) } /// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. +/// Movement in the jumplist and changelist. static void nv_pcmark(cmdarg_T *cap) { - pos_T *pos; - linenr_T lnum = curwin->w_cursor.lnum; - const bool old_KeyTyped = KeyTyped; // getting file may reset it + fmark_T *fm = NULL; + MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + MarkMoveRes move_res = 0; // Result from moving to the mark + const bool old_KeyTyped = KeyTyped; // getting file may reset it. if (!checkclearopq(cap->oap)) { if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { @@ -5614,16 +5722,18 @@ static void nv_pcmark(cmdarg_T *cap) } return; } + if (cap->cmdchar == 'g') { - pos = movechangelist((int)cap->count1); + fm = get_changelist(curbuf, curwin, (int)cap->count1); } else { - pos = movemark((int)cap->count1); - } - if (pos == (pos_T *)-1) { // jump to other file - curwin->w_set_curswant = true; - check_cursor(); - } else if (pos != NULL) { // can jump - nv_cursormark(cap, false, pos); + fm = get_jumplist(curwin, (int)cap->count1); + flags |= KMarkNoContext | kMarkJumpList; + } + // Changelist and jumplist have their own error messages. Therefore avoid + // calling nv_mark_move_to() when not found to avoid incorrect error + // messages. + if (fm != NULL) { + move_res = nv_mark_move_to(cap, flags, fm); } else if (cap->cmdchar == 'g') { if (curbuf->b_changelistlen == 0) { emsg(_("E664: changelist is empty")); @@ -5636,7 +5746,7 @@ static void nv_pcmark(cmdarg_T *cap) clearopbeep(cap->oap); } if (cap->oap->op_type == OP_NOP - && (pos == (pos_T *)-1 || lnum != curwin->w_cursor.lnum) + && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine) && (fdo_flags & FDO_MARK) && old_KeyTyped) { foldOpenCursor(); @@ -5831,13 +5941,217 @@ static void nv_suspend(cmdarg_T *cap) do_cmdline_cmd("st"); } +/// "gv": Reselect the previous Visual area. If Visual already active, +/// exchange previous and current Visual area. +static void nv_gv_cmd(cmdarg_T *cap) +{ + if (checkclearop(cap->oap)) { + return; + } + + if (curbuf->b_visual.vi_start.lnum == 0 + || curbuf->b_visual.vi_start.lnum > curbuf->b_ml.ml_line_count + || curbuf->b_visual.vi_end.lnum == 0) { + beep_flush(); + return; + } + + pos_T tpos; + // set w_cursor to the start of the Visual area, tpos to the end + if (VIsual_active) { + int i = VIsual_mode; + VIsual_mode = curbuf->b_visual.vi_mode; + curbuf->b_visual.vi_mode = i; + curbuf->b_visual_mode_eval = i; + i = curwin->w_curswant; + curwin->w_curswant = curbuf->b_visual.vi_curswant; + curbuf->b_visual.vi_curswant = i; + + tpos = curbuf->b_visual.vi_end; + curbuf->b_visual.vi_end = curwin->w_cursor; + curwin->w_cursor = curbuf->b_visual.vi_start; + curbuf->b_visual.vi_start = VIsual; + } else { + VIsual_mode = curbuf->b_visual.vi_mode; + curwin->w_curswant = curbuf->b_visual.vi_curswant; + tpos = curbuf->b_visual.vi_end; + curwin->w_cursor = curbuf->b_visual.vi_start; + } + + VIsual_active = true; + VIsual_reselect = true; + + // Set Visual to the start and w_cursor to the end of the Visual + // area. Make sure they are on an existing character. + check_cursor(); + VIsual = curwin->w_cursor; + curwin->w_cursor = tpos; + check_cursor(); + update_topline(curwin); + + // When called from normal "g" command: start Select mode when + // 'selectmode' contains "cmd". When called for K_SELECT, always + // start Select mode. + if (cap->arg) { + VIsual_select = true; + VIsual_select_reg = 0; + } else { + may_start_select('c'); + } + setmouse(); + redraw_curbuf_later(INVERTED); + showmode(); +} + +/// "g0", "g^" : Like "0" and "^" but for screen lines. +/// "gm": middle of "g0" and "g$". +static void nv_g_home_m_cmd(cmdarg_T *cap) +{ + int i; + const bool flag = cap->nchar == '^'; + + cap->oap->motion_type = kMTCharWise; + cap->oap->inclusive = false; + if (curwin->w_p_wrap && curwin->w_width_inner != 0) { + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + + validate_virtcol(); + i = 0; + if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) { + i = (curwin->w_virtcol - width1) / width2 * width2 + width1; + } + } else { + i = curwin->w_leftcol; + } + // Go to the middle of the screen line. When 'number' or + // 'relativenumber' is on and lines are wrapping the middle can be more + // to the left. + if (cap->nchar == 'm') { + i += (curwin->w_width_inner - curwin_col_off() + + ((curwin->w_p_wrap && i > 0) ? curwin_col_off2() : 0)) / 2; + } + coladvance((colnr_T)i); + if (flag) { + do { + i = gchar_cursor(); + } while (ascii_iswhite(i) && oneright()); + curwin->w_valid &= ~VALID_WCOL; + } + curwin->w_set_curswant = true; +} + +/// "g_": to the last non-blank character in the line or <count> lines downward. +static void nv_g_underscore_cmd(cmdarg_T *cap) +{ + cap->oap->motion_type = kMTCharWise; + cap->oap->inclusive = true; + curwin->w_curswant = MAXCOL; + if (cursor_down(cap->count1 - 1, cap->oap->op_type == OP_NOP) == false) { + clearopbeep(cap->oap); + return; + } + + char_u *ptr = get_cursor_line_ptr(); + + // In Visual mode we may end up after the line. + if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) { + curwin->w_cursor.col--; + } + + // Decrease the cursor column until it's on a non-blank. + while (curwin->w_cursor.col > 0 && ascii_iswhite(ptr[curwin->w_cursor.col])) { + curwin->w_cursor.col--; + } + curwin->w_set_curswant = true; + adjust_for_sel(cap); +} + +/// "g$" : Like "$" but for screen lines. +static void nv_g_dollar_cmd(cmdarg_T *cap) +{ + oparg_T *oap = cap->oap; + int i; + int col_off = curwin_col_off(); + + oap->motion_type = kMTCharWise; + oap->inclusive = true; + if (curwin->w_p_wrap && curwin->w_width_inner != 0) { + curwin->w_curswant = MAXCOL; // so we stay at the end + if (cap->count1 == 1) { + int width1 = curwin->w_width_inner - col_off; + int width2 = width1 + curwin_col_off2(); + + validate_virtcol(); + i = width1 - 1; + if (curwin->w_virtcol >= (colnr_T)width1) { + i += ((curwin->w_virtcol - width1) / width2 + 1) * width2; + } + coladvance((colnr_T)i); + + // Make sure we stick in this column. + validate_virtcol(); + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = false; + if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { + // Check for landing on a character that got split at + // the end of the line. We do not want to advance to + // the next screen line. + if (curwin->w_virtcol > (colnr_T)i) { + curwin->w_cursor.col--; + } + } + } else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) { + clearopbeep(oap); + } + } else { + if (cap->count1 > 1) { + // if it fails, let the cursor still move to the last char + (void)cursor_down(cap->count1 - 1, false); + } + i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; + coladvance((colnr_T)i); + + // if the character doesn't fit move one back + if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) { + colnr_T vcol; + + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) { + curwin->w_cursor.col--; + } + } + + // Make sure we stick in this column. + validate_virtcol(); + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = false; + } +} + +/// "gi": start Insert at the last position. +static void nv_gi_cmd(cmdarg_T *cap) +{ + if (curbuf->b_last_insert.mark.lnum != 0) { + curwin->w_cursor = curbuf->b_last_insert.mark; + check_cursor_lnum(); + int i = (int)STRLEN(get_cursor_line_ptr()); + if (curwin->w_cursor.col > (colnr_T)i) { + if (virtual_active()) { + curwin->w_cursor.coladd += curwin->w_cursor.col - i; + } + curwin->w_cursor.col = i; + } + } + cap->cmdchar = 'i'; + nv_edit(cap); +} + /// Commands starting with "g". static void nv_g_cmd(cmdarg_T *cap) { oparg_T *oap = cap->oap; - pos_T tpos; int i; - bool flag = false; switch (cap->nchar) { // "g^A/g^X": Sequentially increment visually selected region. @@ -5868,61 +6182,9 @@ static void nv_g_cmd(cmdarg_T *cap) break; // "gv": Reselect the previous Visual area. If Visual already active, - // exchange previous and current Visual area. + // exchange previous and current Visual area. case 'v': - if (checkclearop(oap)) { - break; - } - - if (curbuf->b_visual.vi_start.lnum == 0 - || curbuf->b_visual.vi_start.lnum > curbuf->b_ml.ml_line_count - || curbuf->b_visual.vi_end.lnum == 0) { - beep_flush(); - } else { - // set w_cursor to the start of the Visual area, tpos to the end - if (VIsual_active) { - i = VIsual_mode; - VIsual_mode = curbuf->b_visual.vi_mode; - curbuf->b_visual.vi_mode = i; - curbuf->b_visual_mode_eval = i; - i = curwin->w_curswant; - curwin->w_curswant = curbuf->b_visual.vi_curswant; - curbuf->b_visual.vi_curswant = i; - - tpos = curbuf->b_visual.vi_end; - curbuf->b_visual.vi_end = curwin->w_cursor; - curwin->w_cursor = curbuf->b_visual.vi_start; - curbuf->b_visual.vi_start = VIsual; - } else { - VIsual_mode = curbuf->b_visual.vi_mode; - curwin->w_curswant = curbuf->b_visual.vi_curswant; - tpos = curbuf->b_visual.vi_end; - curwin->w_cursor = curbuf->b_visual.vi_start; - } - - VIsual_active = true; - VIsual_reselect = true; - - // Set Visual to the start and w_cursor to the end of the Visual - // area. Make sure they are on an existing character. - check_cursor(); - VIsual = curwin->w_cursor; - curwin->w_cursor = tpos; - check_cursor(); - update_topline(curwin); - // When called from normal "g" command: start Select mode when - // 'selectmode' contains "cmd". When called for K_SELECT, always - // start Select mode. - if (cap->arg) { - VIsual_select = true; - VIsual_select_reg = 0; - } else { - may_start_select('c'); - } - setmouse(); - redraw_curbuf_later(INVERTED); - showmode(); - } + nv_gv_cmd(cap); break; // "gV": Don't reselect the previous Visual area after a Select mode mapping of menu. case 'V': @@ -5988,47 +6250,14 @@ static void nv_g_cmd(cmdarg_T *cap) nv_join(cap); break; - // "g0", "g^" and "g$": Like "0", "^" and "$" but for screen lines. + // "g0", "g^" : Like "0" and "^" but for screen lines. // "gm": middle of "g0" and "g$". case '^': - flag = true; - FALLTHROUGH; - case '0': case 'm': case K_HOME: case K_KHOME: - oap->motion_type = kMTCharWise; - oap->inclusive = false; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { - int width1 = curwin->w_width_inner - curwin_col_off(); - int width2 = width1 + curwin_col_off2(); - - validate_virtcol(); - i = 0; - if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) { - i = (curwin->w_virtcol - width1) / width2 * width2 + width1; - } - } else { - i = curwin->w_leftcol; - } - // Go to the middle of the screen line. When 'number' or - // 'relativenumber' is on and lines are wrapping the middle can be more - // to the left. - if (cap->nchar == 'm') { - i += (curwin->w_width_inner - curwin_col_off() - + ((curwin->w_p_wrap && i > 0) - ? curwin_col_off2() : 0)) / 2; - } - coladvance((colnr_T)i); - if (flag) { - do { - i = gchar_cursor(); - } while (ascii_iswhite(i) && oneright()); - curwin->w_valid &= ~VALID_WCOL; - } - curwin->w_set_curswant = true; + nv_g_home_m_cmd(cap); break; case 'M': @@ -6043,84 +6272,17 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; + // "g_": to the last non-blank character in the line or <count> lines downward. case '_': - // "g_": to the last non-blank character in the line or <count> lines downward. - cap->oap->motion_type = kMTCharWise; - cap->oap->inclusive = true; - curwin->w_curswant = MAXCOL; - if (cursor_down(cap->count1 - 1, - cap->oap->op_type == OP_NOP) == false) { - clearopbeep(cap->oap); - } else { - char_u *ptr = get_cursor_line_ptr(); - - // In Visual mode we may end up after the line. - if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) { - curwin->w_cursor.col--; - } - - // Decrease the cursor column until it's on a non-blank. - while (curwin->w_cursor.col > 0 - && ascii_iswhite(ptr[curwin->w_cursor.col])) { - curwin->w_cursor.col--; - } - curwin->w_set_curswant = true; - adjust_for_sel(cap); - } + nv_g_underscore_cmd(cap); break; + // "g$" : Like "$" but for screen lines. case '$': case K_END: - case K_KEND: { - int col_off = curwin_col_off(); - - oap->motion_type = kMTCharWise; - oap->inclusive = true; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { - curwin->w_curswant = MAXCOL; // so we stay at the end - if (cap->count1 == 1) { - int width1 = curwin->w_width_inner - col_off; - int width2 = width1 + curwin_col_off2(); - - validate_virtcol(); - i = width1 - 1; - if (curwin->w_virtcol >= (colnr_T)width1) { - i += ((curwin->w_virtcol - width1) / width2 + 1) - * width2; - } - coladvance((colnr_T)i); - - // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; - if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { - // Check for landing on a character that got split at - // the end of the line. We do not want to advance to - // the next screen line. - if (curwin->w_virtcol > (colnr_T)i) { - curwin->w_cursor.col--; - } - } - } else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) { - clearopbeep(oap); - } - } else { - if (cap->count1 > 1) { - // if it fails, let the cursor still move to the last char - (void)cursor_down(cap->count1 - 1, false); - } - i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; - coladvance((colnr_T)i); - - // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; - } - } - break; + case K_KEND: + nv_g_dollar_cmd(cap); + break; // "g*" and "g#", like "*" and "#" but without using "\<" and "\>" case '*': @@ -6151,19 +6313,7 @@ static void nv_g_cmd(cmdarg_T *cap) // "gi": start Insert at the last position. case 'i': - if (curbuf->b_last_insert.mark.lnum != 0) { - curwin->w_cursor = curbuf->b_last_insert.mark; - check_cursor_lnum(); - i = (int)STRLEN(get_cursor_line_ptr()); - if (curwin->w_cursor.col > (colnr_T)i) { - if (virtual_active()) { - curwin->w_cursor.coladd += curwin->w_cursor.col - i; - } - curwin->w_cursor.col = i; - } - } - cap->cmdchar = 'i'; - nv_edit(cap); + nv_gi_cmd(cap); break; // "gI": Start insert in column 1. @@ -6222,14 +6372,14 @@ static void nv_g_cmd(cmdarg_T *cap) nv_goto(cap); break; - // Two-character operators: - // "gq" Format text - // "gw" Format text and keep cursor position - // "g~" Toggle the case of the text. - // "gu" Change text to lower case. - // "gU" Change text to upper case. - // "g?" rot13 encoding - // "g@" call 'operatorfunc' + // Two-character operators: + // "gq" Format text + // "gw" Format text and keep cursor position + // "g~" Toggle the case of the text. + // "gu" Change text to lower case. + // "gU" Change text to upper case. + // "g?" rot13 encoding + // "g@" call 'operatorfunc' case 'q': case 'w': oap->cursor_start = curwin->w_cursor; @@ -6286,13 +6436,7 @@ static void nv_g_cmd(cmdarg_T *cap) // "gQ": improved Ex mode case 'Q': - if (text_locked()) { - clearopbeep(cap->oap); - text_locked_msg(); - break; - } - - if (!checkclearopq(oap)) { + if (!check_text_locked(cap->oap) && !checkclearopq(oap)) { do_exmode(); } break; @@ -6410,8 +6554,7 @@ static void nv_redo_or_register(cmdarg_T *cap) static void nv_Undo(cmdarg_T *cap) { // In Visual mode and typing "gUU" triggers an operator - if (cap->oap->op_type == OP_UPPER - || VIsual_active) { + if (cap->oap->op_type == OP_UPPER || VIsual_active) { // translate "gUU" to "gUgU" cap->cmdchar = 'g'; cap->nchar = 'U'; @@ -6426,9 +6569,7 @@ static void nv_Undo(cmdarg_T *cap) /// single character. static void nv_tilde(cmdarg_T *cap) { - if (!p_to - && !VIsual_active - && cap->oap->op_type != OP_TILDE) { + if (!p_to && !VIsual_active && cap->oap->op_type != OP_TILDE) { if (bt_prompt(curbuf) && !prompt_curpos_editable()) { clearopbeep(cap->oap); return; @@ -6804,7 +6945,6 @@ void set_cursor_for_append_to_line(void) curwin->w_set_curswant = true; if (get_ve_flags() == VE_ALL) { const int save_State = State; - // Pretend Insert mode here to allow the cursor on the // character past the end of the line State = MODE_INSERT; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 42beb2e16d..21ab26898e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1347,7 +1347,7 @@ bool get_spec_reg(int regname, char_u **argp, bool *allocated, bool errmsg) return true; case '#': // alternate file name - *argp = getaltfname(errmsg); // may give emsg if not set + *argp = (char_u *)getaltfname(errmsg); // may give emsg if not set return true; case '=': // result of expression @@ -1810,10 +1810,8 @@ setmarks: /// Used for deletion. static void mb_adjust_opend(oparg_T *oap) { - char_u *p; - if (oap->inclusive) { - p = ml_get(oap->end.lnum); + char *p = (char *)ml_get(oap->end.lnum); oap->end.col += mb_tail_off(p, p + oap->end.col); } } @@ -2791,7 +2789,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) xfree(reg->y_array); } - if (message) { // Display message about yank? + if (message && (p_ch > 0 || ui_has(kUIMessages))) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { yanklines = 0; } @@ -3886,12 +3884,12 @@ void ex_display(exarg_T *eap) // display alternate file name if ((arg == NULL || vim_strchr((char *)arg, '%') != NULL) && !got_int) { - char_u *fname; + char *fname; linenr_T dummy; - if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) { + if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered((char_u *)fname)) { msg_puts("\n c \"# "); - dis_msg(fname, false); + dis_msg((char_u *)fname, false); } } @@ -3950,7 +3948,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co { char_u *comment_flags = NULL; int lead_len; - int leader_offset = get_last_leader_offset(line, &comment_flags); + int leader_offset = get_last_leader_offset((char *)line, (char **)&comment_flags); *is_comment = false; if (leader_offset != -1) { @@ -3972,7 +3970,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co return line; } - lead_len = get_leader_len(line, &comment_flags, false, include_space); + lead_len = get_leader_len((char *)line, (char **)&comment_flags, false, include_space); if (lead_len == 0) { return line; @@ -4423,6 +4421,7 @@ void format_lines(linenr_T line_count, int avoid_fex) int smd_save; long count; bool need_set_indent = true; // set indent of next paragraph + linenr_T first_line = curwin->w_cursor.lnum; bool force_format = false; const int old_State = State; @@ -4549,9 +4548,24 @@ void format_lines(linenr_T line_count, int avoid_fex) */ if (is_end_par || force_format) { if (need_set_indent) { - // replace indent in first line with minimal number of - // tabs and spaces, according to current options - (void)set_indent(get_indent(), SIN_CHANGED); + int indent = 0; // amount of indent needed + + // Replace indent in first line of a paragraph with minimal + // number of tabs and spaces, according to current options. + // For the very first formatted line keep the current + // indent. + if (curwin->w_cursor.lnum == first_line) { + indent = get_indent(); + } else if (curbuf->b_p_lisp) { + indent = get_lisp_indent(); + } else { + if (cindent_on()) { + indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); + } else { + indent = get_indent(); + } + } + (void)set_indent(indent, SIN_CHANGED); } // put cursor on last non-space @@ -4654,7 +4668,7 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, ptr = ml_get(lnum); if (do_comments) { - *leader_len = get_leader_len(ptr, leader_flags, false, true); + *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); } else { *leader_len = 0; } @@ -5597,7 +5611,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a semsg(_(e_nobufnr), (int64_t)num); } } else { - buf = buflist_findnr(buflist_findpat(str, str + STRLEN(str), + buf = buflist_findnr(buflist_findpat((char *)str, (char *)str + STRLEN(str), true, false, false)); } if (buf == NULL) { @@ -5993,9 +6007,9 @@ void cursor_pos_info(dict_T *dict) } else { p = get_cursor_line_ptr(); validate_virtcol(); - col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, + col_print((char *)buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); - col_print(buf2, sizeof(buf2), (int)STRLEN(p), linetabsize(p)); + col_print((char *)buf2, sizeof(buf2), (int)STRLEN(p), linetabsize(p)); if (char_count_cursor == byte_count_cursor && char_count == byte_count) { @@ -6220,6 +6234,15 @@ static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) oap->start = curwin->w_cursor; } +/// Information for redoing the previous Visual selection. +typedef struct { + int rv_mode; ///< 'v', 'V', or Ctrl-V + linenr_T rv_line_count; ///< number of lines + colnr_T rv_vcol; ///< number of cols or end column + long rv_count; ///< count for Visual operator + int rv_arg; ///< extra argument +} redo_VIsual_T; + /// Handle an operator after Visual mode or when the movement is finished. /// "gui_yank" is true when yanking text for the clipboard. void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) @@ -6231,11 +6254,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) int lbr_saved = curwin->w_p_lbr; // The visual area is remembered for redo - static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V - static linenr_T redo_VIsual_line_count; // number of lines - static colnr_T redo_VIsual_vcol; // number of cols or end column - static long redo_VIsual_count; // count for Visual operator - static int redo_VIsual_arg; // extra argument + static redo_VIsual_T redo_VIsual = { NUL, 0, 0, 0, 0 }; + bool include_line_break = false; old_cursor = curwin->w_cursor; @@ -6318,28 +6338,27 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (redo_VIsual_busy) { // Redo of an operation on a Visual area. Use the same size from - // redo_VIsual_line_count and redo_VIsual_vcol. + // redo_VIsual.rv_line_count and redo_VIsual.rv_vcol. oap->start = curwin->w_cursor; - curwin->w_cursor.lnum += redo_VIsual_line_count - 1; + curwin->w_cursor.lnum += redo_VIsual.rv_line_count - 1; if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } - VIsual_mode = redo_VIsual_mode; - if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { + VIsual_mode = redo_VIsual.rv_mode; + if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v') { if (VIsual_mode == 'v') { - if (redo_VIsual_line_count <= 1) { + if (redo_VIsual.rv_line_count <= 1) { validate_virtcol(); - curwin->w_curswant = - curwin->w_virtcol + redo_VIsual_vcol - 1; + curwin->w_curswant = curwin->w_virtcol + redo_VIsual.rv_vcol - 1; } else { - curwin->w_curswant = redo_VIsual_vcol; + curwin->w_curswant = redo_VIsual.rv_vcol; } } else { curwin->w_curswant = MAXCOL; } coladvance(curwin->w_curswant); } - cap->count0 = redo_VIsual_count; + cap->count0 = redo_VIsual.rv_count; cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); } else if (VIsual_active) { if (!gui_yank) { @@ -6426,7 +6445,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) virtual_op = virtual_active(); if (VIsual_active || redo_VIsual_busy) { - get_op_vcol(oap, redo_VIsual_vcol, true); + get_op_vcol(oap, redo_VIsual.rv_vcol, true); if (!redo_VIsual_busy && !gui_yank) { // Prepare to reselect and redo Visual: this is based on the @@ -6471,6 +6490,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) get_op_char(oap->op_type), get_extra_op_char(oap->op_type), oap->motion_force, cap->cmdchar, cap->nchar); } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { + int opchar = get_op_char(oap->op_type); + int extra_opchar = get_extra_op_char(oap->op_type); int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; // reverse what nv_replace() did @@ -6479,15 +6500,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else if (nchar == REPLACE_NL_NCHAR) { nchar = NL; } - prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), nchar); + + if (opchar == 'g' && extra_opchar == '@') { + // also repeat the count for 'operatorfunc' + prep_redo_num2(oap->regname, 0L, NUL, 'v', cap->count0, opchar, extra_opchar, nchar); + } else { + prep_redo(oap->regname, 0L, NUL, 'v', opchar, extra_opchar, nchar); + } } if (!redo_VIsual_busy) { - redo_VIsual_mode = resel_VIsual_mode; - redo_VIsual_vcol = resel_VIsual_vcol; - redo_VIsual_line_count = resel_VIsual_line_count; - redo_VIsual_count = cap->count0; - redo_VIsual_arg = cap->arg; + redo_VIsual.rv_mode = resel_VIsual_mode; + redo_VIsual.rv_vcol = resel_VIsual_vcol; + redo_VIsual.rv_line_count = resel_VIsual_line_count; + redo_VIsual.rv_count = cap->count0; + redo_VIsual.rv_arg = cap->arg; } } @@ -6601,9 +6627,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) switch (oap->op_type) { case OP_LSHIFT: case OP_RSHIFT: - op_shift(oap, true, - oap->is_VIsual ? (int)cap->count1 : - 1); + op_shift(oap, true, oap->is_VIsual ? (int)cap->count1 : 1); auto_format(false, true); break; @@ -6737,12 +6761,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) op_format(oap, true); // use internal function break; - case OP_FUNCTION: + case OP_FUNCTION: { + redo_VIsual_T save_redo_VIsual = redo_VIsual; + // Restore linebreak, so that when the user edits it looks as // before. curwin->w_p_lbr = lbr_saved; - op_function(oap); // call 'operatorfunc' + // call 'operatorfunc' + op_function(oap); + + // Restore the info for redoing Visual mode, the function may + // invoke another operator and unintentionally change it. + redo_VIsual = save_redo_VIsual; break; + } case OP_INSERT: case OP_APPEND: @@ -6823,7 +6855,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { VIsual_active = true; curwin->w_p_lbr = lbr_saved; - op_addsub(oap, (linenr_T)cap->count1, redo_VIsual_arg); + op_addsub(oap, (linenr_T)cap->count1, redo_VIsual.rv_arg); VIsual_active = false; } check_cursor_col(); @@ -6961,7 +6993,7 @@ bool prepare_yankreg_from_object(yankreg_T *reg, String regtype, size_t lines) return false; } const char *p = regtype.data + 1; - reg->y_width = getdigits_int((char_u **)&p, false, 1) - 1; + reg->y_width = getdigits_int((char **)&p, false, 1) - 1; if (regtype.size > (size_t)(p - regtype.data)) { return false; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 09a597f7e2..821c7208e3 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1400,7 +1400,7 @@ int do_set(char *arg, int opt_flags) */ else if (varp == (char_u *)&p_bs && ascii_isdigit(**(char_u **)varp)) { - i = getdigits_int((char_u **)varp, true, 0); + i = getdigits_int((char **)varp, true, 0); switch (i) { case 0: *(char_u **)varp = empty_option; @@ -1435,7 +1435,7 @@ int do_set(char *arg, int opt_flags) else if (varp == (char_u *)&p_ww && ascii_isdigit(*arg)) { *errbuf = NUL; - i = getdigits_int((char_u **)&arg, true, 0); + i = getdigits_int(&arg, true, 0); if (i & 1) { STRLCAT(errbuf, "b,", sizeof(errbuf)); } @@ -1728,7 +1728,7 @@ skip: IObuff[i + ((char_u *)arg - startarg)] = NUL; } // make sure all characters are printable - trans_characters(IObuff, IOSIZE); + trans_characters((char *)IObuff, IOSIZE); no_wait_return++; // wait_return done later emsg((char *)IObuff); // show error highlighted @@ -2366,6 +2366,69 @@ static bool valid_spellfile(const char_u *val) return true; } +/// Handle setting 'mousescroll'. +/// @return error message, NULL if it's OK. +static char *check_mousescroll(char *string) +{ + long vertical = -1; + long horizontal = -1; + + for (;;) { + char *end = vim_strchr(string, ','); + size_t length = end ? (size_t)(end - string) : STRLEN(string); + + // Both "ver:" and "hor:" are 4 bytes long. + // They should be followed by at least one digit. + if (length <= 4) { + return e_invarg; + } + + long *direction; + + if (memcmp(string, "ver:", 4) == 0) { + direction = &vertical; + } else if (memcmp(string, "hor:", 4) == 0) { + direction = &horizontal; + } else { + return e_invarg; + } + + // If the direction has already been set, this is a duplicate. + if (*direction != -1) { + return e_invarg; + } + + // Verify that only digits follow the colon. + for (size_t i = 4; i < length; i++) { + if (!ascii_isdigit(string[i])) { + return N_("E548: digit expected"); + } + } + + string += 4; + *direction = getdigits_int(&string, false, -1); + + // Num options are generally kept within the signed int range. + // We know this number won't be negative because we've already checked for + // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. + if (*direction == -1) { + return e_invarg; + } + + if (!end) { + break; + } + + string = end + 1; + } + + // If a direction wasn't set, fallback to the default value. + p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; + p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; + + return NULL; +} + /// Handle string options that need some action to perform when changed. /// Returns NULL for success, or an error message for an error. /// @@ -2859,6 +2922,8 @@ ambw_end: if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { errmsg = e_invarg; } + } else if (varp == &p_mousescroll) { // 'mousescroll' + errmsg = check_mousescroll((char *)p_mousescroll); } else if (varp == &p_swb) { // 'switchbuf' if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { errmsg = e_invarg; @@ -2941,7 +3006,7 @@ ambw_end: if (*++s == '-') { // ignore a '-' s++; } - wid = getdigits_int(&s, true, 0); + wid = getdigits_int((char **)&s, true, 0); if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) { ru_wid = wid; } else { @@ -3457,7 +3522,7 @@ char *check_colorcolumn(win_T *wp) if (!ascii_isdigit(*s)) { return e_invarg; } - col = col * getdigits_int(&s, true, 0); + col = col * getdigits_int((char **)&s, true, 0); if (wp->w_buffer->b_p_tw == 0) { goto skip; // 'textwidth' not set, skip this item } @@ -3472,7 +3537,7 @@ char *check_colorcolumn(win_T *wp) goto skip; } } else if (ascii_isdigit(*s)) { - col = getdigits_int(&s, true, 0); + col = getdigits_int((char **)&s, true, 0); } else { return e_invarg; } @@ -4278,7 +4343,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - BOOLEAN_OBJ(value)); + BOOLEAN_OBJ(*varp)); } comp_col(); // in case 'ruler' or 'showcmd' changed @@ -4719,7 +4784,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - INTEGER_OBJ(value)); + INTEGER_OBJ(*pp)); } comp_col(); // in case 'columns' or 'ls' changed @@ -5650,7 +5715,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 if (fprintf(fd, "%s %s+=", cmd, name) < 0) { goto fail; } - (void)copy_option_part(&p, part, size, ","); + (void)copy_option_part((char **)&p, (char *)part, size, ","); if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) { goto fail; } @@ -6921,7 +6986,7 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u *** if (xp->xp_context != EXPAND_BOOL_SETTINGS) { for (match = 0; match < (int)ARRAY_SIZE(names); match++) { - if (vim_regexec(regmatch, (char_u *)names[match], (colnr_T)0)) { + if (vim_regexec(regmatch, names[match], (colnr_T)0)) { if (loop == 0) { num_normal++; } else { @@ -6940,10 +7005,10 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u *** continue; } match = false; - if (vim_regexec(regmatch, str, (colnr_T)0) + if (vim_regexec(regmatch, (char *)str, (colnr_T)0) || (options[opt_idx].shortname != NULL && vim_regexec(regmatch, - (char_u *)options[opt_idx].shortname, + options[opt_idx].shortname, (colnr_T)0))) { match = true; } @@ -7460,7 +7525,7 @@ void save_file_ff(buf_T *buf) if (buf->b_start_fenc == NULL || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { xfree(buf->b_start_fenc); - buf->b_start_fenc = vim_strsave(buf->b_p_fenc); + buf->b_start_fenc = (char *)vim_strsave(buf->b_p_fenc); } } @@ -7827,10 +7892,10 @@ static bool briopt_check(win_T *wp) if (STRNCMP(p, "shift:", 6) == 0 && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { p += 6; - bri_shift = getdigits_int(&p, true, 0); + bri_shift = getdigits_int((char **)&p, true, 0); } else if (STRNCMP(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { p += 4; - bri_min = getdigits_int(&p, true, 0); + bri_min = getdigits_int((char **)&p, true, 0); } else if (STRNCMP(p, "sbr", 3) == 0) { p += 3; bri_sbr = true; @@ -7991,10 +8056,10 @@ char_u *skip_to_option_part(const char_u *p) /// @param[in] sep_chars chars that separate the option parts /// /// @return length of `*option` -size_t copy_option_part(char_u **option, char_u *buf, size_t maxlen, char *sep_chars) +size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars) { size_t len = 0; - char_u *p = *option; + char *p = *option; // skip '.' at start of option part, for 'suffixes' if (*p == '.') { @@ -8015,7 +8080,7 @@ size_t copy_option_part(char_u **option, char_u *buf, size_t maxlen, char *sep_c if (*p != NUL && *p != ',') { // skip non-standard separator p++; } - p = skip_to_option_part(p); // p points to next file name + p = (char *)skip_to_option_part((char_u *)p); // p points to next file name *option = p; return len; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 9268b1eff6..237288fbad 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -153,6 +153,11 @@ #define MOUSE_NONE ' ' // don't use Visual selection #define MOUSE_NONEF 'x' // forced modeless selection +// default vertical and horizontal mouse scroll values. +// Note: This should be in sync with the default mousescroll option. +#define MOUSESCROLL_VERT_DFLT 3 +#define MOUSESCROLL_HOR_DFLT 6 + #define COCU_ALL "nvic" // flags for 'concealcursor' /// characters for p_shm option: @@ -491,9 +496,10 @@ EXTERN int p_js; // 'joinspaces' EXTERN char_u *p_jop; // 'jumpooptions' EXTERN unsigned jop_flags; #ifdef IN_OPTION_C -static char *(p_jop_values[]) = { "stack", NULL }; +static char *(p_jop_values[]) = { "stack", "view", NULL }; #endif #define JOP_STACK 0x01 +#define JOP_VIEW 0x02 EXTERN char_u *p_kp; // 'keywordprg' EXTERN char_u *p_km; // 'keymodel' EXTERN char_u *p_langmap; // 'langmap' @@ -527,6 +533,9 @@ EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' EXTERN int p_mousef; // 'mousefocus' +EXTERN char_u *p_mousescroll; // 'mousescroll' +EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT); +EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT); EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index a0fbf8d9f0..9e4a6a084c 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1597,7 +1597,7 @@ return { short_desc=N_("the use of mouse clicks"), type='string', list='flags', scope={'global'}, varname='p_mouse', - defaults={if_true=""} + defaults={if_true="nvi"} }, { full_name='mousefocus', abbreviation='mousef', @@ -1619,7 +1619,15 @@ return { short_desc=N_("changes meaning of mouse buttons"), type='string', scope={'global'}, varname='p_mousem', - defaults={if_true="extend"} + defaults={if_true="popup_setpos"} + }, + { + full_name='mousescroll', + short_desc=N_("amount to scroll by when scrolling with a mouse"), + type='string', list='comma', scope={'global'}, + vi_def=true, + varname='p_mousescroll', + defaults={if_true="ver:3,hor:6"} }, { full_name='mouseshape', abbreviation='mouses', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 2a7f7a221f..9c93057fe7 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -884,7 +884,7 @@ void vim_get_prefix_from_exepath(char *exe_name) // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]" // but c_grammar.lua does not recognize it (yet). xstrlcpy(exe_name, get_vim_var_str(VV_PROGPATH), MAXPATHL * sizeof(*exe_name)); - char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); + char *path_end = path_tail_with_sep(exe_name); *path_end = '\0'; // remove the trailing "nvim.exe" path_end = path_tail(exe_name); *path_end = '\0'; // remove the trailing "bin/" @@ -1143,15 +1143,16 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si /// Like home_replace, store the replaced string in allocated memory. /// @param buf When not NULL, check for help files /// @param src Input file name -char_u *home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET +char *home_replace_save(buf_T *buf, char *src) + FUNC_ATTR_NONNULL_RET { size_t len = 3; // space for "~/" and trailing NUL if (src != NULL) { // just in case len += STRLEN(src); } char *dst = xmalloc(len); - home_replace(buf, (char *)src, dst, len, true); - return (char_u *)dst; + home_replace(buf, src, dst, len, true); + return dst; } /// Function given to ExpandGeneric() to obtain an environment variable name. @@ -1189,7 +1190,7 @@ bool os_setenv_append_path(const char *fname) internal_error("os_setenv_append_path()"); return false; } - const char *tail = (char *)path_tail_with_sep((char_u *)fname); + const char *tail = path_tail_with_sep((char *)fname); size_t dirlen = (size_t)(tail - fname); assert(tail >= fname && dirlen + 1 < sizeof(os_buf)); xstrlcpy(os_buf, fname, dirlen + 1); diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 7c5e4f31d7..901a1bc5a6 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -935,7 +935,7 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di const char *const real_end = e; const char past_head_save = *past_head; while (!os_isdir((char_u *)curdir)) { - e = (char *)path_tail_with_sep((char_u *)curdir); + e = path_tail_with_sep(curdir); if (e <= past_head) { *past_head = NUL; break; diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index c6c43aac92..581f025a0f 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -165,8 +165,7 @@ static char *signal_name(int signum) // This function handles deadly signals. // It tries to preserve any swap files and exit properly. // (partly from Elvis). -// NOTE: Avoid unsafe functions, such as allocating memory, they can result in -// a deadlock. +// NOTE: this is scheduled on the event loop, not called directly from a signal handler. static void deadly_signal(int signum) FUNC_ATTR_NORETURN { diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 5576e7ba07..59d315d44c 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -115,6 +115,10 @@ char *get_xdg_home(const XDGVarType idx) #else dir = concat_fnames_realloc(dir, "nvim", true); #endif + +#ifdef BACKSLASH_IN_FILENAME + slash_adjust((char_u *)dir); +#endif } return dir; } diff --git a/src/nvim/path.c b/src/nvim/path.c index f5c662ca88..b22c0a18bd 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -125,14 +125,14 @@ char *path_tail(const char *fname) /// - Pointer to the last path separator of `fname`, if there is any. /// - `fname` if it contains no path separator. /// - Never NULL. -char_u *path_tail_with_sep(char_u *fname) +char *path_tail_with_sep(char *fname) { assert(fname != NULL); // Don't remove the '/' from "c:/file". - char_u *past_head = get_past_head(fname); - char_u *tail = (char_u *)path_tail((char *)fname); - while (tail > past_head && after_pathsep((char *)fname, (char *)tail)) { + char *past_head = (char *)get_past_head((char_u *)fname); + char *tail = path_tail(fname); + while (tail > past_head && after_pathsep(fname, tail)) { tail--; } return tail; @@ -326,11 +326,11 @@ void shorten_dir(char_u *str) */ bool dir_of_file_exists(char_u *fname) { - char_u *p = path_tail_with_sep(fname); - if (p == fname) { + char *p = path_tail_with_sep((char *)fname); + if ((char_u *)p == fname) { return true; } - char_u c = *p; + char c = *p; *p = NUL; bool retval = os_isdir(fname); *p = c; @@ -731,7 +731,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, || ((flags & EW_DODOT) && name[1] != NUL && (name[1] != '.' || name[2] != NUL))) // -V557 - && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) + && ((regmatch.regprog != NULL && vim_regexec(®match, (char *)name, 0)) || ((flags & EW_NOTWILD) && FNAMENCMP(path + (s - buf), name, e - s) == 0))) { STRCPY(s, name); @@ -845,7 +845,7 @@ static void expand_path_option(char_u *curdir, garray_T *gap) char_u *buf = xmalloc(MAXPATHL); while (*path_option != NUL) { - copy_option_part(&path_option, buf, MAXPATHL, " ,"); + copy_option_part((char **)&path_option, (char *)buf, MAXPATHL, " ,"); if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1]))) { /* Relative to current buffer: @@ -854,8 +854,8 @@ static void expand_path_option(char_u *curdir, garray_T *gap) if (curbuf->b_ffname == NULL) { continue; } - char_u *p = (char_u *)path_tail((char *)curbuf->b_ffname); - size_t len = (size_t)(p - curbuf->b_ffname); + char_u *p = (char_u *)path_tail(curbuf->b_ffname); + size_t len = (size_t)(p - (char_u *)curbuf->b_ffname); if (len + STRLEN(buf) >= MAXPATHL) { continue; } @@ -996,7 +996,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) if (pattern[0] == '*' && pattern[1] == '*' && vim_ispathsep_nocolon(pattern[2]) && path_cutoff != NULL - && vim_regexec(®match, path_cutoff, (colnr_T)0) + && vim_regexec(®match, (char *)path_cutoff, (colnr_T)0) && is_unique(path_cutoff, gap, i)) { sort_again = true; memmove(path, path_cutoff, STRLEN(path_cutoff) + 1); @@ -1005,7 +1005,7 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) // unique path. We start at the end of the path. */ pathsep_p = path + len - 1; while (find_previous_pathsep(path, &pathsep_p)) { - if (vim_regexec(®match, pathsep_p + 1, (colnr_T)0) + if (vim_regexec(®match, (char *)pathsep_p + 1, (colnr_T)0) && is_unique(pathsep_p + 1, gap, i) && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) { sort_again = true; @@ -1877,7 +1877,7 @@ char *fix_fname(const char *fname) fname = xstrdup(fname); # ifdef USE_FNAME_CASE - path_fix_case((char_u *)fname); // set correct case for file name + path_fix_case(fname); // set correct case for file name # endif return (char *)fname; @@ -1889,17 +1889,17 @@ char *fix_fname(const char *fname) /// Only required for file systems where case is ignored and preserved. // TODO(SplinterOfChaos): Could also be used when mounting case-insensitive // file systems. -void path_fix_case(char_u *name) +void path_fix_case(char *name) FUNC_ATTR_NONNULL_ALL { FileInfo file_info; - if (!os_fileinfo_link((char *)name, &file_info)) { + if (!os_fileinfo_link(name, &file_info)) { return; } // Open the directory where the file is located. - char_u *slash = STRRCHR(name, '/'); - char_u *tail; + char *slash = (char *)STRRCHR(name, '/'); + char *tail; Directory dir; bool ok; if (slash == NULL) { @@ -1907,7 +1907,7 @@ void path_fix_case(char_u *name) tail = name; } else { *slash = NUL; - ok = os_scandir(&dir, (char *)name); + ok = os_scandir(&dir, name); *slash = '/'; tail = slash + 1; } @@ -1916,8 +1916,8 @@ void path_fix_case(char_u *name) return; } - char_u *entry; - while ((entry = (char_u *)os_scandir_next(&dir))) { + char *entry; + while ((entry = (char *)os_scandir_next(&dir))) { // Only accept names that differ in case and are the same byte // length. TODO: accept different length name. if (STRICMP(tail, entry) == 0 && STRLEN(tail) == STRLEN(entry)) { @@ -1956,9 +1956,9 @@ int after_pathsep(const char *b, const char *p) */ bool same_directory(char_u *f1, char_u *f2) { - char_u ffname[MAXPATHL]; - char_u *t1; - char_u *t2; + char ffname[MAXPATHL]; + char *t1; + char *t2; // safety check if (f1 == NULL || f2 == NULL) { @@ -1967,8 +1967,8 @@ bool same_directory(char_u *f1, char_u *f2) (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, FALSE); t1 = path_tail_with_sep(ffname); - t2 = path_tail_with_sep(f2); - return t1 - ffname == t2 - f2 + t2 = path_tail_with_sep((char *)f2); + return t1 - ffname == (char_u *)t2 - f2 && pathcmp((char *)ffname, (char *)f2, (int)(t1 - ffname)) == 0; } @@ -2246,7 +2246,7 @@ int match_suffix(char_u *fname) size_t fnamelen = STRLEN(fname); size_t setsuflen = 0; for (char_u *setsuf = p_su; *setsuf;) { - setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,"); + setsuflen = copy_option_part((char **)&setsuf, (char *)suf_buf, MAXSUFLEN, ".,"); if (setsuflen == 0) { char_u *tail = (char_u *)path_tail((char *)fname); diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index a76dd8eeea..82345f8a46 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -34,6 +34,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO_8859-1\n" "Content-Transfer-Encoding: 8-bit\n" +"Plural-Forms: nplurals=2; plural=n!=1;\n" #~ msgid "[Location List]" #~ msgstr "" @@ -645,11 +646,11 @@ msgstr "&Ok" #~ msgid "filter() argument" #~ msgstr "" -#, fuzzy, c-format -#~ msgid "+-%s%3ld line: " -#~ msgid_plural "+-%s%3ld lines: " -#~ msgstr[0] "+-%s%3ld reëls: " -#~ msgstr[1] "+-%s%3ld reëls: " +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld reëls: " +msgstr[1] "+-%s%3ld reëls: " #, fuzzy, c-format #~ msgid "E700: Unknown function: %s" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index 740e9e5f6a..2dde77e9f7 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -18,6 +18,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO_8859-1\n" "Content-Transfer-Encoding: 8-bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../api/private/helpers.c:197 msgid "Unable to get option value" @@ -6233,8 +6234,10 @@ msgstr "filter()-Argument" #: ../eval.c:8717 #, c-format -msgid "+-%s%3ld lines: " -msgstr "+-%s%3ld Zeilen: " +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld Zeile: " +msgstr[1] "+-%s%3ld Zeilen: " #: ../eval.c:8779 #, c-format diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index 9b374e91ae..1c503d0a84 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -25,6 +25,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "E831: bf_key_init() called with empty password" msgstr "E831: bf_key_init() alvokita kun malplena pasvorto" @@ -1932,6 +1933,12 @@ msgstr "E350: Ne eblas krei faldon per la aktuala 'foldmethod'" msgid "E351: Cannot delete fold with current 'foldmethod'" msgstr "E351: Ne eblas forviÅi faldon per la aktuala 'foldmethod'" +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld linio: " +msgstr[1] "+-%s%3ld linioj: " + msgid "E222: Add to read buffer" msgstr "E222: Aldoni al lega bufro" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index 064484d1a4..adea651b7c 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -19,6 +19,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: octect-stream\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../api/private/helpers.c:201 #, fuzzy @@ -786,8 +787,10 @@ msgstr "-c [argumentos]" #: ../eval.c:9229 #, c-format -msgid "+-%s%3ld lines: " -msgstr "+-%s%3ld lÃneas: " +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld lÃnea: " +msgstr[1] "+-%s%3ld lÃneas: " #: ../eval.c:9291 #, c-format diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index 77d5f7f826..f10d4ce977 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -841,9 +841,11 @@ msgstr "map()-argumentti" msgid "filter() argument" msgstr "filter()-argumentti" -#, fuzzy, c-format -#~ msgid "+-%s%3ld lines: " -#~ msgstr "+-%s%3ld rivi: " +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+--%s%3ld rivi: " +msgstr[1] "+--%s%3ld riviä: " #, c-format msgid "E700: Unknown function: %s" diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index 6df7741f1a..614ba013e6 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -1574,6 +1574,12 @@ msgid_plural "+--%3ld lines folded " msgstr[0] "+--%3ld ligne déplacée " msgstr[1] "+--%3ld lignes déplacées " +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld ligne : " +msgstr[1] "+-%s%3ld lignes : " + msgid "E222: Add to read buffer" msgstr "E222: Ajout au tampon de lecture" diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index ff16a87dbc..1c25ee481c 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -7103,13 +7103,14 @@ msgstr "" "Níorbh fhéidir an chonair a shocrú: ní liosta é sys.path\n" "Ba chóir duit vim.VIM_SPECIAL_PATH a cheangal le deireadh sys.path" -#~ msgid "+-%s%3ld line: " -#~ msgid_plural "+-%s%3ld lines: " -#~ msgstr[0] "+-%s%3ld líne: " -#~ msgstr[1] "+-%s%3ld líne: " -#~ msgstr[2] "+-%s%3ld líne: " -#~ msgstr[3] "+-%s%3ld líne: " -#~ msgstr[4] "+-%s%3ld líne: " +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld líne: " +msgstr[1] "+-%s%3ld líne: " +msgstr[2] "+-%s%3ld líne: " +msgstr[3] "+-%s%3ld líne: " +msgstr[4] "+-%s%3ld líne: " #~ msgid "+--%3ld line folded " #~ msgid_plural "+--%3ld lines folded " diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index dfabc4bee0..313280c807 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -25,6 +25,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO_8859-1\n" "Content-Transfer-Encoding: 8-bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #: ../api/private/helpers.c:201 msgid "Unable to get option value" diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index 9633bec9f2..d7d0faca80 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -1837,6 +1837,11 @@ msgid "+--%3ld line folded " msgid_plural "+--%3ld lines folded " msgstr[0] "+--%3ld ¹Ô¤¬ÀÞ¾ö¤Þ¤ì¤Þ¤·¤¿" +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld ¹Ô: " + msgid "E222: Add to read buffer" msgstr "E222: ÆÉ¹þ¥Ð¥Ã¥Õ¥¡¤ØÄɲÃ" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index c363c00fa6..b56345e066 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -1837,6 +1837,11 @@ msgid "+--%3ld line folded " msgid_plural "+--%3ld lines folded " msgstr[0] "+--%3ld è¡ŒãŒæŠ˜ç•³ã¾ã‚Œã¾ã—ãŸ" +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld 行: " + msgid "E222: Add to read buffer" msgstr "E222: èªè¾¼ãƒãƒƒãƒ•ã‚¡ã¸è¿½åŠ " diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index 5d3e51b7e2..7566036d3e 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -17,6 +17,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: ../api/private/helpers.c:201 #, fuzzy @@ -767,8 +769,11 @@ msgstr "параметра filter()" #: ../eval.c:9229 #, c-format -msgid "+-%s%3ld lines: " -msgstr "+-%s%3ld Ñтрок: " +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld Ñтрока: " +msgstr[1] "+-%s%3ld Ñтроки: " +msgstr[2] "+-%s%3ld Ñтрок: " #: ../eval.c:9291 #, c-format diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index d34c1c3100..3c45e1bf80 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -931,7 +931,8 @@ msgstr "&Ок" msgid "+-%s%3ld line: " msgid_plural "+-%s%3ld lines: " msgstr[0] "+-%s%3ld линија: " -msgstr[1] "+-%s%3ld линија: " +msgstr[1] "+-%s%3ld линијe: " +msgstr[2] "+-%s%3ld линија: " #, c-format msgid "E700: Unknown function: %s" diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 7f0fe6a197..da87d50683 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -22,6 +22,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n" +"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" msgid "--Deleted--" msgstr "--Знищено--" @@ -852,6 +854,12 @@ msgstr "" "%.*s" #, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld Ñ€Ñдок: " +msgstr[1] "+-%s%3ld Ñ€Ñдків: " + +#, c-format msgid "E474: Expected string end: %.*s" msgstr "E474: ОчікувавÑÑ ÐºÑ–Ð½ÐµÑ†ÑŒ Ñ€Ñдка: %.*s" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index 1e329443ce..9a8cd38f5e 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -23,6 +23,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8-bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" #: ../api/private/helpers.c:201 #, fuzzy @@ -781,8 +782,9 @@ msgstr "-c 傿•°" #: ../eval.c:9229 #, c-format -msgid "+-%s%3ld lines: " -msgstr "+-%s%3ld 行: " +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld 行: " #: ../eval.c:9291 #, c-format diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index c97f31ddcf..e2fb2d39d4 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -827,8 +827,9 @@ msgstr "" #: ../eval.c:9229 #, c-format -msgid "+-%s%3ld lines: " -msgstr "+-%s%3ld 行: " +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld 行: " #: ../eval.c:9291 #, fuzzy, c-format diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index ecaeca005d..841277f8f3 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -18,6 +18,7 @@ #include "nvim/ex_cmds.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/menu.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/popupmnu.h" @@ -512,9 +513,13 @@ void pum_redraw(void) char_u *st; char_u saved = *p; - *p = NUL; + if (saved != NUL) { + *p = NUL; + } st = (char_u *)transstr((const char *)s, true); - *p = saved; + if (saved != NUL) { + *p = saved; + } if (pum_rl) { char *rt = (char *)reverse_text(st); @@ -721,8 +726,7 @@ static int pum_set_selected(int n, int repeat) if (!resized && (curbuf->b_nwindows == 1) && (curbuf->b_fname == NULL) - && (curbuf->b_p_bt[0] == 'n') - && (curbuf->b_p_bt[2] == 'f') + && bt_nofile(curbuf) && (curbuf->b_p_bh[0] == 'w')) { // Already a "wipeout" buffer, make it empty. while (!buf_is_empty(curbuf)) { @@ -932,3 +936,181 @@ void pum_set_event_info(dict_T *dict) (void)tv_dict_add_bool(dict, S_LEN("scrollbar"), pum_scrollbar ? kBoolVarTrue : kBoolVarFalse); } + +static void pum_position_at_mouse(int min_width) +{ + pum_anchor_grid = mouse_grid; + if (Rows - mouse_row > pum_size) { + // Enough space below the mouse row. + pum_above = false; + pum_row = mouse_row + 1; + if (pum_height > Rows - pum_row) { + pum_height = Rows - pum_row; + } + } else { + // Show above the mouse row, reduce height if it does not fit. + pum_above = true; + pum_row = mouse_row - pum_size; + if (pum_row < 0) { + pum_height += pum_row; + pum_row = 0; + } + } + if (Columns - mouse_col >= pum_base_width || Columns - mouse_col > min_width) { + // Enough space to show at mouse column. + pum_col = mouse_col; + } else { + // Not enough space, right align with window. + pum_col = Columns - (pum_base_width > min_width ? min_width : pum_base_width); + } + + pum_width = Columns - pum_col; + if (pum_width > pum_base_width + 1) { + pum_width = pum_base_width + 1; + } +} + +/// Select the pum entry at the mouse position. +static void pum_select_mouse_pos(void) +{ + if (mouse_grid == pum_grid.handle) { + pum_selected = mouse_row; + return; + } else if (mouse_grid != pum_anchor_grid) { + pum_selected = -1; + return; + } + + int idx = mouse_row - pum_row; + + if (idx < 0 || idx >= pum_size) { + pum_selected = -1; + } else if (*pum_array[idx].pum_text != NUL) { + pum_selected = idx; + } +} + +/// Execute the currently selected popup menu item. +static void pum_execute_menu(vimmenu_T *menu, int mode) +{ + int idx = 0; + exarg_T ea; + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected) { + memset(&ea, 0, sizeof(ea)); + execute_menu(&ea, mp, -1); + break; + } + } +} + +/// Open the terminal version of the popup menu and don't return until it is closed. +void pum_show_popupmenu(vimmenu_T *menu) +{ + pum_undisplay(true); + pum_size = 0; + int mode = get_menu_mode_flag(); + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if (menu_is_separator(mp->dname) || (mp->modes & mp->enabled & mode)) { + pum_size++; + } + } + + // When there are only Terminal mode menus, using "popup Edit" results in + // pum_size being zero. + if (pum_size <= 0) { + emsg(e_menuothermode); + return; + } + + int idx = 0; + pumitem_T *array = (pumitem_T *)xcalloc((size_t)pum_size, sizeof(pumitem_T)); + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if (menu_is_separator(mp->dname)) { + array[idx++].pum_text = (char_u *)""; + } else if (mp->modes & mp->enabled & mode) { + array[idx++].pum_text = (char_u *)mp->dname; + } + } + + pum_array = array; + pum_compute_size(); + pum_scrollbar = 0; + pum_height = pum_size; + pum_position_at_mouse(20); + + pum_selected = -1; + pum_first = 0; + + for (;;) { + pum_is_visible = true; + pum_is_drawn = true; + pum_redraw(); + setcursor_mayforce(true); + ui_flush(); + + int c = vgetc(); + if (c == ESC || c == Ctrl_C) { + break; + } else if (c == CAR || c == NL) { + // enter: select current item, if any, and close + pum_execute_menu(menu, mode); + break; + } else if (c == 'k' || c == K_UP || c == K_MOUSEUP) { + // cursor up: select previous item + while (pum_selected > 0) { + pum_selected--; + if (*array[pum_selected].pum_text != NUL) { + break; + } + } + } else if (c == 'j' || c == K_DOWN || c == K_MOUSEDOWN) { + // cursor down: select next item + while (pum_selected < pum_size - 1) { + pum_selected++; + if (*array[pum_selected].pum_text != NUL) { + break; + } + } + } else if (c == K_RIGHTMOUSE) { + // Right mouse down: reposition the menu. + vungetc(c); + break; + } else if (c == K_LEFTDRAG || c == K_RIGHTDRAG || c == K_MOUSEMOVE) { + // mouse moved: select item in the mouse row + pum_select_mouse_pos(); + } else if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM || c == K_RIGHTRELEASE) { + // left mouse click: select clicked item, if any, and close; + // right mouse release: select clicked item, close if any + pum_select_mouse_pos(); + if (pum_selected >= 0) { + pum_execute_menu(menu, mode); + break; + } + if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM) { + break; + } + } + } + + xfree(array); + pum_undisplay(true); +} + +void pum_make_popup(const char *path_name, int use_mouse_pos) +{ + if (!use_mouse_pos) { + // Hack: set mouse position at the cursor so that the menu pops up + // around there. + mouse_row = curwin->w_winrow + curwin->w_wrow; + mouse_col = curwin->w_wincol + curwin->w_wcol; + } + + vimmenu_T *menu = menu_find(path_name); + if (menu != NULL) { + pum_show_popupmenu(menu); + } +} diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index edf964f5dd..2138437b29 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1563,7 +1563,7 @@ static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qf // Always ignore case when looking for a matching error. regmatch.rm_ic = true; regmatch.regprog = fmt_ptr->prog; - r = vim_regexec(®match, (char_u *)linebuf, (colnr_T)0); + r = vim_regexec(®match, linebuf, (colnr_T)0); fmt_ptr->prog = regmatch.regprog; if (r) { status = qf_parse_match(linebuf, linelen, fmt_ptr, ®match, fields, @@ -2139,7 +2139,7 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname) xfree(ptr); } else { xfree(qf_last_bufname); - buf = buflist_new((char_u *)bufname, NULL, (linenr_T)0, BLN_NOOPT); + buf = buflist_new(bufname, NULL, (linenr_T)0, BLN_NOOPT); qf_last_bufname = (bufname == ptr) ? bufname : xstrdup(bufname); set_bufref(&qf_last_bufref, buf); } @@ -3301,7 +3301,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) } STRLCAT(buf, title, IOSIZE); } - trunc_string((char_u *)buf, (char_u *)buf, Columns - 1, IOSIZE); + trunc_string(buf, buf, Columns - 1, IOSIZE); msg(buf); } @@ -4035,7 +4035,7 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli // buffer. if (first_bufline && (errbuf->b_sfname == NULL - || path_is_absolute(errbuf->b_sfname))) { + || path_is_absolute((char_u *)errbuf->b_sfname))) { if (*dirname == NUL) { os_dirname((char_u *)dirname, MAXPATHL); } @@ -5462,7 +5462,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo vgr_display_fname(fname); } - buf_T *buf = buflist_findname_exp((char_u *)cmd_args->fnames[fi]); + buf_T *buf = buflist_findname_exp(cmd_args->fnames[fi]); bool using_dummy; if (buf == NULL || buf->b_ml.ml_mfp == NULL) { // Remember that a buffer with this name already exists. @@ -6936,8 +6936,7 @@ void ex_cbuffer(exarg_T *eap) qf_title = qf_cmdtitle(*eap->cmdlinep); if (buf->b_sfname) { - vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", - qf_title, (char *)buf->b_sfname); + vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", qf_title, buf->b_sfname); qf_title = (char *)IObuff; } @@ -7099,7 +7098,7 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch) while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { char *line = (char *)IObuff; - if (vim_regexec(p_regmatch, (char_u *)line, (colnr_T)0)) { + if (vim_regexec(p_regmatch, line, (colnr_T)0)) { int l = (int)STRLEN(line); // remove trailing CR, LF, spaces, etc. @@ -7181,7 +7180,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char // Go through all directories in 'runtimepath' char *p = (char *)p_rtp; while (*p != NUL && !got_int) { - copy_option_part((char_u **)&p, NameBuff, MAXPATHL, ","); + copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); hgr_search_files_in_dir(qfl, (char *)NameBuff, p_regmatch, (char *)lang); } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 45f2cf0e1d..4c49d30819 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -2327,7 +2327,6 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) { regprog_T *prog = NULL; char_u *expr = (char_u *)expr_arg; - int save_called_emsg; regexp_engine = p_re; @@ -2360,8 +2359,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) // // First try the NFA engine, unless backtracking was requested. // - save_called_emsg = called_emsg; - called_emsg = false; + const int called_emsg_before = called_emsg; if (regexp_engine != BACKTRACKING_ENGINE) { prog = nfa_regengine.regcomp(expr, re_flags + (regexp_engine == AUTOMATIC_ENGINE ? RE_AUTO : 0)); @@ -2388,13 +2386,12 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) // also fails for patterns that it can't handle well but are still valid // patterns, thus a retry should work. // But don't try if an error message was given. - if (regexp_engine == AUTOMATIC_ENGINE && !called_emsg) { + if (regexp_engine == AUTOMATIC_ENGINE && called_emsg == called_emsg_before) { regexp_engine = BACKTRACKING_ENGINE; report_re_switch(expr); prog = bt_regengine.regcomp(expr, re_flags); } } - called_emsg |= save_called_emsg; if (prog != NULL) { // Store the info needed to call regcomp() again when the engine turns out @@ -2516,9 +2513,9 @@ bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T // Note: "rmp->regprog" may be freed and changed. // Return true if there is a match, false if not. -bool vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) +bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col) { - return vim_regexec_string(rmp, line, col, false); + return vim_regexec_string(rmp, (char_u *)line, col, false); } // Like vim_regexec(), but consider a "\n" in "line" to be a line break. diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index 03e4d74f14..272429bb91 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -3706,7 +3706,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) pos_T *pos; size_t col = REG_MULTI ? rex.input - rex.line : 0; - pos = getmark_buf(rex.reg_buf, mark, false); + // fm will be NULL if the mark is not set in reg_buf + fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, mark); // Line may have been freed, get it again. if (REG_MULTI) { @@ -3714,10 +3715,11 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) rex.input = rex.line + col; } - if (pos == NULL // mark doesn't exist - || pos->lnum <= 0) { // mark isn't set in reg_buf + if (fm == NULL // mark doesn't exist + || fm->mark.lnum <= 0) { // mark isn't set in reg_buf status = RA_NOMATCH; } else { + pos = &fm->mark; const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum && pos->col == MAXCOL ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 1e8204085c..870af3eafc 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -6930,10 +6930,10 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm case NFA_MARK: case NFA_MARK_GT: case NFA_MARK_LT: { - pos_T *pos; + fmark_T *fm; size_t col = REG_MULTI ? rex.input - rex.line : 0; - - pos = getmark_buf(rex.reg_buf, t->state->val, false); + // fm will be NULL if the mark is not set, doesn't belong to reg_buf + fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, t->state->val); // Line may have been freed, get it again. if (REG_MULTI) { @@ -6943,7 +6943,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm // Compare the mark position to the match position, if the mark // exists and mark is set in reg_buf. - if (pos != NULL && pos->lnum > 0) { + if (fm != NULL && fm->mark.lnum > 0) { + pos_T *pos = &fm->mark; const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum && pos->col == MAXCOL ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 28d85b54bd..045cee2439 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -93,7 +93,7 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, char_u *rtp = rtp_copy; while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { // Copy the path from 'runtimepath' to buf[]. - copy_option_part(&rtp, buf, MAXPATHL, ","); + copy_option_part((char **)&rtp, (char *)buf, MAXPATHL, ","); size_t buflen = STRLEN(buf); // Skip after or non-after directories. @@ -118,8 +118,7 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. assert(MAXPATHL >= (tail - buf)); - copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), - "\t "); + copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -252,8 +251,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. assert(MAXPATHL >= (tail - buf)); - copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), - "\t "); + copy_option_part((char **)&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -513,7 +511,7 @@ RuntimeSearchPath runtime_search_path_build(void) static char_u buf[MAXPATHL]; for (char *entry = (char *)p_pp; *entry != NUL;) { char *cur_entry = entry; - copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); + copy_option_part(&entry, (char *)buf, MAXPATHL, ","); String the_entry = { .data = cur_entry, .size = STRLEN(buf) }; @@ -524,7 +522,7 @@ RuntimeSearchPath runtime_search_path_build(void) char *rtp_entry; for (rtp_entry = (char *)p_rtp; *rtp_entry != NUL;) { char *cur_entry = rtp_entry; - copy_option_part((char_u **)&rtp_entry, buf, MAXPATHL, ","); + copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); size_t buflen = STRLEN(buf); if (path_is_after(buf, buflen)) { @@ -558,7 +556,7 @@ RuntimeSearchPath runtime_search_path_build(void) // "after" dirs in rtp for (; *rtp_entry != NUL;) { - copy_option_part((char_u **)&rtp_entry, buf, MAXPATHL, ","); + copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); expand_rtp_entry(&search_path, &rtp_used, (char *)buf, path_is_after(buf, STRLEN(buf))); } @@ -700,7 +698,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) for (const char *entry = (const char *)p_rtp; *entry != NUL;) { const char *cur_entry = entry; - copy_option_part((char_u **)&entry, buf, MAXPATHL, ","); + copy_option_part((char **)&entry, (char *)buf, MAXPATHL, ","); if (insp == NULL) { add_pathsep((char *)buf); char *const rtp_ffname = fix_fname((char *)buf); @@ -849,7 +847,7 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) const char *p = (const char *)p_rtp; while (*p != NUL) { - copy_option_part((char_u **)&p, (char_u *)buf, MAXPATHL, ","); + copy_option_part((char **)&p, buf, MAXPATHL, ","); if (path_fnamecmp(buf, (char *)fname) == 0) { found = true; break; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ea75900ded..de837720c1 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -298,6 +298,13 @@ void redraw_win_signcol(win_T *wp) } } +/// Update all windows that are editing the current buffer. +void update_curbuf(int type) +{ + redraw_curbuf_later(type); + update_screen(type); +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and @@ -1862,7 +1869,7 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b schar_from_ascii(dest[0], *p); s->prev_c = u8c; } else { - if (p_arshape && !p_tbidi && arabic_char(u8c)) { + if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { // Do Arabic shaping. int pc, pc1, nc; int pcc[MAX_MCO]; @@ -3150,7 +3157,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } } else if (mb_l == 0) { // at the NUL at end-of-line mb_l = 1; - } else if (p_arshape && !p_tbidi && arabic_char(mb_c)) { + } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) { // Do Arabic shaping. int pc, pc1, nc; int pcc[MAX_MCO]; @@ -4838,7 +4845,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char_u **matches, in clen = len; i = first_match; - while ((long)(clen + status_match_len(xp, L_MATCH(i)) + 2) < Columns) { + while (clen + status_match_len(xp, L_MATCH(i)) + 2 < Columns) { if (i == match) { selstart = buf + len; selstart_col = clen; @@ -5309,7 +5316,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) } fillchar = wp->w_p_fcs_chars.wbr; - attr = (wp == curwin) ? HL_ATTR(HLF_WBR) : HL_ATTR(HLF_WBRNC); + attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); maxwidth = wp->w_width_inner; use_sandbox = was_set_insecurely(wp, "winbar", 0); @@ -5486,7 +5493,7 @@ static void win_redr_border(win_T *wp) int *attrs = wp->w_float_config.border_attr; int *adj = wp->w_border_adj; - int irow = wp->w_height_inner, icol = wp->w_width_inner; + int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner; if (adj[0]) { grid_puts_line_start(grid, 0); @@ -5750,12 +5757,17 @@ static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) width * sizeof(sattr_T)); } -/* - * Set cursor to its position in the current window. - */ +/// Set cursor to its position in the current window. void setcursor(void) { - if (redrawing()) { + setcursor_mayforce(false); +} + +/// Set cursor to its position in the current window. +/// @param force when true, also when not redrawing. +void setcursor_mayforce(bool force) +{ + if (force || redrawing()) { validate_cursor(); ScreenGrid *grid = &curwin->w_grid; @@ -6153,6 +6165,10 @@ void clearmode(void) static void recording_mode(int attr) { + if (p_ch <= 0 && !ui_has(kUIMessages)) { + return; + } + msg_puts_attr(_("recording"), attr); if (!shortmess(SHM_RECORDING)) { char s[4]; @@ -6396,7 +6412,7 @@ void get_trans_bufname(buf_T *buf) } else { home_replace(buf, buf->b_fname, (char *)NameBuff, MAXPATHL, true); } - trans_characters(NameBuff, MAXPATHL); + trans_characters((char *)NameBuff, MAXPATHL); } /* @@ -6457,7 +6473,8 @@ int redrawing(void) */ int messaging(void) { - return !(p_lz && char_avail() && !KeyTyped); + return !(p_lz && char_avail() && !KeyTyped) + && (p_ch > 0 || ui_has(kUIMessages)); } /// Show current status info in ruler and various other places @@ -6515,14 +6532,12 @@ static void win_redr_ruler(win_T *wp, bool always) } } - if (*p_ruf) { - int save_called_emsg = called_emsg; - called_emsg = false; + if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) { + const int called_emsg_before = called_emsg; win_redr_custom(wp, false, true); - if (called_emsg) { + if (called_emsg > called_emsg_before) { set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); } - called_emsg |= save_called_emsg; return; } @@ -6596,7 +6611,7 @@ static void win_redr_ruler(win_T *wp, bool always) (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum); size_t len = STRLEN(buffer); - col_print((char_u *)buffer + len, RULER_BUF_LEN - len, + col_print(buffer + len, RULER_BUF_LEN - len, empty_line ? 0 : (int)wp->w_cursor.col + 1, (int)virtcol + 1); diff --git a/src/nvim/search.c b/src/nvim/search.c index 2a248a53e7..f3061b4dc4 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -514,7 +514,7 @@ void last_pat_prog(regmmatch_T *regmatch) --emsg_off; } -/// lowest level search function. +/// Lowest level search function. /// Search for 'count'th occurrence of pattern "pat" in direction "dir". /// Start at position "pos" and return the found position in "pos". /// @@ -556,7 +556,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, long nmatched; int submatch = 0; bool first_match = true; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; bool break_loop = false; linenr_T stop_lnum = 0; // stop after this line number when != 0 proftime_T *tm = NULL; // timeout limit or NULL @@ -579,7 +579,6 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, /* * find the string */ - called_emsg = FALSE; do { // loop for count // When not accepting a match at the start position set "extra_col" to a // non-zero value. Don't do that when starting at MAXCOL, since MAXCOL + 1 @@ -651,7 +650,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; } // Abort searching on an error (e.g., out of stack). - if (called_emsg || (timed_out != NULL && *timed_out)) { + if (called_emsg > called_emsg_before || (timed_out != NULL && *timed_out)) { break; } if (nmatched > 0) { @@ -908,7 +907,8 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // Stop the search if wrapscan isn't set, "stop_lnum" is // specified, after an interrupt, after a match and after looping // twice. - if (!p_ws || stop_lnum != 0 || got_int || called_emsg + if (!p_ws || stop_lnum != 0 || got_int + || called_emsg > called_emsg_before || (timed_out != NULL && *timed_out) || break_loop || found || loop) { @@ -933,7 +933,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, extra_arg->sa_wrapped = true; } } - if (got_int || called_emsg + if (got_int || called_emsg > called_emsg_before || (timed_out != NULL && *timed_out) || break_loop) { break; @@ -942,8 +942,6 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, vim_regfree(regmatch.regprog); - called_emsg |= save_called_emsg; - if (!found) { // did not find it if (got_int) { emsg(_(e_interr)); @@ -3991,8 +3989,7 @@ static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *e } if (n & 1) { col_start -= n; // uneven number of escape chars, skip it - } else if (line[col_start] == - quotechar) { + } else if (line[col_start] == quotechar) { break; } } @@ -4115,8 +4112,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) col_end = curwin->w_cursor.col; } } - } else if (line[col_start] == quotechar - || !vis_empty) { + } else if (line[col_start] == quotechar || !vis_empty) { int first_col = col_start; if (!vis_empty) { @@ -4185,9 +4181,8 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) // Set start position. After vi" another i" must include the ". // For v2i" include the quotes. - if (!include && count < 2 - && (vis_empty || !inside_quotes)) { - ++col_start; + if (!include && count < 2 && (vis_empty || !inside_quotes)) { + col_start++; } curwin->w_cursor.col = col_start; if (VIsual_active) { @@ -4412,7 +4407,7 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct int nmatched = 0; int result = -1; pos_T pos; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; int flag = 0; if (pattern == NULL) { @@ -4438,7 +4433,6 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if // start and end are in the same position. - called_emsg = false; do { regmatch.startpos[0].col++; nmatched = vim_regexec_multi(®match, curwin, curbuf, @@ -4452,14 +4446,13 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct ? regmatch.startpos[0].col < pos.col : regmatch.startpos[0].col > pos.col); - if (!called_emsg) { + if (called_emsg == called_emsg_before) { result = (nmatched != 0 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum && regmatch.startpos[0].col == regmatch.endpos[0].col); } } - called_emsg |= save_called_emsg; vim_regfree(regmatch.regprog); return result; } @@ -5306,6 +5299,16 @@ void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_fuzzymatch(argvars, rettv, true); } +/// Get line "lnum" and copy it into "buf[LSIZE]". +/// The copy is made because the regexp may make the line invalid when using a +/// mark. +static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) +{ + char_u *line = ml_get(lnum); + STRLCPY(buf, line, LSIZE); + return buf; +} + /// Find identifiers or defines in included files. /// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. /// @@ -5402,13 +5405,13 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (lnum > end_lnum) { // do at least one line lnum = end_lnum; } - line = ml_get(lnum); + line = get_line_and_copy(lnum, file_line); for (;;) { if (incl_regmatch.regprog != NULL - && vim_regexec(&incl_regmatch, line, (colnr_T)0)) { + && vim_regexec(&incl_regmatch, (char *)line, (colnr_T)0)) { char_u *p_fname = (curr_fname == (char_u *)curbuf->b_fname) - ? curbuf->b_ffname : curr_fname; + ? (char_u *)curbuf->b_ffname : curr_fname; if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL) { // Use text from '\zs' to '\ze' (or end) of 'include'. @@ -5586,12 +5589,10 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo search_line: define_matched = false; if (def_regmatch.regprog != NULL - && vim_regexec(&def_regmatch, line, (colnr_T)0)) { - /* - * Pattern must be first identifier after 'define', so skip - * to that position before checking for match of pattern. Also - * don't let it match beyond the end of this identifier. - */ + && vim_regexec(&def_regmatch, (char *)line, (colnr_T)0)) { + // Pattern must be first identifier after 'define', so skip + // to that position before checking for match of pattern. Also + // don't let it match beyond the end of this identifier. p = def_regmatch.endp[0]; while (*p && !vim_iswordc(*p)) { p++; @@ -5618,7 +5619,7 @@ search_line: matched = false; } } else if (regmatch.regprog != NULL - && vim_regexec(®match, line, (colnr_T)(p - line))) { + && vim_regexec(®match, (char *)line, (colnr_T)(p - line))) { matched = true; startp = regmatch.startp[0]; // Check if the line is not a comment line (unless we are @@ -5627,7 +5628,7 @@ search_line: if (skip_comments) { if ((*line != '#' || STRNCMP(skipwhite((char *)line + 1), "define", 6) != 0) - && get_leader_len(line, NULL, false, true)) { + && get_leader_len((char *)line, NULL, false, true)) { matched = false; } @@ -5692,7 +5693,7 @@ search_line: if (lnum >= end_lnum) { goto exit_matched; } - line = ml_get(++lnum); + line = get_line_and_copy(++lnum, file_line); } else if (vim_fgets(line = file_line, LSIZE, files[depth].fp)) { goto exit_matched; @@ -5884,7 +5885,7 @@ exit_matched: if (++lnum > end_lnum) { break; } - line = ml_get(lnum); + line = get_line_and_copy(lnum, file_line); } already = NULL; } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 32a0f3902d..6e80b550d8 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1328,11 +1328,11 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) char *const sfname = (char *)path_try_shorten_fname((char_u *)cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = - buflist_new((char_u *)cur_entry.data.buffer_list.buffers[i].fname, (char_u *)sfname, 0, - BLN_LISTED); + buflist_new(cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, BLN_LISTED); if (buf != NULL) { + fmarkv_T view = INIT_FMARKV; RESET_FMARK(&buf->b_last_cursor, - cur_entry.data.buffer_list.buffers[i].pos, 0); + cur_entry.data.buffer_list.buffers[i].pos, 0, view); buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum, buf->b_last_cursor.mark.col, false); buf->additional_data = @@ -2306,7 +2306,7 @@ static inline ShadaEntry shada_get_buflist(khash_t(bufset) *const removable_bufs } buflist_entry.data.buffer_list.buffers[i] = (struct buffer_list_buffer) { .pos = buf->b_last_cursor.mark, - .fname = (char *)buf->b_ffname, + .fname = buf->b_ffname, .additional_data = buf->additional_data, }; i++; @@ -2444,7 +2444,7 @@ static inline void replace_numbered_mark(WriteMergerState *const wms, const size static inline void find_removable_bufs(khash_t(bufset) *removable_bufs) { FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname != NULL && shada_removable((char *)buf->b_ffname)) { + if (buf->b_ffname != NULL && shada_removable(buf->b_ffname)) { int kh_ret; (void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret); } @@ -2803,7 +2803,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef .mark = curwin->w_cursor, .name = '0', .additional_data = NULL, - .fname = (char *)curbuf->b_ffname, + .fname = curbuf->b_ffname, } } }, @@ -2997,7 +2997,7 @@ shada_write_file_open: {} } if (nomerge) { shada_write_file_nomerge: {} - char *const tail = (char *)path_tail_with_sep((char_u *)fname); + char *const tail = path_tail_with_sep(fname); if (tail != fname) { const char tail_save = *tail; *tail = NUL; @@ -3973,9 +3973,9 @@ static bool shada_removable(const char *name) char part[MAXPATHL + 1]; bool retval = false; - char *new_name = (char *)home_replace_save(NULL, (char_u *)name); + char *new_name = home_replace_save(NULL, (char *)name); for (p = (char *)p_shada; *p;) { - (void)copy_option_part((char_u **)&p, (char_u *)part, ARRAY_SIZE(part), ", "); + (void)copy_option_part(&p, part, ARRAY_SIZE(part), ", "); if (part[0] == 'r') { home_replace(NULL, part + 1, (char *)NameBuff, MAXPATHL, true); size_t n = STRLEN(NameBuff); @@ -4025,8 +4025,7 @@ static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps, continue; } const char *const fname = - (char *)(fm.fmark.fnum == - 0 ? (fm.fname == NULL ? NULL : (char_u *)fm.fname) : buf ? buf->b_ffname : NULL); + (fm.fmark.fnum == 0 ? (fm.fname == NULL ? NULL : fm.fname) : buf ? buf->b_ffname : NULL); if (fname == NULL) { continue; } diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 940cd1d274..9a4b304d6c 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -100,10 +100,9 @@ static signgroup_T *sign_group_ref(const char_u *groupname) /// removed, then remove the group. static void sign_group_unref(char_u *groupname) { - hashitem_T *hi; signgroup_T *group; - hi = hash_find(&sg_table, groupname); + hashitem_T *hi = hash_find(&sg_table, (char *)groupname); if (!HASHITEM_EMPTY(hi)) { group = HI2SG(hi); group->sg_refcount--; @@ -136,7 +135,7 @@ static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) int found = false; if (groupname != NULL) { - hi = hash_find(&sg_table, groupname); + hi = hash_find(&sg_table, (char *)groupname); if (HASHITEM_EMPTY(hi)) { return id; } @@ -647,7 +646,7 @@ static int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname) } /// Delete signs in buffer "buf". -void buf_delete_signs(buf_T *buf, char_u *group) +void buf_delete_signs(buf_T *buf, char *group) { sign_entry_T *sign; sign_entry_T **lastp; // pointer to pointer to current sign @@ -662,7 +661,7 @@ void buf_delete_signs(buf_T *buf, char_u *group) lastp = &buf->b_signlist; for (sign = buf->b_signlist; sign != NULL; sign = next) { next = sign->se_next; - if (sign_in_group(sign, group)) { + if (sign_in_group(sign, (char_u *)group)) { *lastp = next; if (next != NULL) { next->se_prev = sign->se_prev; @@ -1086,7 +1085,7 @@ static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T at if (sign_id == 0) { // Delete all the signs in the specified buffer redraw_buf_later(buf, NOT_VALID); - buf_delete_signs(buf, sign_group); + buf_delete_signs(buf, (char *)sign_group); } else { linenr_T lnum; @@ -1173,7 +1172,7 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) if (*arg == NUL) { break; } - p = skiptowhite_esc(arg); + p = (char_u *)skiptowhite_esc((char *)arg); if (STRNCMP(arg, "icon=", 5) == 0) { arg += 5; XFREE_CLEAR(icon); @@ -1273,7 +1272,7 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int i // :sign unplace * group=* FOR_ALL_BUFFERS(cbuf) { if (cbuf->b_signlist != NULL) { - buf_delete_signs(cbuf, group); + buf_delete_signs(cbuf, (char *)group); } } } @@ -1341,7 +1340,7 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si // first arg could be placed sign id arg1 = arg; if (ascii_isdigit(*arg)) { - *signid = getdigits_int(&arg, true, 0); + *signid = getdigits_int((char **)&arg, true, 0); if (!ascii_iswhite(*arg) && *arg != NUL) { *signid = -1; arg = arg1; @@ -1388,12 +1387,12 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si } else if (STRNCMP(arg, "file=", 5) == 0) { arg += 5; filename = arg; - *buf = buflist_findname_exp(arg); + *buf = buflist_findname_exp((char *)arg); break; } else if (STRNCMP(arg, "buffer=", 7) == 0) { arg += 7; filename = arg; - *buf = buflist_findnr(getdigits_int(&arg, true, 0)); + *buf = buflist_findnr(getdigits_int((char **)&arg, true, 0)); if (*skipwhite((char *)arg) != NUL) { emsg(_(e_trailing)); } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index e4805f3c4a..2aadc2258e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -504,7 +504,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // Check for end of sentence. regmatch.regprog = wp->w_s->b_cap_prog; regmatch.rm_ic = false; - int r = vim_regexec(®match, ptr, 0); + int r = vim_regexec(®match, (char *)ptr, 0); wp->w_s->b_cap_prog = regmatch.regprog; if (r) { *capcol = (int)(regmatch.endp[0] - ptr); @@ -1469,7 +1469,9 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // Copy the line into "buf" and append the start of the next line if - // possible. + // possible. Note: this ml_get_buf() may make "line" invalid, check + // for empty line first. + bool empty_line = *skipwhite((const char *)line) == NUL; STRCPY(buf, line); if (lnum < wp->w_buffer->b_ml.ml_line_count) { spell_cat_line(buf + STRLEN(buf), @@ -1613,7 +1615,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att --capcol; // But after empty line check first word in next line - if (*skipwhite((char *)line) == NUL) { + if (empty_line) { capcol = 0; } } @@ -1908,12 +1910,11 @@ void count_common_word(slang_T *lp, char_u *word, int len, int count) /// @param split word was split, less bonus static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split) { - hashitem_T *hi; wordcount_T *wc; int bonus; int newscore; - hi = hash_find(&slang->sl_wordcount, word); + hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word); if (!HASHITEM_EMPTY(hi)) { wc = HI2WC(hi); if (wc->wc_count < SCORE_THRES2) { @@ -2083,7 +2084,7 @@ char *did_set_spelllang(win_T *wp) // Loop over comma separated language names. for (splp = spl_copy; *splp != NUL;) { // Get one language name. - copy_option_part(&splp, lang, MAXWLEN, ","); + copy_option_part((char **)&splp, (char *)lang, MAXWLEN, ","); region = NULL; len = (int)STRLEN(lang); @@ -2215,7 +2216,7 @@ char *did_set_spelllang(win_T *wp) int_wordlist_spl(spf_name); } else { // One entry in 'spellfile'. - copy_option_part(&spf, spf_name, MAXPATHL - 5, ","); + copy_option_part((char **)&spf, (char *)spf_name, MAXPATHL - 5, ","); STRCAT(spf_name, ".spl"); // If it was already found above then skip it. @@ -2328,11 +2329,11 @@ char *did_set_spelllang(win_T *wp) } } } + redraw_later(wp, NOT_VALID); theend: xfree(spl_copy); recursive = false; - redraw_later(wp, NOT_VALID); return ret_msg; } @@ -2805,12 +2806,12 @@ int spell_check_sps(void) sps_limit = 9999; for (p = p_sps; *p != NUL;) { - copy_option_part(&p, buf, MAXPATHL, ","); + copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); f = 0; if (ascii_isdigit(*buf)) { s = buf; - sps_limit = getdigits_int(&s, true, 0); + sps_limit = getdigits_int((char **)&s, true, 0); if (*s != NUL && !ascii_isdigit(*s)) { f = -1; } @@ -3121,7 +3122,7 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) if (p == line || spell_iswordp_nmw(p, curwin)) { break; } - if (vim_regexec(®match, p, 0) + if (vim_regexec(®match, (char *)p, 0) && regmatch.endp[0] == line + endcol) { need_cap = true; break; @@ -3328,7 +3329,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma // Loop over the items in 'spellsuggest'. for (p = sps_copy; *p != NUL;) { - copy_option_part(&p, buf, MAXPATHL, ","); + copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); if (STRNCMP(buf, "expr:", 5) == 0) { // Evaluate an expression. Skip this when called recursively, @@ -4034,8 +4035,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so break; } if ((sp->ts_complen == sp->ts_compsplit - && WAS_BANNED(su, preword + sp->ts_prewordlen)) - || WAS_BANNED(su, preword)) { + && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen)) + || WAS_BANNED(su, (char *)preword)) { if (slang->sl_compprog == NULL) { break; } @@ -5661,7 +5662,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) if (c1 >= 256) { buf[utf_char2bytes(c1, (char *)buf)] = 0; - hi = hash_find(&slang->sl_map_hash, (char_u *)buf); + hi = hash_find(&slang->sl_map_hash, buf); if (HASHITEM_EMPTY(hi)) { m1 = 0; } else { @@ -5676,7 +5677,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) if (c2 >= 256) { buf[utf_char2bytes(c2, (char *)buf)] = 0; - hi = hash_find(&slang->sl_map_hash, (char_u *)buf); + hi = hash_find(&slang->sl_map_hash, buf); if (HASHITEM_EMPTY(hi)) { m2 = 0; } else { @@ -7022,8 +7023,9 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) n = arridx[depth] + curi[depth]; ++curi[depth]; c = byts[n]; - if (c == 0) { - // End of word, deal with the word. + if (c == 0 || depth >= MAXWLEN - 1) { + // End of word or reached maximum length, deal with the + // word. // Don't use keep-case words in the fold-case tree, // they will appear in the keep-case tree. // Only use the word when the region matches. @@ -7144,7 +7146,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, hashitem_T *hi; // Include the word count for ":spelldump!". - hi = hash_find(&slang->sl_wordcount, tw); + hi = hash_find(&slang->sl_wordcount, (char *)tw); if (!HASHITEM_EMPTY(hi)) { vim_snprintf((char *)IObuff, IOSIZE, "%s\t%d", tw, HI2WC(hi)->wc_count); diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 07f3d39886..9d2fd2637d 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -733,7 +733,7 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile if (lp->sl_syllable == NULL) { goto endFAIL; } - if (init_syl_tab(lp) == FAIL) { + if (init_syl_tab(lp) != OK) { goto endFAIL; } break; @@ -2351,7 +2351,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // "S" flag on all but the last block, thus we check for that // and store it in ah_follows. STRLCPY(key, items[1], AH_KEY_LEN); - hi = hash_find(tp, key); + hi = hash_find(tp, (char *)key); if (!HASHITEM_EMPTY(hi)) { cur_aff = HI2AH(hi); if (cur_aff->ah_combine != (*items[2] == 'Y')) { @@ -2379,7 +2379,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) || cur_aff->ah_flag == aff->af_needcomp || cur_aff->ah_flag == aff->af_comproot) { smsg(_("Affix also used for " - "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST" + "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST " "in %s line %d: %s"), fname, lnum, items[1]); } @@ -2688,9 +2688,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } else if (STRCMP(items[0], "COMMON") == 0) { int i; - for (i = 1; i < itemcnt; ++i) { - if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, - items[i]))) { + for (i = 1; i < itemcnt; i++) { + if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, (char *)items[i]))) { p = vim_strsave(items[i]); hash_add(&spin->si_commonwords, p); } @@ -2872,7 +2871,7 @@ static unsigned get_affitem(int flagtype, char_u **pp) ++*pp; // always advance, avoid getting stuck return 0; } - res = getdigits_int(pp, true, 0); + res = getdigits_int((char **)pp, true, 0); if (res == 0) { res = ZERO_FLAG; } @@ -2932,7 +2931,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla // Find the flag in the hashtable. If it was used before, use // the existing ID. Otherwise add a new entry. STRLCPY(key, prevp, p - prevp + 1); - hi = hash_find(&aff->af_comp, key); + hi = hash_find(&aff->af_comp, (char *)key); if (!HASHITEM_EMPTY(hi)) { id = HI2CI(hi)->ci_newID; } else { @@ -2997,7 +2996,7 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) case AFT_NUM: for (p = afflist; *p != NUL;) { - int digits = getdigits_int(&p, true, 0); + int digits = getdigits_int((char **)&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; if (n == 0) { @@ -3359,7 +3358,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist // A flag is a postponed prefix flag if it appears in "af_pref" // and its ID is not zero. STRLCPY(key, prevp, p - prevp + 1); - hi = hash_find(&affile->af_pref, key); + hi = hash_find(&affile->af_pref, (char *)key); if (!HASHITEM_EMPTY(hi)) { id = HI2AH(hi)->ah_newID; if (id != 0) { @@ -3392,7 +3391,7 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl if (get_affitem(affile->af_flagtype, &p) != 0) { // A flag is a compound flag if it appears in "af_comp". STRLCPY(key, prevp, p - prevp + 1); - hi = hash_find(&affile->af_comp, key); + hi = hash_find(&affile->af_comp, (char *)key); if (!HASHITEM_EMPTY(hi)) { store_afflist[cnt++] = HI2CI(hi)->ci_newID; } @@ -3904,6 +3903,21 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin) return (wordnode_T *)getroom(spin, sizeof(wordnode_T), true); } +/// Return true if "word" contains valid word characters. +/// Control characters and trailing '/' are invalid. Space is OK. +static bool valid_spell_word(const char_u *word, const char_u *end) +{ + if (!utf_valid_string(word, end)) { + return false; + } + for (const char_u *p = word; *p != NUL && p < end; p += utfc_ptr2len((const char *)p)) { + if (*p < ' ' || (p[0] == '/' && p[1] == NUL)) { + return false; + } + } + return true; +} + /// Store a word in the tree(s). /// Always store it in the case-folded tree. For a keep-case word this is /// useful when the word can also be used with all caps (no WF_FIXCAP flag) and @@ -3925,7 +3939,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co int res = OK; // Avoid adding illegal bytes to the word tree. - if (!utf_valid_string(word, NULL)) { + if (!valid_spell_word(word, word + len)) { return FAIL; } @@ -5522,7 +5536,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo int i; char_u *spf; - if (!utf_valid_string(word, NULL)) { + if (!valid_spell_word(word, word + len)) { emsg(_(e_illegal_character_in_word)); return; } @@ -5548,8 +5562,8 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } fnamebuf = xmalloc(MAXPATHL); - for (spf = curwin->w_s->b_p_spf, i = 1; *spf != NUL; ++i) { - copy_option_part(&spf, fnamebuf, MAXPATHL, ","); + for (spf = curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { + copy_option_part((char **)&spf, (char *)fnamebuf, MAXPATHL, ","); if (i == idx) { break; } @@ -5561,7 +5575,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } // Check that the user isn't editing the .add file somewhere. - buf = buflist_findname_exp(fnamebuf); + buf = buflist_findname_exp((char *)fnamebuf); if (buf != NULL && buf->b_ml.ml_mfp == NULL) { buf = NULL; } @@ -5623,7 +5637,8 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // file. We may need to create the "spell" directory first. We // already checked the runtime directory is writable in // init_spellfile(). - if (!dir_of_file_exists(fname) && (p = path_tail_with_sep(fname)) != fname) { + if (!dir_of_file_exists(fname) + && (p = (char_u *)path_tail_with_sep((char *)fname)) != fname) { int c = *p; // The directory doesn't exist. Try creating it and opening @@ -5701,7 +5716,7 @@ static void init_spellfile(void) lstart - curbuf->b_s.b_p_spl); } else { // Copy the path from 'runtimepath' to buf[]. - copy_option_part(&rtp, buf, MAXPATHL, ","); + copy_option_part((char **)&rtp, (char *)buf, MAXPATHL, ","); } if (os_file_is_writable((char *)buf) == 2) { // Use the first language name from 'spelllang' and the diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 9615423765..43dbeccf01 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3005,7 +3005,7 @@ static int check_keyword_id(char_u *const line, const int startcol, int *const e /// Accept a keyword at other levels only if it is in the contains list. static keyentry_T *match_keyword(char_u *keyword, hashtab_T *ht, stateitem_T *cur_si) { - hashitem_T *hi = hash_find(ht, keyword); + hashitem_T *hi = hash_find(ht, (char *)keyword); if (!HASHITEM_EMPTY(hi)) { for (keyentry_T *kp = HI2KE(hi); kp != NULL; kp = kp->ke_next) { if (current_next_list != 0 @@ -5123,7 +5123,7 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) ci->sp_off_flags |= (1 << idx); if (idx == SPO_LC_OFF) { // lc=99 end += 3; - *p = getdigits_int(&end, true, 0); + *p = getdigits_int((char **)&end, true, 0); // "lc=" offset automatically sets "ms=" offset if (!(ci->sp_off_flags & (1 << SPO_MS_OFF))) { @@ -5134,10 +5134,10 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) end += 4; if (*end == '+') { end++; - *p = getdigits_int(&end, true, 0); // positive offset + *p = getdigits_int((char **)&end, true, 0); // positive offset } else if (*end == '-') { end++; - *p = -getdigits_int(&end, true, 0); // negative offset + *p = -getdigits_int((char **)&end, true, 0); // negative offset } } if (*end != ',') { @@ -5382,7 +5382,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis regmatch.rm_ic = TRUE; id = 0; for (int i = highlight_num_groups(); --i >= 0;) { - if (vim_regexec(®match, highlight_group_name(i), (colnr_T)0)) { + if (vim_regexec(®match, (char *)highlight_group_name(i), (colnr_T)0)) { if (round == 2) { // Got more items than expected; can happen // when adding items that match: diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 796a2fa5f3..28b3b6c1ef 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -116,7 +116,7 @@ static char_u *tagmatchname = NULL; // name of last used tag * Tag for preview window is remembered separately, to avoid messing up the * normal tagstack. */ -static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0, NULL }; +static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL }; static int tfu_in_use = false; // disallow recursive call of tagfunc @@ -167,7 +167,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) char_u **new_matches; int use_tagstack; int skip_msg = false; - char_u *buf_ffname = curbuf->b_ffname; // name for priority computation + char_u *buf_ffname = (char_u *)curbuf->b_ffname; // name for priority computation int use_tfu = 1; // remember the matches for the last used tag @@ -423,7 +423,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) buf_T *buf = buflist_findnr(cur_fnum); if (buf != NULL) { - buf_ffname = buf->b_ffname; + buf_ffname = (char_u *)buf->b_ffname; } } @@ -1863,6 +1863,7 @@ parse_line: // For "normal" tags: Do a quick check if the tag matches. // This speeds up tag searching a lot! if (orgpat.headlen) { + memset(&tagp, 0, sizeof(tagp)); tagp.tagname = lbuf; tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB); if (tagp.tagname_end == NULL) { @@ -2031,14 +2032,13 @@ parse_line: cc = *tagp.tagname_end; *tagp.tagname_end = NUL; - match = vim_regexec(&orgpat.regmatch, tagp.tagname, (colnr_T)0); + match = vim_regexec(&orgpat.regmatch, (char *)tagp.tagname, (colnr_T)0); if (match) { matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); if (orgpat.regmatch.rm_ic) { - orgpat.regmatch.rm_ic = FALSE; - match_no_ic = vim_regexec(&orgpat.regmatch, tagp.tagname, - (colnr_T)0); - orgpat.regmatch.rm_ic = TRUE; + orgpat.regmatch.rm_ic = false; + match_no_ic = vim_regexec(&orgpat.regmatch, (char *)tagp.tagname, (colnr_T)0); + orgpat.regmatch.rm_ic = true; } } *tagp.tagname_end = (char_u)cc; @@ -2423,7 +2423,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) * Copy next file name into buf. */ buf[0] = NUL; - (void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,"); + (void)copy_option_part((char **)&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,"); r_ptr = vim_findfile_stopdir(buf); // move the filename one char forward and truncate the @@ -2436,7 +2436,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) r_ptr, 100, FALSE, // don't free visited list FINDFILE_FILE, // we search for a file - tnp->tn_search_ctx, TRUE, curbuf->b_ffname); + tnp->tn_search_ctx, true, (char_u *)curbuf->b_ffname); if (tnp->tn_search_ctx != NULL) { tnp->tn_did_filefind_init = TRUE; } @@ -2749,7 +2749,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // If it was a CTRL-W CTRL-] command split window now. For ":tab tag" // open a new tab page. if (postponed_split && (swb_flags & (SWB_USEOPEN | SWB_USETAB))) { - buf_T *const existing_buf = buflist_findname_exp(fname); + buf_T *const existing_buf = buflist_findname_exp((char *)fname); if (existing_buf != NULL) { const win_T *wp = NULL; @@ -2785,7 +2785,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // A :ta from a help file will keep the b_help flag set. For ":ptag" // we need to use the flag from the window where we came from. if (l_g_do_tagpreview != 0) { - keep_help_flag = curwin_save->w_buffer->b_help; + keep_help_flag = bt_help(curwin_save->w_buffer); } else { keep_help_flag = curbuf->b_help; } @@ -3112,11 +3112,11 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char_u ***file) if (pat[0] == '/') { ret = find_tags(pat + 1, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC, - TAG_MANY, curbuf->b_ffname); + TAG_MANY, (char_u *)curbuf->b_ffname); } else { ret = find_tags(pat, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC, - TAG_MANY, curbuf->b_ffname); + TAG_MANY, (char_u *)curbuf->b_ffname); } if (ret == OK && !tagnames) { // Reorganize the tags for display and matching as strings of: diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 85517a71a4..be49048aec 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -228,7 +228,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts) set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); if (buf->b_ffname != NULL) { - buf_set_term_title(buf, (char *)buf->b_ffname); + buf_set_term_title(buf, buf->b_ffname); } RESET_BINDING(curwin); // Reset cursor in current window. diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 8f97d959ce..4107df99d6 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -55,6 +55,14 @@ func CheckMSWindows() endif endfunc +" Command to check for NOT running on MS-Windows +command CheckNotMSWindows call CheckNotMSWindows() +func CheckNotMSWindows() + if has('win32') + throw 'Skipped: does not work on MS-Windows' + endif +endfunc + " Command to check for running on Unix command CheckUnix call CheckUnix() func CheckUnix() @@ -129,14 +137,6 @@ func CheckEnglish() endif endfunc -" Command to check for NOT running on MS-Windows -command CheckNotMSWindows call CheckNotMSWindows() -func CheckNotMSWindows() - if has('win32') - throw 'Skipped: does not work on MS-Windows' - endif -endfunc - " Command to check for not running under ASAN command CheckNotAsan call CheckNotAsan() func CheckNotAsan() diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index b0d872e392..6b16e888a9 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -153,6 +153,9 @@ func RunTheTest(test) " directory after executing the test. let save_cwd = getcwd() + " Align Nvim defaults to Vim. + source setup.vim + if exists("*SetUp") try call SetUp() @@ -361,24 +364,25 @@ let s:flaky_tests = [ \ 'Test_cursorhold_insert()', \ 'Test_exit_callback_interval()', \ 'Test_map_timeout_with_timer_interrupt()', - \ 'Test_oneshot()', \ 'Test_out_cb()', - \ 'Test_paused()', \ 'Test_popup_and_window_resize()', \ 'Test_quoteplus()', \ 'Test_quotestar()', \ 'Test_reltime()', - \ 'Test_repeat_many()', - \ 'Test_repeat_three()', \ 'Test_state()', - \ 'Test_stop_all_in_callback()', \ 'Test_term_mouse_double_click_to_create_tab()', \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', + \ 'Test_timer_oneshot()', + \ 'Test_timer_paused()', + \ 'Test_timer_repeat_many()', + \ 'Test_timer_repeat_three()', + \ 'Test_timer_stop_all_in_callback()', + \ 'Test_timer_stop_in_callback()', + \ 'Test_timer_with_partial_callback()', \ 'Test_termwinscroll()', - \ 'Test_with_partial_callback()', \ ] " Locate Test_ functions and execute them. diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index f1092af358..e6c0762729 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -1,3 +1,35 @@ +if exists('s:did_load') + " Align Nvim defaults to Vim. + set backspace= + set complete=.,w,b,u,t,i + set directory& + set directory^=. + set fillchars=vert:\|,fold:- + set formatoptions=tcq + set fsync + set laststatus=1 + set listchars=eol:$ + set joinspaces + set nohidden nosmarttab noautoindent noautoread noruler noshowcmd + set nohlsearch noincsearch + set nrformats=bin,octal,hex + set shortmess=filnxtToOS + set sidescroll=0 + set tags=./tags,tags + set undodir& + set undodir^=. + set wildoptions= + set startofline + set sessionoptions& + set sessionoptions+=options + set viewoptions& + set viewoptions+=options + set switchbuf= + " Make "Q" switch to Ex mode. + " This does not work for all tests. + nnoremap Q gQ +endif + " Common preparations for running tests. " Only load this once. @@ -6,26 +38,6 @@ if exists('s:did_load') endif let s:did_load = 1 -" Align Nvim defaults to Vim. -set backspace= -set directory^=. -set fillchars=vert:\|,fold:- -set fsync -set laststatus=1 -set listchars=eol:$ -set joinspaces -set nohidden nosmarttab noautoindent noautoread complete-=i noruler noshowcmd -set nrformats+=octal -set shortmess-=F -set sidescroll=0 -set tags=./tags,tags -set undodir^=. -set wildoptions= -set startofline -set sessionoptions+=options -set viewoptions+=options -set switchbuf= - " Clear Nvim default mappings. mapclear mapclear! diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim index 450c6f98f5..272937387d 100644 --- a/src/nvim/testdir/test_arabic.vim +++ b/src/nvim/testdir/test_arabic.vim @@ -2,9 +2,8 @@ " NOTE: This just checks if the code works. If you know Arabic please add " functional tests that check the shaping works with real text. -if !has('arabic') - throw 'Skipped: arabic feature missing' -endif +source check.vim +CheckFeature arabic source view_util.vim @@ -563,3 +562,26 @@ func Test_shape_combination_isolated() set arabicshape& bwipe! endfunc + +" Test for entering arabic character in a search command +func Test_arabic_chars_in_search_cmd() + new + set arabic + call feedkeys("i\nsghl!\<C-^>vim\<C-^>", 'tx') + call cursor(1, 1) + call feedkeys("/^sghl!\<C-^>vim$\<C-^>\<CR>", 'tx') + call assert_equal([2, 1], [line('.'), col('.')]) + + " Try searching in left-to-right mode + set rightleftcmd= + call cursor(1, 1) + call feedkeys("/^sghl!\<C-^>vim$\<CR>", 'tx') + call assert_equal([2, 1], [line('.'), col('.')]) + + set rightleftcmd& + set rightleft& + set arabic& + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 164149476f..ca7c8574cb 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -1,5 +1,8 @@ " Test argument list commands +source shared.vim +source term_util.vim + func Reset_arglist() args a | %argd endfunc @@ -510,3 +513,42 @@ func Test_argdo() call assert_equal(['Xa.c', 'Xb.c', 'Xc.c'], l) bwipe Xa.c Xb.c Xc.c endfunc + +" Test for quiting Vim with unedited files in the argument list +func Test_quit_with_arglist() + if !CanRunVimInTerminal() + throw 'Skipped: cannot run vim in terminal' + endif + let buf = RunVimInTerminal('', {'rows': 6}) + call term_sendkeys(buf, ":set nomore\n") + call term_sendkeys(buf, ":args a b c\n") + call term_sendkeys(buf, ":quit\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^E173:', term_getline(buf, 6))}) + call StopVimInTerminal(buf) + + " Try :confirm quit with unedited files in arglist + let buf = RunVimInTerminal('', {'rows': 6}) + call term_sendkeys(buf, ":set nomore\n") + call term_sendkeys(buf, ":args a b c\n") + call term_sendkeys(buf, ":confirm quit\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$', + \ term_getline(buf, 6))}) + call term_sendkeys(buf, "N") + call term_wait(buf) + call term_sendkeys(buf, ":confirm quit\n") + call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$', + \ term_getline(buf, 6))}) + call term_sendkeys(buf, "Y") + call term_wait(buf) + call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))}) + only! + " When this test fails, swap files are left behind which breaks subsequent + " tests + call delete('.a.swp') + call delete('.b.swp') + call delete('.c.swp') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 28c5948142..fdd8b0bef6 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -50,6 +50,26 @@ func Test_assert_equal() call remove(v:errors, 0) endfunc +func Test_assert_equal_dict() + call assert_equal(0, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 1})) + + call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 3})) + call assert_match("Expected {'one': 1} but got {'one': 3} - 1 equal item omitted", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 22, one: 11})) + call assert_match("Expected {'one': 1, 'two': 2} but got {'one': 11, 'two': 22}", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equal(#{}, #{two: 2, one: 1})) + call assert_match("Expected {} but got {'one': 1, 'two': 2}", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equal(#{two: 2, one: 1}, #{})) + call assert_match("Expected {'one': 1, 'two': 2} but got {}", v:errors[0]) + call remove(v:errors, 0) +endfunc + func Test_assert_equalfile() call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz')) call assert_match("E485: Can't read file abcabc", v:errors[0]) @@ -257,6 +277,26 @@ func Test_assert_with_msg() call remove(v:errors, 0) endfunc +func Test_mouse_position() + throw 'Skipped: Nvim does not have test_setmouse()' + let save_mouse = &mouse + set mouse=a + new + call setline(1, ['line one', 'line two']) + call assert_equal([0, 1, 1, 0], getpos('.')) + call test_setmouse(1, 5) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 5, 0], getpos('.')) + call test_setmouse(2, 20) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 8, 0], getpos('.')) + call test_setmouse(5, 1) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 2, 1, 0], getpos('.')) + bwipe! + let &mouse = save_mouse +endfunc + " Must be last. func Test_zz_quit_detected() " Verify that if a test function ends Vim the test script detects this. diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 660801d575..438851a0ad 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -169,7 +169,9 @@ func Test_autocmd_bufunload_avoiding_SEGV_01() exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!' augroup END - call assert_fails('edit bb.txt', 'E937:') + " Todo: check for E937 generated first + " call assert_fails('edit bb.txt', 'E937:') + call assert_fails('edit bb.txt', 'E517:') autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -540,7 +542,7 @@ func Test_three_windows() e Xtestje2 sp Xtestje1 call assert_fails('e', 'E937:') - call assert_equal('Xtestje2', expand('%')) + call assert_equal('Xtestje1', expand('%')) " Test changing buffers in a BufWipeout autocommand. If this goes wrong " there are ml_line errors and/or a Crash. @@ -563,7 +565,6 @@ func Test_three_windows() au! enew - bwipe! Xtestje1 call delete('Xtestje1') call delete('Xtestje2') call delete('Xtestje3') @@ -1719,7 +1720,7 @@ func Test_Cmd_Autocmds() au BufWriteCmd XtestE call extend(g:lines, getline(0, '$')) wall " will write other window to 'lines' call assert_equal(4, len(g:lines), g:lines) - call assert_equal("\tasdf", g:lines[2]) + call assert_equal("asdf", g:lines[2]) au! BufReadCmd au! BufWriteCmd @@ -2225,7 +2226,7 @@ func Test_autocmd_bufreadpre() " (even though the position will be invalid, this should make Vim reset the " cursor position in the other window. wincmd p - 1 + 1 " set cpo+=g " won't do anything, but try to set the cursor on an invalid lnum autocmd BufReadPre <buffer> :norm! 70gg " triggers BufReadPre, should not move the cursor in either window @@ -2240,8 +2241,11 @@ func Test_autocmd_bufreadpre() close close call delete('XAutocmdBufReadPre.txt') + " set cpo-=g endfunc +" FileChangedShell tested in test_filechanged.vim + " Tests for the following autocommands: " - FileWritePre writing a compressed file " - FileReadPost reading a compressed file @@ -2559,7 +2563,29 @@ func Test_BufWrite_lockmarks() call delete('Xtest2') endfunc -" FileChangedShell tested in test_filechanged.vim +" Test closing a window or editing another buffer from a FileChangedRO handler +" in a readonly buffer +func Test_FileChangedRO_winclose() + augroup FileChangedROTest + au! + autocmd FileChangedRO * quit + augroup END + new + set readonly + call assert_fails('normal i', 'E788:') + close + augroup! FileChangedROTest + + augroup FileChangedROTest + au! + autocmd FileChangedRO * edit Xfile + augroup END + new + set readonly + call assert_fails('normal i', 'E788:') + close + augroup! FileChangedROTest +endfunc func LogACmd() call add(g:logged, line('$')) @@ -2668,6 +2694,27 @@ func Test_autocmd_window() %bw! endfunc +" Test for trying to close the temporary window used for executing an autocmd +func Test_close_autocmd_window() + %bw! + edit one.txt + tabnew two.txt + augroup aucmd_win_test2 + au! + " Nvim makes aucmd_win the last window + " au BufEnter * if expand('<afile>') == 'one.txt' | 1close | endif + au BufEnter * if expand('<afile>') == 'one.txt' | close | endif + augroup END + + call assert_fails('doautoall BufEnter', 'E813:') + + augroup aucmd_win_test2 + au! + augroup END + augroup! aucmd_win_test2 + %bw! +endfunc + " Test for trying to close the tab that has the temporary window for exeucing " an autocmd. func Test_close_autocmd_tab() diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 9eb768f124..67be3e6747 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -2,6 +2,161 @@ source check.vim +" Test for the :bunload command with an offset +func Test_bunload_with_offset() + %bwipe! + call writefile(['B1'], 'b1') + call writefile(['B2'], 'b2') + call writefile(['B3'], 'b3') + call writefile(['B4'], 'b4') + + " Load four buffers. Unload the second and third buffers and then + " execute .+3bunload to unload the last buffer. + edit b1 + new b2 + new b3 + new b4 + + bunload b2 + bunload b3 + exe bufwinnr('b1') . 'wincmd w' + .+3bunload + call assert_equal(0, getbufinfo('b4')[0].loaded) + call assert_equal('b1', + \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t')) + + " Load four buffers. Unload the third and fourth buffers. Execute .+3bunload + " and check whether the second buffer is unloaded. + ball + bunload b3 + bunload b4 + exe bufwinnr('b1') . 'wincmd w' + .+3bunload + call assert_equal(0, getbufinfo('b2')[0].loaded) + call assert_equal('b1', + \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t')) + + " Load four buffers. Unload the second and third buffers and from the last + " buffer execute .-3bunload to unload the first buffer. + ball + bunload b2 + bunload b3 + exe bufwinnr('b4') . 'wincmd w' + .-3bunload + call assert_equal(0, getbufinfo('b1')[0].loaded) + call assert_equal('b4', + \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t')) + + " Load four buffers. Unload the first and second buffers. Execute .-3bunload + " from the last buffer and check whether the third buffer is unloaded. + ball + bunload b1 + bunload b2 + exe bufwinnr('b4') . 'wincmd w' + .-3bunload + call assert_equal(0, getbufinfo('b3')[0].loaded) + call assert_equal('b4', + \ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t')) + + %bwipe! + call delete('b1') + call delete('b2') + call delete('b3') + call delete('b4') + + call assert_fails('1,4bunload', 'E16:') + call assert_fails(',100bunload', 'E16:') + + " Use a try-catch for this test. When assert_fails() is used for this + " test, the command fails with E515: instead of E90: + let caught_E90 = 0 + try + $bunload + catch /E90:/ + let caught_E90 = 1 + endtry + call assert_equal(1, caught_E90) + call assert_fails('$bunload', 'E515:') +endfunc + +" Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified +" commands +func Test_buflist_browse() + %bwipe! + call assert_fails('buffer 1000', 'E86:') + + call writefile(['foo1', 'foo2', 'foo3', 'foo4'], 'Xfile1') + call writefile(['bar1', 'bar2', 'bar3', 'bar4'], 'Xfile2') + call writefile(['baz1', 'baz2', 'baz3', 'baz4'], 'Xfile3') + edit Xfile1 + let b1 = bufnr() + edit Xfile2 + let b2 = bufnr() + edit +/baz4 Xfile3 + let b3 = bufnr() + + call assert_fails('buffer ' .. b1 .. ' abc', 'E488:') + call assert_equal(b3, bufnr()) + call assert_equal(4, line('.')) + exe 'buffer +/bar2 ' .. b2 + call assert_equal(b2, bufnr()) + call assert_equal(2, line('.')) + exe 'buffer +/bar1' + call assert_equal(b2, bufnr()) + call assert_equal(1, line('.')) + + brewind + + call assert_equal(b1, bufnr()) + call assert_equal(4, line('.')) + + blast +/baz2 + call assert_equal(b3, bufnr()) + call assert_equal(2, line('.')) + + bprevious +/bar4 + call assert_equal(b2, bufnr()) + call assert_equal(4, line('.')) + + bnext +/baz3 + call assert_equal(b3, bufnr()) + call assert_equal(3, line('.')) + + call assert_fails('bmodified', 'E84:') + call setbufvar(b2, '&modified', 1) + exe 'bmodified +/bar3' + call assert_equal(b2, bufnr()) + call assert_equal(3, line('.')) + + " With no listed buffers in the list, :bnext and :bprev should fail + %bwipe! + set nobuflisted + call assert_fails('bnext', 'E85:') + call assert_fails('bprev', 'E85:') + set buflisted + + call assert_fails('sandbox bnext', 'E48:') + + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') + %bwipe! +endfunc + +" Test for :bdelete +func Test_bdelete_cmd() + %bwipe! + call assert_fails('bdelete 5', 'E516:') + call assert_fails('1,1bdelete 1 2', 'E488:') + + " Deleting a unlisted and unloaded buffer + edit Xfile1 + let bnr = bufnr() + set nobuflisted + enew + call assert_fails('bdelete ' .. bnr, 'E516:') + %bwipe! +endfunc + func Test_buffer_error() new foo1 new foo2 diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index c364babd65..a1e53df774 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -225,6 +225,21 @@ func Test_cd_from_non_existing_dir() call assert_equal(saveddir, getcwd()) endfunc +func Test_cd_completion() + call mkdir('XComplDir1', 'p') + call mkdir('XComplDir2', 'p') + call writefile([], 'XComplFile') + + for cmd in ['cd', 'chdir', 'lcd', 'lchdir', 'tcd', 'tchdir'] + call feedkeys(':' .. cmd .. " XCompl\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"' .. cmd .. ' XComplDir1/ XComplDir2/', @:) + endfor + + call delete('XComplDir1', 'd') + call delete('XComplDir2', 'd') + call delete('XComplFile') +endfunc + func Test_cd_unknown_dir() call mkdir('Xa') cd Xa diff --git a/src/nvim/testdir/test_changelist.vim b/src/nvim/testdir/test_changelist.vim index ce77c1f3c7..3741f32e69 100644 --- a/src/nvim/testdir/test_changelist.vim +++ b/src/nvim/testdir/test_changelist.vim @@ -46,3 +46,5 @@ func Test_getchangelist() call delete('Xfile1.txt') call delete('Xfile2.txt') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index 6f09e85a42..d386d74f8d 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -1,3 +1,4 @@ +" Test for character search commands - t, T, f, F, ; and , func Test_charsearch() enew! @@ -28,6 +29,17 @@ func Test_charsearch() set cpo-=; normal! ;;p call assert_equal('ZabcdeZfghijkZZemnokqretkZvwxyz', getline(3)) + + " check that repeating a search before and after a line fails + normal 3Gfv + call assert_beeps('normal ;') + call assert_beeps('normal ,') + + " clear the character search + call setcharsearch({'char' : ''}) + call assert_equal('', getcharsearch().char) + + call assert_fails("call setcharsearch([])", 'E715:') enew! endfunc @@ -60,3 +72,30 @@ func Test_search_cmds() call assert_equal('ddd yee y', getline(6)) enew! endfunc + +" Test for character search in virtual edit mode with <Tab> +func Test_csearch_virtualedit() + new + set virtualedit=all + call setline(1, "a\tb") + normal! tb + call assert_equal([0, 1, 2, 6], getpos('.')) + set virtualedit& + close! +endfunc + +" Test for character search failure in latin1 encoding +func Test_charsearch_latin1() + new + let save_enc = &encoding + " set encoding=latin1 + call setline(1, 'abcdefghijk') + call assert_beeps('normal fz') + call assert_beeps('normal tx') + call assert_beeps('normal $Fz') + call assert_beeps('normal $Tx') + let &encoding = save_enc + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 7ba8ef3397..ccc8168c09 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -5306,11 +5306,20 @@ func Test_cindent_case() set cindent norm! f:a: call assert_equal('case x:: // x', getline(1)) - set cindent& bwipe! endfunc +" Test for changing multiple lines (using c) with cindent +func Test_cindent_change_multline() + new + setlocal cindent + call setline(1, ['if (a)', '{', ' i = 1;', '}']) + normal! jc3jm = 2; + call assert_equal("\tm = 2;", getline(2)) + close! +endfunc + " This was reading past the end of the line func Test_cindent_check_funcdecl() new diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index 922803438f..db62fe5fa6 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -39,6 +39,8 @@ func Test_client_server() call remote_send(name, ":let testvar = 'yes'\<CR>") call WaitFor('remote_expr("' . name . '", "exists(\"testvar\") ? testvar : \"\"", "", 1) == "yes"') call assert_equal('yes', remote_expr(name, "testvar", "", 2)) + call assert_fails("let x=remote_expr(name, '2+x')", 'E449:') + call assert_fails("let x=remote_expr('[], '2+2')", 'E116:') if has('unix') && has('gui') && !has('gui_running') " Running in a terminal and the GUI is available: Tell the server to open @@ -75,6 +77,7 @@ func Test_client_server() eval 'MYSELF'->remote_startserver() " May get MYSELF1 when running the test again. call assert_match('MYSELF', v:servername) + call assert_fails("call remote_startserver('MYSELF')", 'E941:') endif let g:testvar = 'myself' call assert_equal('myself', remote_expr(v:servername, 'testvar')) @@ -107,7 +110,12 @@ func Test_client_server() call job_stop(job, 'kill') endif endtry + + call assert_fails("let x=remote_peek([])", 'E730:') + call assert_fails("let x=remote_read('vim10')", 'E277:') endfunc " Uncomment this line to get a debugging log " call ch_logfile('channellog', 'w') + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 8d1746be2f..276bb7fb71 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -2,6 +2,7 @@ source check.vim source screendump.vim +source view_util.vim func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') @@ -18,6 +19,11 @@ 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')) + + " If a command doesn't support completion, then CTRL-D should be literally + " used. + call feedkeys(":chistory \<C-D>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"chistory \<C-D>", @:) endfunc func Test_complete_wildmenu() @@ -71,6 +77,17 @@ func Test_complete_wildmenu() cunmap <C-K> endif + " Test for canceling the wild menu by adding a character + redrawstatus + call feedkeys(":e Xdir1/\<Tab>x\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xdir1/Xdir2/x', @:) + + " Completion using a relative path + cd Xdir1/Xdir2 + call feedkeys(":e ../\<Tab>\<Right>\<Down>\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"e Xtestfile3 Xtestfile4', @:) + cd - + " cleanup %bwipe call delete('Xdir1/Xdir2/Xtestfile4') @@ -581,6 +598,29 @@ func Test_cmdline_paste() " ignore error E32 endtry call assert_equal("Xtestfile", bufname("%")) + + " Try to paste an invalid register using <C-R> + call feedkeys(":\"one\<C-R>\<C-X>two\<CR>", 'xt') + call assert_equal('"onetwo', @:) + + " Test for pasting register containing CTRL-H using CTRL-R and CTRL-R CTRL-R + let @a = "xy\<C-H>z" + call feedkeys(":\"\<C-R>a\<CR>", 'xt') + call assert_equal('"xz', @:) + call feedkeys(":\"\<C-R>\<C-R>a\<CR>", 'xt') + call assert_equal("\"xy\<C-H>z", @:) + call feedkeys(":\"\<C-R>\<C-O>a\<CR>", 'xt') + call assert_equal("\"xy\<C-H>z", @:) + + " Test for pasting register containing CTRL-V using CTRL-R and CTRL-R CTRL-R + let @a = "xy\<C-V>z" + call feedkeys(":\"\<C-R>=@a\<CR>\<cr>", 'xt') + call assert_equal('"xyz', @:) + call feedkeys(":\"\<C-R>\<C-R>=@a\<CR>\<cr>", 'xt') + call assert_equal("\"xy\<C-V>z", @:) + + call assert_beeps('call feedkeys(":\<C-R>=\<C-R>=\<Esc>", "xt")') + bwipe! endfunc @@ -757,6 +797,15 @@ funct Test_cmdline_complete_languages() endif endfunc +func Test_cmdline_complete_env_variable() + let $X_VIM_TEST_COMPLETE_ENV = 'foo' + + call feedkeys(":edit $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('"edit $X_VIM_TEST_COMPLETE_ENV', @:) + + unlet $X_VIM_TEST_COMPLETE_ENV +endfunc + func Test_cmdline_complete_expression() let g:SomeVar = 'blah' for cmd in ['exe', 'echo', 'echon', 'echomsg'] @@ -768,6 +817,158 @@ func Test_cmdline_complete_expression() unlet g:SomeVar endfunc +" Test for various command-line completion +func Test_cmdline_complete_various() + " completion for a command starting with a comment + call feedkeys(": :|\"\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\" :|\"\<C-A>", @:) + + " completion for a range followed by a comment + call feedkeys(":1,2\"\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"1,2\"\<C-A>", @:) + + " completion for :k command + call feedkeys(":ka\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"ka\<C-A>", @:) + + " completion for short version of the :s command + call feedkeys(":sI \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"sI \<C-A>", @:) + + " completion for :write command + call mkdir('Xdir') + call writefile(['one'], 'Xdir/Xfile1') + let save_cwd = getcwd() + cd Xdir + call feedkeys(":w >> \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"w >> Xfile1", @:) + call chdir(save_cwd) + call delete('Xdir', 'rf') + + " completion for :w ! and :r ! commands + call feedkeys(":w !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"w !invalid_xyz_cmd", @:) + call feedkeys(":r !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"r !invalid_xyz_cmd", @:) + + " completion for :>> and :<< commands + call feedkeys(":>>>\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\">>>\<C-A>", @:) + call feedkeys(":<<<\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"<<<\<C-A>", @:) + + " completion for command with +cmd argument + call feedkeys(":buffer +/pat Xabc\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"buffer +/pat Xabc", @:) + call feedkeys(":buffer +/pat\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"buffer +/pat\<C-A>", @:) + + " completion for a command with a trailing comment + call feedkeys(":ls \" comment\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"ls \" comment\<C-A>", @:) + + " completion for a command with a trailing command + call feedkeys(":ls | ls\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"ls | ls", @:) + + " completion for a command with an CTRL-V escaped argument + call feedkeys(":ls \<C-V>\<C-V>a\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"ls \<C-V>a\<C-A>", @:) + + " completion for a command that doesn't take additional arguments + call feedkeys(":all abc\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"all abc\<C-A>", @:) + + " completion for a command with a command modifier + call feedkeys(":topleft new\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"topleft new", @:) + + " completion for the :match command + call feedkeys(":match Search /pat/\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"match Search /pat/\<C-A>", @:) + + " completion for the :s command + call feedkeys(":s/from/to/g\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/from/to/g\<C-A>", @:) + + " completion for the :dlist command + call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:) + + " completion for the :doautocmd command + call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:) + + " completion for the :augroup command + augroup XTest + augroup END + call feedkeys(":augroup X\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"augroup XTest", @:) + augroup! XTest + + " completion for the :unlet command + call feedkeys(":unlet one two\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"unlet one two", @:) + + " completion for the :buffer command with curlies + " FIXME: what should happen on MS-Windows? + if !has('win32') + edit \{someFile} + call feedkeys(":buf someFile\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"buf {someFile}", @:) + bwipe {someFile} + endif + + " completion for the :bdelete command + call feedkeys(":bdel a b c\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"bdel a b c", @:) + + " completion for the :mapclear command + call feedkeys(":mapclear \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"mapclear <buffer>", @:) + + " completion for user defined commands with menu names + menu Test.foo :ls<CR> + com -nargs=* -complete=menu MyCmd + call feedkeys(":MyCmd Te\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd Test.', @:) + delcom MyCmd + unmenu Test + + " completion for user defined commands with mappings + mapclear + map <F3> :ls<CR> + com -nargs=* -complete=mapping MyCmd + call feedkeys(":MyCmd <F\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd <F3>', @:) + mapclear + delcom MyCmd + + " completion for :set path= with multiple backslashes + call feedkeys(":set path=a\\\\\\ b\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set path=a\\\ b', @:) + + " completion for :set dir= with a backslash + call feedkeys(":set dir=a\\ b\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set dir=a\ b', @:) + + " completion for the :py3 commands + call feedkeys(":py3\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"py3 py3do py3file', @:) + + " redir @" is not the start of a comment. So complete after that + call feedkeys(":redir @\" | cwin\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"redir @" | cwindow', @:) + + " completion after a backtick + call feedkeys(":e `a1b2c\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"e `a1b2c', @:) + + " completion for the expression register + call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt') + call assert_equal('"float2nr("', @=) +endfunc + func Test_cmdline_write_alternatefile() new call setline('.', ['one', 'two']) @@ -809,9 +1010,32 @@ func Test_cmdline_search_range() 1,\&s/b/B/ call assert_equal('B', getline(2)) + let @/ = 'apple' + call assert_fails('\/print', 'E486:') + bwipe! endfunc +" Test for the tick mark (') in an excmd range +func Test_tick_mark_in_range() + " If only the tick is passed as a range and no command is specified, there + " should not be an error + call feedkeys(":'\<CR>", 'xt') + call assert_equal("'", getreg(':')) + call assert_fails("',print", 'E78:') +endfunc + +" Test for using a line number followed by a search pattern as range +func Test_lnum_and_pattern_as_range() + new + call setline(1, ['foo 1', 'foo 2', 'foo 3']) + let @" = '' + 2/foo/yank + call assert_equal("foo 3\n", @") + call assert_equal(1, line('.')) + close! +endfunc + " Tests for getcmdline(), getcmdpos() and getcmdtype() func Check_cmdline(cmdtype) call assert_equal('MyCmd a', getcmdline()) @@ -844,6 +1068,8 @@ func Test_getcmdtype() cnoremap <expr> <F6> Check_cmdline('=') call feedkeys("a\<C-R>=MyCmd a\<F6>\<Esc>\<Esc>", "xt") cunmap <F6> + + call assert_equal('', getcmdline()) endfunc func Test_getcmdwintype() @@ -893,22 +1119,6 @@ func Test_getcmdwin_autocmd() augroup END endfunc -" Test error: "E135: *Filter* Autocommands must not change current buffer" -func Test_cmd_bang_E135() - new - call setline(1, ['a', 'b', 'c', 'd']) - augroup test_cmd_filter_E135 - au! - autocmd FilterReadPost * help - augroup END - call assert_fails('2,3!echo "x"', 'E135:') - - augroup test_cmd_filter_E135 - au! - augroup END - %bwipe! -endfunc - func Test_verbosefile() set verbosefile=Xlog echomsg 'foo' @@ -989,34 +1199,6 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc -func Test_cmdwin_feedkeys() - " This should not generate E488 - call feedkeys("q:\<CR>", 'x') -endfunc - -" Tests for the issues fixed in 7.4.441. -" When 'cedit' is set to Ctrl-C, opening the command window hangs Vim -func Test_cmdwin_cedit() - exe "set cedit=\<C-c>" - normal! : - call assert_equal(1, winnr('$')) - - let g:cmd_wintype = '' - func CmdWinType() - let g:cmd_wintype = getcmdwintype() - let g:wintype = win_gettype() - return '' - endfunc - - call feedkeys("\<C-c>a\<C-R>=CmdWinType()\<CR>\<CR>") - echo input('') - call assert_equal('@', g:cmd_wintype) - call assert_equal('command', g:wintype) - - set cedit&vim - delfunc CmdWinType -endfunc - func Test_cmdwin_restore() CheckScreendump @@ -1093,6 +1275,38 @@ func Test_buffers_lastused() bwipeout bufc endfunc +func Test_cmdwin_feedkeys() + " This should not generate E488 + call feedkeys("q:\<CR>", 'x') + " Using feedkeys with q: only should automatically close the cmd window + call feedkeys('q:', 'xt') + call assert_equal(1, winnr('$')) + call assert_equal('', getcmdwintype()) +endfunc + +" Tests for the issues fixed in 7.4.441. +" When 'cedit' is set to Ctrl-C, opening the command window hangs Vim +func Test_cmdwin_cedit() + exe "set cedit=\<C-c>" + normal! : + call assert_equal(1, winnr('$')) + + let g:cmd_wintype = '' + func CmdWinType() + let g:cmd_wintype = getcmdwintype() + let g:wintype = win_gettype() + return '' + endfunc + + call feedkeys("\<C-c>a\<C-R>=CmdWinType()\<CR>\<CR>") + echo input('') + call assert_equal('@', g:cmd_wintype) + call assert_equal('command', g:wintype) + + set cedit&vim + delfunc CmdWinType +endfunc + " Test for CmdwinEnter autocmd func Test_cmdwin_autocmd() CheckFeature cmdwin @@ -1131,6 +1345,48 @@ func Test_cmdlineclear_tabenter() call delete('XtestCmdlineClearTabenter') endfunc +" Test for expanding special keywords in cmdline +func Test_cmdline_expand_special() + new + %bwipe! + call assert_fails('e #', 'E194:') + call assert_fails('e <afile>', 'E495:') + call assert_fails('e <abuf>', 'E496:') + call assert_fails('e <amatch>', 'E497:') + call writefile([], 'Xfile.cpp') + call writefile([], 'Xfile.java') + new Xfile.cpp + call feedkeys(":e %:r\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.cpp Xfile.java', @:) + close + call delete('Xfile.cpp') + call delete('Xfile.java') +endfunc + +func Test_cmdwin_jump_to_win() + call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:') + new + set modified + call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', 'E162:') + close! + call feedkeys("q/:close\<CR>", "xt") + call assert_equal(1, winnr('$')) + call feedkeys("q/:exit\<CR>", "xt") + call assert_equal(1, winnr('$')) + + " opening command window twice should fail + call assert_beeps('call feedkeys("q:q:\<CR>\<CR>", "xt")') + call assert_equal(1, winnr('$')) +endfunc + +" Test for backtick expression in the command line +func Test_cmd_backtick() + %argd + argadd `=['a', 'b', 'c']` + call assert_equal(['a', 'b', 'c'], argv()) + %argd +endfunc + func Test_cmdwin_tabpage() tabedit " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. @@ -1143,6 +1399,323 @@ func Test_cmdwin_tabpage() tabclose! endfunc +" Test for the :! command +func Test_cmd_bang() + if !has('unix') + return + endif + + let lines =<< trim [SCRIPT] + " Test for no previous command + call assert_fails('!!', 'E34:') + set nomore + " Test for cmdline expansion with :! + call setline(1, 'foo!') + silent !echo <cWORD> > Xfile.out + call assert_equal(['foo!'], readfile('Xfile.out')) + " Test for using previous command + silent !echo \! ! + call assert_equal(['! echo foo!'], readfile('Xfile.out')) + call writefile(v:errors, 'Xresult') + call delete('Xfile.out') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test error: "E135: *Filter* Autocommands must not change current buffer" +func Test_cmd_bang_E135() + new + call setline(1, ['a', 'b', 'c', 'd']) + augroup test_cmd_filter_E135 + au! + autocmd FilterReadPost * help + augroup END + call assert_fails('2,3!echo "x"', 'E135:') + + augroup test_cmd_filter_E135 + au! + augroup END + %bwipe! +endfunc + +" Test for using ~ for home directory in cmdline completion matches +func Test_cmdline_expand_home() + call mkdir('Xdir') + call writefile([], 'Xdir/Xfile1') + call writefile([], 'Xdir/Xfile2') + cd Xdir + let save_HOME = $HOME + let $HOME = getcwd() + call feedkeys(":e ~/\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ~/Xfile1 ~/Xfile2', @:) + let $HOME = save_HOME + cd .. + call delete('Xdir', 'rf') +endfunc + +" Test for using CTRL-\ CTRL-G in the command line to go back to normal mode +" or insert mode (when 'insertmode' is set) +func Test_cmdline_ctrl_g() + new + call setline(1, 'abc') + call cursor(1, 3) + " If command line is entered from insert mode, using C-\ C-G should back to + " insert mode + call feedkeys("i\<C-O>:\<C-\>\<C-G>xy", 'xt') + call assert_equal('abxyc', getline(1)) + call assert_equal(4, col('.')) + + " If command line is entered in 'insertmode', using C-\ C-G should back to + " 'insertmode' + " call feedkeys(":set im\<cr>\<C-L>:\<C-\>\<C-G>12\<C-L>:set noim\<cr>", 'xt') + " call assert_equal('ab12xyc', getline(1)) + close! +endfunc + +" Test for 'wildmode' +func Test_wildmode() + func T(a, c, p) + return "oneA\noneB\noneC" + endfunc + command -nargs=1 -complete=custom,T MyCmd + + func SaveScreenLine() + let g:Sline = Screenline(&lines - 1) + return '' + endfunc + cnoremap <expr> <F2> SaveScreenLine() + + set nowildmenu + set wildmode=full,list + let g:Sline = '' + call feedkeys(":MyCmd \t\t\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('oneA oneB oneC', g:Sline) + call assert_equal('"MyCmd oneA', @:) + + set wildmode=longest,full + call feedkeys(":MyCmd o\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd one', @:) + call feedkeys(":MyCmd o\t\t\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneC', @:) + + set wildmode=longest + call feedkeys(":MyCmd one\t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd one', @:) + + set wildmode=list:longest + let g:Sline = '' + call feedkeys(":MyCmd \t\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('oneA oneB oneC', g:Sline) + call assert_equal('"MyCmd one', @:) + + set wildmode="" + call feedkeys(":MyCmd \t\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"MyCmd oneA', @:) + + " Test for wildmode=longest with 'fileignorecase' set + set wildmode=longest + set fileignorecase + argadd AA AAA AAAA + call feedkeys(":buffer \t\<C-B>\"\<CR>", 'xt') + call assert_equal('"buffer AA', @:) + set fileignorecase& + + " Test for listing files with wildmode=list + set wildmode=list + let g:Sline = '' + call feedkeys(":b A\t\t\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('AA AAA AAAA', g:Sline) + call assert_equal('"b A', @:) + + %argdelete + delcommand MyCmd + delfunc T + delfunc SaveScreenLine + cunmap <F2> + set wildmode& + %bwipe! +endfunc + +" Test for interrupting the command-line completion +func Test_interrupt_compl() + func F(lead, cmdl, p) + if a:lead =~ 'tw' + call interrupt() + return + endif + return "one\ntwo\nthree" + endfunc + command -nargs=1 -complete=custom,F Tcmd + + set nowildmenu + set wildmode=full + let interrupted = 0 + try + call feedkeys(":Tcmd tw\<Tab>\<C-B>\"\<CR>", 'xt') + catch /^Vim:Interrupt$/ + let interrupted = 1 + endtry + call assert_equal(1, interrupted) + + delcommand Tcmd + delfunc F + set wildmode& +endfunc + +" Test for moving the cursor on the : command line +func Test_cmdline_edit() + let str = ":one two\<C-U>" + let str ..= "one two\<C-W>\<C-W>" + let str ..= "four\<BS>\<C-H>\<Del>\<kDel>" + let str ..= "\<Left>five\<Right>" + let str ..= "\<Home>two " + let str ..= "\<C-Left>one " + let str ..= "\<C-Right> three" + let str ..= "\<End>\<S-Left>four " + let str ..= "\<S-Right> six" + let str ..= "\<C-B>\"\<C-E> seven\<CR>" + call feedkeys(str, 'xt') + call assert_equal("\"one two three four five six seven", @:) +endfunc + +" Test for moving the cursor on the / command line in 'rightleft' mode +func Test_cmdline_edit_rightleft() + CheckFeature rightleft + set rightleft + set rightleftcmd=search + let str = "/one two\<C-U>" + let str ..= "one two\<C-W>\<C-W>" + let str ..= "four\<BS>\<C-H>\<Del>\<kDel>" + let str ..= "\<Right>five\<Left>" + let str ..= "\<Home>two " + let str ..= "\<C-Right>one " + let str ..= "\<C-Left> three" + let str ..= "\<End>\<S-Right>four " + let str ..= "\<S-Left> six" + let str ..= "\<C-B>\"\<C-E> seven\<CR>" + call assert_fails("call feedkeys(str, 'xt')", 'E486:') + call assert_equal("\"one two three four five six seven", @/) + set rightleftcmd& + set rightleft& +endfunc + +" Test for using <C-\>e in the command line to evaluate an expression +func Test_cmdline_expr() + " Evaluate an expression from the beginning of a command line + call feedkeys(":abc\<C-B>\<C-\>e\"\\\"hello\"\<CR>\<CR>", 'xt') + call assert_equal('"hello', @:) + + " Use an invalid expression for <C-\>e + call assert_beeps('call feedkeys(":\<C-\>einvalid\<CR>", "tx")') + + " Insert literal <CTRL-\> in the command line + call feedkeys(":\"e \<C-\>\<C-Y>\<CR>", 'xt') + call assert_equal("\"e \<C-\>\<C-Y>", @:) +endfunc + +" Test for 'imcmdline' and 'imsearch' +" This test doesn't actually test the input method functionality. +func Test_cmdline_inputmethod() + new + call setline(1, ['', 'abc', '']) + set imcmdline + + call feedkeys(":\"abc\<CR>", 'xt') + call assert_equal("\"abc", @:) + call feedkeys(":\"\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal("\"abc", @:) + call feedkeys("/abc\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + + " set imsearch=2 + call cursor(1, 1) + call feedkeys("/abc\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + call cursor(1, 1) + call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + set imdisable + call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + set imdisable& + set imsearch& + + set imcmdline& + %bwipe! +endfunc + +" Test for recursively getting multiple command line inputs +func Test_cmdwin_multi_input() + call feedkeys(":\<C-R>=input('P: ')\<CR>\"cyan\<CR>\<CR>", 'xt') + call assert_equal('"cyan', @:) +endfunc + +" Test for using CTRL-_ in the command line with 'allowrevins' +func Test_cmdline_revins() + CheckNotMSWindows + CheckFeature rightleft + call feedkeys(":\"abc\<c-_>\<cr>", 'xt') + call assert_equal("\"abc\<c-_>", @:) + set allowrevins + call feedkeys(":\"abc\<c-_>xyz\<c-_>\<CR>", 'xt') + call assert_equal('"abcñèæ', @:) + set allowrevins& +endfunc + +" Test for typing UTF-8 composing characters in the command line +func Test_cmdline_composing_chars() + call feedkeys(":\"\<C-V>u3046\<C-V>u3099\<CR>", 'xt') + call assert_equal('"ã†ã‚™', @:) +endfunc + +" Test for normal mode commands not supported in the cmd window +func Test_cmdwin_blocked_commands() + call assert_fails('call feedkeys("q:\<C-T>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-]>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-^>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:Q\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:Z\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<F1>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>s\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>v\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>^\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>n\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>z\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>o\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>w\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>j\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>k\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>h\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>l\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>T\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>x\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>r\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>R\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>K\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>}\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>]\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>f\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>d\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>g\<CR>", "xt")', 'E11:') +endfunc + +" Close the Cmd-line window in insert mode using CTRL-C +func Test_cmdwin_insert_mode_close() + %bw! + let s = '' + exe "normal q:a\<C-C>let s='Hello'\<CR>" + call assert_equal('Hello', s) + call assert_equal(1, winnr('$')) +endfunc + " test that ";" works to find a match at the start of the first line func Test_zero_line_search() new @@ -1268,4 +1841,9 @@ func Test_recursive_register() call assert_equal('yes', caught) endfunc +func Test_long_error_message() + " the error should be truncated, not overrun IObuff + silent! norm Q00000000000000     000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000                                                                                                                                                                                                                        +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim index c7dddf4164..55b230373f 100644 --- a/src/nvim/testdir/test_command_count.vim +++ b/src/nvim/testdir/test_command_count.vim @@ -33,7 +33,7 @@ func Test_command_count_0() delcommand RangeBuffers delcommand RangeBuffersAll - set nohidden + set hidden& set swapfile& endfunc diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index c0c572ce65..3dc8710d63 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -68,5 +68,9 @@ func Test_compiler_completion() endfunc func Test_compiler_error() + let g:current_compiler = 'abc' call assert_fails('compiler doesnotexist', 'E666:') + call assert_equal('abc', g:current_compiler) + call assert_fails('compiler! doesnotexist', 'E666:') + unlet! g:current_compiler endfunc diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim index faf37485cd..76ea35fa10 100644 --- a/src/nvim/testdir/test_cscope.vim +++ b/src/nvim/testdir/test_cscope.vim @@ -1,7 +1,11 @@ " Test for cscope commands. -if !has('cscope') || !executable('cscope') || !has('quickfix') - finish +source check.vim +CheckFeature cscope +CheckFeature quickfix + +if !executable('cscope') + throw 'Skipped: cscope program missing' endif func CscopeSetupOrClean(setup) diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 6c6a6290cb..3b8a5f27ad 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -99,6 +99,7 @@ func Test_screenpos() \ 'curscol': wincol + 9, \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) close + call assert_equal({}, screenpos(999, 1, 1)) bwipe! call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index be9a77ee75..8c20c647f1 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -540,7 +540,7 @@ func Test_diffopt_hiddenoff() bwipe! bwipe! - set nohidden diffopt& + set hidden& diffopt& endfunc func Test_diffoff_hidden() @@ -577,7 +577,7 @@ func Test_diffoff_hidden() bwipe! bwipe! - set nohidden diffopt& + set hidden& diffopt& endfunc func Test_setting_cursor() diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index 7b6bc940d3..acc34e5e7c 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -466,10 +466,12 @@ endfunc func Test_digraph_cmndline() " Create digraph on commandline - " This is a hack, to let Vim create the digraph in commandline mode - let s = '' - exe "sil! norm! :let s.='\<c-k>Eu'\<cr>" - call assert_equal("€", s) + call feedkeys(":\"\<c-k>Eu\<cr>", 'xt') + call assert_equal('"€', @:) + + " Canceling a CTRL-K on the cmdline + call feedkeys(":\"a\<c-k>\<esc>b\<cr>", 'xt') + call assert_equal('"ab', @:) endfunc func Test_show_digraph() diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 275c8f7a15..42c77518e4 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -270,6 +270,10 @@ func Test_edit_10() call cursor(1, 4) call feedkeys("A\<s-home>start\<esc>", 'txin') call assert_equal(['startdef', 'ghi'], getline(1, '$')) + " start select mode again with gv + set selectmode=cmd + call feedkeys('gvabc', 'xt') + call assert_equal('abctdef', getline(1)) set selectmode= keymodel= bw! endfunc @@ -282,7 +286,7 @@ func Test_edit_11() call cursor(2, 1) call feedkeys("i\<c-f>int c;\<esc>", 'tnix') call cursor(3, 1) - call feedkeys("i/* comment */", 'tnix') + call feedkeys("\<Insert>/* comment */", 'tnix') call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$')) " added changed cindentkeys slightly set cindent cinkeys+=*/ @@ -345,8 +349,8 @@ func Test_edit_11_indentexpr() bw! endfunc +" Test changing indent in replace mode func Test_edit_12() - " Test changing indent in replace mode new call setline(1, ["\tabc", "\tdef"]) call cursor(2, 4) @@ -385,15 +389,15 @@ func Test_edit_12() call feedkeys("R\<c-t>\<c-t>", 'tnix') call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) call assert_equal([0, 2, 2, 0], getpos('.')) - set et - set sw& et& + set sw& + + " In replace mode, after hitting enter in a line with tab characters, + " pressing backspace should restore the tab characters. %d - call setline(1, ["\t/*"]) - set formatoptions=croql - call cursor(1, 3) - call feedkeys("A\<cr>\<cr>/", 'tnix') - call assert_equal(["\t/*", " *", " */"], getline(1, '$')) - set formatoptions& + setlocal autoindent backspace=2 + call setline(1, "\tone\t\ttwo") + exe "normal ggRred\<CR>six" .. repeat("\<BS>", 8) + call assert_equal(["\tone\t\ttwo"], getline(1, '$')) bw! endfunc @@ -416,16 +420,49 @@ func Test_edit_13() call assert_equal("", getline(2)) call assert_equal(" baz", getline(3)) set autoindent& + + " pressing <C-U> to erase line should keep the indent with 'autoindent' + set backspace=2 autoindent + %d + exe "normal i\tone\<CR>three\<C-U>two" + call assert_equal(["\tone", "\ttwo"], getline(1, '$')) + set backspace& autoindent& + bwipe! endfunc +" Test for autoindent removing indent when insert mode is stopped. Some parts +" of the code is exercised only when interactive mode is used. So use Vim in a +" terminal. +func Test_autoindent_remove_indent() + CheckRunVimInTerminal + let buf = RunVimInTerminal('-N Xfile', {'rows': 6, 'cols' : 20}) + call TermWait(buf) + call term_sendkeys(buf, ":set autoindent\n") + " leaving insert mode in a new line with indent added by autoindent, should + " remove the indent. + call term_sendkeys(buf, "i\<Tab>foo\<CR>\<Esc>") + " Need to delay for sometime, otherwise the code in getchar.c will not be + " exercised. + call TermWait(buf, 50) + " when a line is wrapped and the cursor is at the start of the second line, + " leaving insert mode, should move the cursor back to the first line. + call term_sendkeys(buf, "o" .. repeat('x', 20) .. "\<Esc>") + " Need to delay for sometime, otherwise the code in getchar.c will not be + " exercised. + call TermWait(buf, 50) + call term_sendkeys(buf, ":w\n") + call TermWait(buf) + call StopVimInTerminal(buf) + call assert_equal(["\tfoo", '', repeat('x', 20)], readfile('Xfile')) + call delete('Xfile') +endfunc + func Test_edit_CR() " Test for <CR> in insert mode " basically only in quickfix mode ist tested, the rest " has been taken care of by other tests - if !has("quickfix") - return - endif + CheckFeature quickfix botright new call writefile(range(1, 10), 'Xqflist.txt') call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}]) @@ -981,6 +1018,22 @@ func Test_edit_CTRL_U() bw! endfunc +func Test_edit_completefunc_delete() + func CompleteFunc(findstart, base) + if a:findstart == 1 + return col('.') - 1 + endif + normal dd + return ['a', 'b'] + endfunc + new + set completefunc=CompleteFunc + call setline(1, ['', 'abcd', '']) + 2d + call assert_fails("normal 2G$a\<C-X>\<C-U>", 'E565:') + bwipe! +endfunc + func Test_edit_CTRL_Z() " Ctrl-Z when insertmode is not set inserts it literally new @@ -1327,7 +1380,7 @@ func Test_edit_forbidden() try call feedkeys("ix\<esc>", 'tnix') call assert_fails(1, 'textlock') - catch /^Vim\%((\a\+)\)\=:E523/ " catch E523: not allowed here + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565: not allowed here endtry " TODO: Might be a bug: should x really be inserted here call assert_equal(['xa'], getline(1, '$')) @@ -1352,7 +1405,7 @@ func Test_edit_forbidden() try call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix') call assert_fails(1, 'change in complete function') - catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565 endtry delfu Complete set completefunc= @@ -1597,6 +1650,22 @@ func Test_edit_startinsert() bwipe! endfunc +" Test for :startreplace and :startgreplace +func Test_edit_startreplace() + new + call setline(1, 'abc') + call feedkeys("l:startreplace\<CR>xyz\e", 'xt') + call assert_equal('axyz', getline(1)) + call feedkeys("0:startreplace!\<CR>abc\e", 'xt') + call assert_equal('axyzabc', getline(1)) + call setline(1, "a\tb") + call feedkeys("0l:startgreplace\<CR>xyz\e", 'xt') + call assert_equal("axyz\tb", getline(1)) + call feedkeys("0i\<C-R>=execute('startreplace')\<CR>12\e", 'xt') + call assert_equal("12axyz\tb", getline(1)) + close! +endfunc + func Test_edit_noesckeys() CheckNotGui new @@ -1617,6 +1686,58 @@ func Test_edit_noesckeys() " set esckeys endfunc +" Test for running an invalid ex command in insert mode using CTRL-O +" Note that vim has a hard-coded sleep of 3 seconds. So this test will take +" more than 3 seconds to complete. +func Test_edit_ctrl_o_invalid_cmd() + new + set showmode showcmd + let caught_e492 = 0 + try + call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt") + catch /E492:/ + let caught_e492 = 1 + endtry + call assert_equal(1, caught_e492) + call assert_equal('abc', getline(1)) + set showmode& showcmd& + close! +endfunc + +" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') +func Test_edit_cpo_H() + throw 'Skipped: Nvim does not support cpoptions flag "H"' + new + call setline(1, ' ') + normal! Ia + call assert_equal(' a', getline(1)) + set cpo+=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a ', getline(1)) + set cpo-=H + close! +endfunc + +" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') +func Test_edit_cpo_L() + new + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo+=L + set list + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) + set nolist + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo-=L + %bw! +endfunc + " Test for editing a directory func Test_edit_is_a_directory() CheckEnglish diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 0ce333fa40..122572f32a 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -64,16 +64,118 @@ func Test_ex_mode() let &encoding = encoding_save endfunc +" Test substitute confirmation prompt :%s/pat/str/c in Ex mode +func Test_Ex_substitute() + CheckRunVimInTerminal + let buf = RunVimInTerminal('', {'rows': 6}) + + call term_sendkeys(buf, ":call setline(1, ['foo foo', 'foo foo', 'foo foo'])\<CR>") + call term_sendkeys(buf, ":set number\<CR>") + call term_sendkeys(buf, "gQ") + call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, "%s/foo/bar/gc\<CR>") + call WaitForAssert({-> assert_match(' 1 foo foo', term_getline(buf, 5))}, + \ 1000) + call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, "N\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, "n\<CR>") + call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, + \ 1000) + call term_sendkeys(buf, "y\<CR>") + + call term_sendkeys(buf, "q\<CR>") + call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) + + " Pressing enter in ex mode should print the current line + call term_sendkeys(buf, "\<CR>") + call WaitForAssert({-> assert_match(' 3 foo foo', + \ term_getline(buf, 5))}, 1000) + + call term_sendkeys(buf, ":vi\<CR>") + call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000) + + call term_sendkeys(buf, ":q!\n") + call StopVimInTerminal(buf) +endfunc + +" Test for displaying lines from an empty buffer in Ex mode +func Test_Ex_emptybuf() + new + call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E749:') + call setline(1, "abc") + call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E501:') + call assert_fails('call feedkeys("Q%d\<CR>", "xt")', 'E749:') + close! +endfunc + +" Test for the :open command +func Test_open_command() + throw 'Skipped: Nvim does not have :open' + new + call setline(1, ['foo foo', 'foo bar', 'foo baz']) + call feedkeys("Qopen\<CR>j", 'xt') + call assert_equal('foo bar', getline('.')) + call feedkeys("Qopen /bar/\<CR>", 'xt') + call assert_equal(5, col('.')) + call assert_fails('call feedkeys("Qopen /baz/\<CR>", "xt")', 'E479:') + close! +endfunc + +" Test for :g/pat/visual to run vi commands in Ex mode +" This used to hang Vim before 8.2.0274. +func Test_Ex_global() + new + call setline(1, ['', 'foo', 'bar', 'foo', 'bar', 'foo']) + call feedkeys("Q\<bs>g/bar/visual\<CR>$rxQ$ryQvisual\<CR>j", "xt") + call assert_equal('bax', getline(3)) + call assert_equal('bay', getline(5)) + bwipe! +endfunc + +" In Ex-mode, a backslash escapes a newline +func Test_Ex_escape_enter() + call feedkeys("gQlet l = \"a\\\<kEnter>b\"\<cr>vi\<cr>", 'xt') + call assert_equal("a\rb", l) +endfunc + +" Test for :append! command in Ex mode +func Test_Ex_append() + throw 'Skipped: Nvim only supports Vim Ex mode' + new + call setline(1, "\t abc") + call feedkeys("Qappend!\npqr\nxyz\n.\nvisual\n", 'xt') + call assert_equal(["\t abc", "\t pqr", "\t xyz"], getline(1, '$')) + close! +endfunc + +" In Ex-mode, backslashes at the end of a command should be halved. +func Test_Ex_echo_backslash() + throw 'Skipped: Nvim only supports Vim Ex mode' + " This test works only when the language is English + if v:lang != "C" && v:lang !~ '^[Ee]n' + return + endif + let bsl = '\\\\' + let bsl2 = '\\\' + call assert_fails('call feedkeys("Qecho " .. bsl .. "\nvisual\n", "xt")', + \ "E15: Invalid expression: \\\\") + call assert_fails('call feedkeys("Qecho " .. bsl2 .. "\nm\nvisual\n", "xt")', + \ "E15: Invalid expression: \\\nm") +endfunc + func Test_ex_mode_errors() " Not allowed to enter ex mode when text is locked au InsertCharPre <buffer> normal! gQ<CR> - let caught_e523 = 0 + let caught_e565 = 0 try call feedkeys("ix\<esc>", 'xt') - catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 - let caught_e523 = 1 + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565 + let caught_e565 = 1 endtry - call assert_equal(1, caught_e523) + call assert_equal(1, caught_e565) au! InsertCharPre new @@ -89,6 +191,9 @@ func Test_ex_mode_errors() endfunc func Test_ex_mode_count_overflow() + " The multiplication causes an integer overflow + CheckNotAsan + " this used to cause a crash let lines =<< trim END call feedkeys("\<Esc>gQ\<CR>") diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 7dde8a0439..7d581d5efd 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -22,6 +22,7 @@ func Test_range_error() call assert_fails(':\/echo 1', 'E481:') normal vv call assert_fails(":'<,'>echo 1", 'E481:') + call assert_fails(":\\xcenter", 'E10:') endfunc func Test_buffers_lastused() @@ -65,6 +66,10 @@ func Test_copy() 1,3copy 2 call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7)) + " Specifying a count before using : to run an ex-command + exe "normal! gg4:yank\<CR>" + call assert_equal("L1\nL2\nL1\nL2\n", @") + close! endfunc @@ -147,7 +152,6 @@ endfunc " Test for the :insert command func Test_insert_cmd() - set noautoindent " test assumes noautoindent, but it's on by default in Nvim new call setline(1, [' L1']) call feedkeys(":insert\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt') @@ -197,7 +201,6 @@ endfunc " Test for the :change command func Test_change_cmd() - set noautoindent " test assumes noautoindent, but it's on by default in Nvim new call setline(1, [' L1', 'L2', 'L3']) call feedkeys(":change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt') @@ -224,12 +227,24 @@ func Test_change_cmd() close! endfunc +" Test for the :language command +func Test_language_cmd() + CheckNotMSWindows " FIXME: why does this fail on Windows CI? + CheckNotBSD " FIXME: why does this fail on OpenBSD CI? + CheckFeature multi_lang + + call assert_fails('language ctype non_existing_lang', 'E197:') + call assert_fails('language time non_existing_lang', 'E197:') +endfunc + " Test for the :confirm command dialog func Test_confirm_cmd() CheckNotGui CheckRunVimInTerminal + call writefile(['foo1'], 'foo') call writefile(['bar1'], 'bar') + " Test for saving all the modified buffers let buf = RunVimInTerminal('', {'rows': 20}) call term_sendkeys(buf, ":set nomore\n") @@ -242,8 +257,10 @@ func Test_confirm_cmd() call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000) call term_sendkeys(buf, "A") call StopVimInTerminal(buf) + call assert_equal(['foo2'], readfile('foo')) call assert_equal(['bar2'], readfile('bar')) + " Test for discarding all the changes to modified buffers let buf = RunVimInTerminal('', {'rows': 20}) call term_sendkeys(buf, ":set nomore\n") @@ -256,8 +273,10 @@ func Test_confirm_cmd() call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000) call term_sendkeys(buf, "D") call StopVimInTerminal(buf) + call assert_equal(['foo2'], readfile('foo')) call assert_equal(['bar2'], readfile('bar')) + " Test for saving and discarding changes to some buffers let buf = RunVimInTerminal('', {'rows': 20}) call term_sendkeys(buf, ":set nomore\n") @@ -272,6 +291,7 @@ func Test_confirm_cmd() call WaitForAssert({-> assert_match('\[Y\]es, (N)o, (C)ancel: ', term_getline(buf, 20))}, 1000) call term_sendkeys(buf, "Y") call StopVimInTerminal(buf) + call assert_equal(['foo4'], readfile('foo')) call assert_equal(['bar2'], readfile('bar')) @@ -393,6 +413,11 @@ func Test_confirm_write_partial_file() call delete('Xscript') endfunc +" Test for the :print command +func Test_print_cmd() + call assert_fails('print', 'E749:') +endfunc + " Test for the :winsize command func Test_winsize_cmd() call assert_fails('winsize 1', 'E465:') @@ -401,6 +426,197 @@ func Test_winsize_cmd() " Actually changing the window size would be flaky. endfunc +" Test for the :redir command +func Test_redir_cmd() + call assert_fails('redir @@', 'E475:') + call assert_fails('redir abc', 'E475:') + if has('unix') + call mkdir('Xdir') + call assert_fails('redir > Xdir', 'E17:') + call delete('Xdir', 'd') + endif + if !has('bsd') + call writefile([], 'Xfile') + call setfperm('Xfile', 'r--r--r--') + call assert_fails('redir! > Xfile', 'E190:') + call delete('Xfile') + endif + + " Test for redirecting to a register + redir @q> | echon 'clean ' | redir END + redir @q>> | echon 'water' | redir END + call assert_equal('clean water', @q) + + " Test for redirecting to a variable + redir => color | echon 'blue ' | redir END + redir =>> color | echon 'sky' | redir END + call assert_equal('blue sky', color) +endfunc + +" Test for the :filetype command +func Test_filetype_cmd() + call assert_fails('filetype abc', 'E475:') +endfunc + +" Test for the :mode command +func Test_mode_cmd() + call assert_fails('mode abc', 'E359:') +endfunc + +" Test for the :sleep command +func Test_sleep_cmd() + call assert_fails('sleep x', 'E475:') +endfunc + +" Test for the :read command +func Test_read_cmd() + call writefile(['one'], 'Xfile') + new + call assert_fails('read', 'E32:') + edit Xfile + read + call assert_equal(['one', 'one'], getline(1, '$')) + close! + new + read Xfile + call assert_equal(['', 'one'], getline(1, '$')) + call deletebufline('', 1, '$') + call feedkeys("Qr Xfile\<CR>visual\<CR>", 'xt') + call assert_equal(['one'], getline(1, '$')) + close! + call delete('Xfile') +endfunc + +" Test for running Ex commands when text is locked. +" <C-\>e in the command line is used to lock the text +func Test_run_excmd_with_text_locked() + " :quit + let cmd = ":\<C-\>eexecute('quit')\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + + " :qall + let cmd = ":\<C-\>eexecute('qall')\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + + " :exit + let cmd = ":\<C-\>eexecute('exit')\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + + " :close - should be ignored + new + let cmd = ":\<C-\>eexecute('close')\<CR>\<C-C>" + call assert_equal(2, winnr('$')) + close + + call assert_fails("call feedkeys(\":\<C-R>=execute('bnext')\<CR>\", 'xt')", 'E565:') + + " :tabfirst + tabnew + call assert_fails("call feedkeys(\":\<C-R>=execute('tabfirst')\<CR>\", 'xt')", 'E565:') + tabclose +endfunc + +" Test for the :verbose command +func Test_verbose_cmd() + call assert_equal([' verbose=1'], split(execute('verbose set vbs'), "\n")) + call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n")) + let l = execute("4verbose set verbose | set verbose") + call assert_equal([' verbose=4', ' verbose=0'], split(l, "\n")) +endfunc + +" Test for the :delete command and the related abbreviated commands +func Test_excmd_delete() + new + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('dl'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('dell'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('delel'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('deletl'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('deletel'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('dp'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('dep'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('delp'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('delep'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('deletp'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('deletep'), "\n")) + close! +endfunc + +" Test for commands that are blocked in a sandbox +func Sandbox_tests() + call assert_fails("call histadd(':', 'ls')", 'E48:') + call assert_fails("call mkdir('Xdir')", 'E48:') + call assert_fails("call rename('a', 'b')", 'E48:') + call assert_fails("call setbufvar(1, 'myvar', 1)", 'E48:') + call assert_fails("call settabvar(1, 'myvar', 1)", 'E48:') + call assert_fails("call settabwinvar(1, 1, 'myvar', 1)", 'E48:') + call assert_fails("call setwinvar(1, 'myvar', 1)", 'E48:') + call assert_fails("call timer_start(100, '')", 'E48:') + if has('channel') + call assert_fails("call prompt_setcallback(1, '')", 'E48:') + call assert_fails("call prompt_setinterrupt(1, '')", 'E48:') + call assert_fails("call prompt_setprompt(1, '')", 'E48:') + endif + call assert_fails("let $TESTVAR=1", 'E48:') + call assert_fails("call feedkeys('ivim')", 'E48:') + call assert_fails("source! Xfile", 'E48:') + call assert_fails("call delete('Xfile')", 'E48:') + call assert_fails("call writefile([], 'Xfile')", 'E48:') + call assert_fails('!ls', 'E48:') + " call assert_fails('shell', 'E48:') + call assert_fails('stop', 'E48:') + call assert_fails('exe "normal \<C-Z>"', 'E48:') + " set insertmode + " call assert_fails('call feedkeys("\<C-Z>", "xt")', 'E48:') + " set insertmode& + call assert_fails('suspend', 'E48:') + call assert_fails('call system("ls")', 'E48:') + call assert_fails('call systemlist("ls")', 'E48:') + if has('clientserver') + call assert_fails('let s=remote_expr("gvim", "2+2")', 'E48:') + if !has('win32') + " remote_foreground() doesn't thrown an error message on MS-Windows + call assert_fails('call remote_foreground("gvim")', 'E48:') + endif + call assert_fails('let s=remote_peek("gvim")', 'E48:') + call assert_fails('let s=remote_read("gvim")', 'E48:') + call assert_fails('let s=remote_send("gvim", "abc")', 'E48:') + call assert_fails('let s=server2client("gvim", "abc")', 'E48:') + endif + if has('terminal') + call assert_fails('terminal', 'E48:') + call assert_fails('call term_start("vim")', 'E48:') + call assert_fails('call term_dumpwrite(1, "Xfile")', 'E48:') + endif + if has('channel') + call assert_fails("call ch_logfile('chlog')", 'E48:') + call assert_fails("call ch_open('localhost:8765')", 'E48:') + endif + if has('job') + call assert_fails("call job_start('vim')", 'E48:') + endif + if has('unix') && has('libcall') + call assert_fails("echo libcall('libc.so', 'getenv', 'HOME')", 'E48:') + endif + if has('unix') + call assert_fails('cd `pwd`', 'E48:') + endif +endfunc + +func Test_sandbox() + sandbox call Sandbox_tests() +endfunc + func Test_not_break_expression_register() call setreg('=', '1+1') if 0 diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim index fd34be83b0..471c77853d 100644 --- a/src/nvim/testdir/test_exists.vim +++ b/src/nvim/testdir/test_exists.vim @@ -94,8 +94,12 @@ func Test_exists() call assert_equal(0, exists(':edit/a')) " Valid internal command (partial match) call assert_equal(1, exists(':q')) + " Valid internal command with a digit + call assert_equal(2, exists(':2match')) " Non-existing internal command call assert_equal(0, exists(':invalidcmd')) + " Internal command with a count + call assert_equal(0, exists(':3buffer')) " User defined command (full match) command! MyCmd :echo 'My command' diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim index 48dce25bb3..d86fea4f45 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -1,5 +1,7 @@ " Test for expanding file names +source shared.vim + func Test_with_directories() call mkdir('Xdir1') call mkdir('Xdir2') @@ -79,5 +81,48 @@ func Test_expandcmd() call assert_fails('call expandcmd("make <afile>")', 'E495:') enew call assert_fails('call expandcmd("make %")', 'E499:') - close + let $FOO="blue\tsky" + call setline(1, "$FOO") + call assert_equal("grep pat blue\tsky", expandcmd('grep pat <cfile>')) + unlet $FOO + close! +endfunc + +" Test for expanding <sfile>, <slnum> and <sflnum> outside of sourcing a script +func Test_source_sfile() + let lines =<< trim [SCRIPT] + :call assert_fails('echo expandcmd("<sfile>")', 'E498:') + :call assert_fails('echo expandcmd("<slnum>")', 'E842:') + :call assert_fails('echo expandcmd("<sflnum>")', 'E961:') + :call assert_fails('call expandcmd("edit <cfile>")', 'E446:') + :call assert_fails('call expandcmd("edit #")', 'E194:') + :call assert_fails('call expandcmd("edit #<2")', 'E684:') + :call assert_fails('call expandcmd("edit <cword>")', 'E348:') + :call assert_fails('call expandcmd("edit <cexpr>")', 'E348:') + :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:') + :call writefile(v:errors, 'Xresult') + :qall! + + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -s Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test for expanding filenames multiple times in a command line +func Test_expand_filename_multicmd() + edit foo + call setline(1, 'foo!') + new + call setline(1, 'foo!') + new <cword> | new <cWORD> + call assert_equal(4, winnr('$')) + call assert_equal('foo!', bufname(winbufnr(1))) + call assert_equal('foo', bufname(winbufnr(2))) + %bwipe! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index 8e1cb2c3ee..c6e781a1ef 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -237,7 +237,7 @@ func Test_file_changed_dialog() sleep 2 silent !touch Xchanged_d let v:warningmsg = '' - checktime + checktime Xchanged_d call assert_equal('', v:warningmsg) call assert_equal(1, line('$')) call assert_equal('new line', getline(1)) @@ -245,3 +245,19 @@ func Test_file_changed_dialog() bwipe! call delete('Xchanged_d') endfunc + +" Test for editing a new buffer from a FileChangedShell autocmd +func Test_FileChangedShell_newbuf() + call writefile(['one', 'two'], 'Xfile') + new Xfile + augroup testnewbuf + autocmd FileChangedShell * enew + augroup END + sleep 10m " make the test less flaky in Nvim + call writefile(['red'], 'Xfile') + call assert_fails('checktime', 'E811:') + au! testnewbuf + call delete('Xfile') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 88c354a55b..59b272d563 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -83,6 +83,7 @@ let s:filename_checks = { \ 'bib': ['file.bib'], \ 'bicep': ['file.bicep'], \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'], + \ 'bitbake': ['file.bb', 'file.bbappend', 'file.bbclass', 'build/conf/local.conf', 'meta/conf/layer.conf', 'build/conf/bbappend.conf', 'meta-layer/conf/distro/foo.conf'], \ 'blank': ['file.bl'], \ 'bsdl': ['file.bsd', 'file.bsdl', 'bsd', 'some-bsd'], \ 'bst': ['file.bst'], @@ -115,7 +116,7 @@ let s:filename_checks = { \ 'coco': ['file.atg'], \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], - \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file'], + \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'], \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cook': ['file.cook'], @@ -128,6 +129,7 @@ let s:filename_checks = { \ 'csp': ['file.csp', 'file.fdr'], \ 'css': ['file.css'], \ 'cterm': ['file.con'], + \ 'csv': ['file.csv'], \ 'cucumber': ['file.feature'], \ 'cuda': ['file.cu', 'file.cuh'], \ 'cupl': ['file.pld'], @@ -208,7 +210,7 @@ let s:filename_checks = { \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], - \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], + \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], @@ -313,7 +315,6 @@ let s:filename_checks = { \ 'lotos': ['file.lot', 'file.lotos'], \ 'lout': ['file.lou', 'file.lout'], \ 'lpc': ['file.lpc', 'file.ulpc'], - \ 'lprolog': ['file.sig'], \ 'lsl': ['file.lsl'], \ 'lss': ['file.lss'], \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], @@ -564,6 +565,7 @@ let s:filename_checks = { \ 'tsscl': ['file.tsscl'], \ 'tssgm': ['file.tssgm'], \ 'tssop': ['file.tssop'], + \ 'tsv': ['file.tsv'], \ 'twig': ['file.twig'], \ 'typescript.glimmer': ['file.gts'], \ 'typescriptreact': ['file.tsx'], @@ -706,7 +708,8 @@ let s:script_checks = { \ 'awk': [['#!/path/awk'], \ ['#!/path/gawk']], \ 'wml': [['#!/path/wml']], - \ 'scheme': [['#!/path/scheme']], + \ 'scheme': [['#!/path/scheme'], + \ ['#!/path/guile']], \ 'cfengine': [['#!/path/cfengine']], \ 'erlang': [['#!/path/escript']], \ 'haskell': [['#!/path/haskell']], @@ -755,6 +758,30 @@ func Test_setfiletype_completion() call assert_equal('"setfiletype java javacc javascript javascriptreact', @:) endfunc +" Test for ':filetype detect' command for a buffer without a file +func Test_emptybuf_ftdetect() + new + call setline(1, '#!/bin/sh') + call assert_equal('', &filetype) + filetype detect + call assert_equal('sh', &filetype) + close! +endfunc + +" Test for ':filetype indent on' and ':filetype indent off' commands +func Test_filetype_indent_off() + new Xtest.vim + filetype indent on + call assert_equal(1, g:did_indent_on) + call assert_equal(['filetype detection:ON plugin:OFF indent:ON'], + \ execute('filetype')->split("\n")) + filetype indent off + call assert_equal(0, exists('g:did_indent_on')) + call assert_equal(['filetype detection:ON plugin:OFF indent:OFF'], + \ execute('filetype')->split("\n")) + close +endfunc + """"""""""""""""""""""""""""""""""""""""""""""""" " Tests for specific extensions and filetypes. " Keep sorted. @@ -1733,4 +1760,112 @@ func Test_cls_file() filetype off endfunc +func Test_sig_file() + filetype on + + call writefile(['this is neither Lambda Prolog nor SML'], 'Xfile.sig') + split Xfile.sig + call assert_equal('', &filetype) + bwipe! + + " Test dist#ft#FTsig() + + let g:filetype_sig = 'sml' + split Xfile.sig + call assert_equal('sml', &filetype) + bwipe! + unlet g:filetype_sig + + " Lambda Prolog + + call writefile(['sig foo.'], 'Xfile.sig') + split Xfile.sig + call assert_equal('lprolog', &filetype) + bwipe! + + call writefile(['/* ... */'], 'Xfile.sig') + split Xfile.sig + call assert_equal('lprolog', &filetype) + bwipe! + + call writefile(['% ...'], 'Xfile.sig') + split Xfile.sig + call assert_equal('lprolog', &filetype) + bwipe! + + " SML signature file + + call writefile(['signature FOO ='], 'Xfile.sig') + split Xfile.sig + call assert_equal('sml', &filetype) + bwipe! + + call writefile(['structure FOO ='], 'Xfile.sig') + split Xfile.sig + call assert_equal('sml', &filetype) + bwipe! + + call writefile(['(* ... *)'], 'Xfile.sig') + split Xfile.sig + call assert_equal('sml', &filetype) + bwipe! + + call delete('Xfile.sig') + filetype off +endfunc + +func Test_inc_file() + filetype on + + call writefile(['this is the fallback'], 'Xfile.inc') + split Xfile.inc + call assert_equal('pov', &filetype) + bwipe! + + let g:filetype_inc = 'foo' + split Xfile.inc + call assert_equal('foo', &filetype) + bwipe! + unlet g:filetype_inc + + " aspperl + call writefile(['perlscript'], 'Xfile.inc') + split Xfile.inc + call assert_equal('aspperl', &filetype) + bwipe! + + " aspvbs + call writefile(['<% something'], 'Xfile.inc') + split Xfile.inc + call assert_equal('aspvbs', &filetype) + bwipe! + + " php + call writefile(['<?php'], 'Xfile.inc') + split Xfile.inc + call assert_equal('php', &filetype) + bwipe! + + " pascal + call writefile(['program'], 'Xfile.inc') + split Xfile.inc + call assert_equal('pascal', &filetype) + bwipe! + + " bitbake + call writefile(['require foo'], 'Xfile.inc') + split Xfile.inc + call assert_equal('bitbake', &filetype) + bwipe! + + " asm + call writefile(['asmsyntax=bar'], 'Xfile.inc') + split Xfile.inc + call assert_equal('bar', &filetype) + bwipe! + + call delete('Xfile.inc') + filetype off +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim deleted file mode 100644 index d9c0dcba9c..0000000000 --- a/src/nvim/testdir/test_filetype_lua.vim +++ /dev/null @@ -1,3 +0,0 @@ -let g:do_filetype_lua = 1 -let g:did_load_filetypes = 0 -source test_filetype.vim diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim index d465e48c7b..dae164b11c 100644 --- a/src/nvim/testdir/test_filter_cmd.vim +++ b/src/nvim/testdir/test_filter_cmd.vim @@ -45,6 +45,14 @@ func Test_filter_fails() call assert_fails('filter /pat', 'E476:') call assert_fails('filter /pat/', 'E476:') call assert_fails('filter /pat/ asdf', 'E492:') + " Using assert_fails() causes E476 instead of E866. So use a try-catch. + let caught_e866 = 0 + try + filter /\@>b/ ls + catch /E866:/ + let caught_e866 = 1 + endtry + call assert_equal(1, caught_e866) call assert_fails('filter!', 'E471:') call assert_fails('filter! pat', 'E476:') diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 1684c5d30a..0f4b30aec2 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -193,12 +193,14 @@ func Test_find_cmd() set path=.,./**/* call CreateFiles() cd Xdir1 + " Test for :find find foo call assert_equal('foo', expand('%:.')) 2find foo call assert_equal('Xdir2/foo', expand('%:.')) call assert_fails('3find foo', 'E347:') + " Test for :sfind enew sfind barfoo @@ -207,6 +209,7 @@ func Test_find_cmd() close call assert_fails('sfind baz', 'E345:') call assert_equal(2, winnr('$')) + " Test for :tabfind enew tabfind foobar @@ -215,7 +218,8 @@ func Test_find_cmd() tabclose call assert_fails('tabfind baz', 'E345:') call assert_equal(1, tabpagenr('$')) - " call chdir(save_dir) + + call chdir(save_dir) exe 'cd ' . save_dir call CleanFiles() let &path = save_path diff --git a/src/nvim/testdir/test_float_func.vim b/src/nvim/testdir/test_float_func.vim index 1e0c75c49d..902a011a9d 100644 --- a/src/nvim/testdir/test_float_func.vim +++ b/src/nvim/testdir/test_float_func.vim @@ -1,8 +1,7 @@ " test float functions -if !has('float') - finish -end +source check.vim +CheckFeature float func Test_abs() call assert_equal('1.23', string(abs(1.23))) diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 2391b4a485..9b8d740efb 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -185,7 +185,9 @@ func Test_str2nr() call assert_fails('call str2nr([])', 'E730:') call assert_fails('call str2nr({->2})', 'E729:') - call assert_fails('call str2nr(1.2)', 'E806:') + if has('float') + call assert_fails('call str2nr(1.2)', 'E806:') + endif call assert_fails('call str2nr(10, [])', 'E474:') endfunc @@ -325,11 +327,18 @@ func Test_simplify() call assert_equal('./file', simplify('./dir/../file')) call assert_equal('../dir/file', simplify('dir/../../dir/file')) call assert_equal('./file', simplify('dir/.././file')) + call assert_equal('../dir', simplify('./../dir')) + call assert_equal('..', simplify('../testdir/..')) + call mkdir('Xdir') + call assert_equal('.', simplify('Xdir/../.')) + call delete('Xdir', 'd') call assert_fails('call simplify({->0})', 'E729:') call assert_fails('call simplify([])', 'E730:') call assert_fails('call simplify({})', 'E731:') - call assert_fails('call simplify(1.2)', 'E806:') + if has('float') + call assert_fails('call simplify(1.2)', 'E806:') + endif endfunc func Test_pathshorten() @@ -1218,6 +1227,16 @@ func Test_input_func() call assert_fails("call input('F:', '', [])", 'E730:') endfunc +" Test for the inputdialog() function +func Test_inputdialog() + CheckNotGui + + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') + call assert_equal('xx', v) + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') + call assert_equal('yy', v) +endfunc + " Test for inputlist() func Test_inputlist() call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>1\<cr>", 'tx') diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index 589899f532..1569177d66 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -35,6 +35,13 @@ func Test_gf_url() call search("URL") call assert_equal("URL://machine.name:1234?q=vim", expand("<cfile>")) + %d + call setline(1, "demo://remote_file") + wincmd f + call assert_equal('demo://remote_file', @%) + call assert_equal(2, winnr('$')) + close! + set isf&vim enew! endfunc @@ -63,11 +70,18 @@ func Test_gF() call assert_equal('Xfile', bufname('%')) call assert_equal(2, getcurpos()[1]) + " jumping to the file/line with CTRL-W_F + %bw! + edit Xfile1 + call setline(1, ['one', 'Xfile:4', 'three']) + exe "normal 2G\<C-W>F" + call assert_equal('Xfile', bufname('%')) + call assert_equal(4, getcurpos()[1]) + set isfname& call delete('Xfile') call delete('Xfile2') - bwipe Xfile - bwipe Xfile2 + %bw! endfunc " Test for invoking 'gf' on a ${VAR} variable @@ -137,7 +151,7 @@ func Test_gf_visual() bwipe! call delete('Xtest_gf_visual') - set nohidden + set hidden& endfunc func Test_gf_error() @@ -147,5 +161,37 @@ func Test_gf_error() call setline(1, '/doesnotexist') call assert_fails('normal gf', 'E447:') call assert_fails('normal gF', 'E447:') + call assert_fails('normal [f', 'E447:') + + " gf is not allowed when text is locked + au InsertCharPre <buffer> normal! gF<CR> + let caught_e565 = 0 + try + call feedkeys("ix\<esc>", 'xt') + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565 + let caught_e565 = 1 + endtry + call assert_equal(1, caught_e565) + au! InsertCharPre + bwipe! endfunc + +" If a file is not found by 'gf', then 'includeexpr' should be used to locate +" the file. +func Test_gf_includeexpr() + new + let g:Inc_fname = '' + func IncFunc() + let g:Inc_fname = v:fname + return v:fname + endfunc + setlocal includeexpr=IncFunc() + call setline(1, 'somefile.java') + call assert_fails('normal gf', 'E447:') + call assert_equal('somefile.java', g:Inc_fname) + close! + delfunc IncFunc +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index ad561baf4a..feddf85346 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -66,6 +66,18 @@ func Test_global_print() close! endfunc +" Test for global command with newline character +func Test_global_newline() + new + call setline(1, ['foo']) + exe "g/foo/s/f/h/\<NL>s/o$/w/" + call assert_equal('how', getline(1)) + call setline(1, ["foo\<NL>bar"]) + exe "g/foo/s/foo\\\<NL>bar/xyz/" + call assert_equal('xyz', getline(1)) + close! +endfunc + func Test_wrong_delimiter() call assert_fails('g x^bxd', 'E146:') endfunc diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index d09b25b0e7..c4a41a6742 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -154,8 +154,24 @@ func Test_gn_command() norm! gg0f2vf7gNd call assert_equal(['1678'], getline(1,'$')) sil! %d _ - set wrapscan&vim + + " Without 'wrapscan', in visual mode, running gn without a match should fail + " but the visual mode should be kept. + set nowrapscan + call setline('.', 'one two') + let @/ = 'one' + call assert_beeps('normal 0wvlgn') + exe "normal y" + call assert_equal('tw', @") + + " with exclusive selection, run gn and gN + set selection=exclusive + normal 0gny + call assert_equal('one', @") + normal 0wgNy + call assert_equal('one', @") + set selection& endfunc func Test_gN_repeat() diff --git a/src/nvim/testdir/test_goto.vim b/src/nvim/testdir/test_goto.vim index 19513b315a..49095400ef 100644 --- a/src/nvim/testdir/test_goto.vim +++ b/src/nvim/testdir/test_goto.vim @@ -334,21 +334,24 @@ endfunc func Test_motion_if_elif_else_endif() new - a -/* Test pressing % on #if, #else #elsif and #endif, - * with nested #if - */ -#if FOO -/* ... */ -# if BAR -/* ... */ -# endif -#elif BAR -/* ... */ -#else -/* ... */ -#endif -. + let lines =<< trim END + /* Test pressing % on #if, #else #elsif and #endif, + * with nested #if + */ + #if FOO + /* ... */ + # if BAR + /* ... */ + # endif + #elif BAR + /* ... */ + #else + /* ... */ + #endif + + #define FOO 1 + END + call setline(1, lines) /#if FOO norm % call assert_equal([9, 1], getpos('.')[1:2]) @@ -364,6 +367,30 @@ func Test_motion_if_elif_else_endif() norm $% call assert_equal([6, 1], getpos('.')[1:2]) + " Test for [# and ]# command + call cursor(5, 1) + normal [# + call assert_equal([4, 1], getpos('.')[1:2]) + call cursor(5, 1) + normal ]# + call assert_equal([9, 1], getpos('.')[1:2]) + call cursor(10, 1) + normal [# + call assert_equal([9, 1], getpos('.')[1:2]) + call cursor(10, 1) + normal ]# + call assert_equal([11, 1], getpos('.')[1:2]) + + " Finding a match before the first line or after the last line should fail + normal gg + call assert_beeps('normal [#') + normal G + call assert_beeps('normal ]#') + + " Finding a match for a macro definition (#define) should fail + normal G + call assert_beeps('normal %') + bw! endfunc @@ -393,3 +420,5 @@ func Test_motion_c_comment() bw! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim index 9569cfa4e5..dbb36facee 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -92,6 +92,11 @@ func Test_help_local_additions() let &rtp = rtp_save endfunc +func Test_help_completion() + call feedkeys(":help :undo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"help :undo :undoj :undol :undojoin :undolist', @:) +endfunc + " Test for the :helptags command func Test_helptag_cmd() call mkdir('Xdir/a/doc', 'p') diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index ece8ccf215..e84726bbfc 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -125,6 +125,11 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*/\\bar\*') helpclose + help \_$ + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*/\\_$\*') + helpclose + help CTRL-\_CTRL-N call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*') diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim index 2f0dc2dae1..feb521e232 100644 --- a/src/nvim/testdir/test_history.vim +++ b/src/nvim/testdir/test_history.vim @@ -1,8 +1,7 @@ " Tests for the history functions -if !has('cmdline_hist') - finish -endif +source check.vim +CheckFeature cmdline_hist set history=7 @@ -71,6 +70,14 @@ function History_Tests(hist) call assert_equal('', histget(a:hist, i)) call assert_equal('', histget(a:hist, i - 7 - 1)) endfor + + " Test for freeing an entry at the beginning of the history list + for i in range(1, 4) + call histadd(a:hist, 'text_' . i) + endfor + call histdel(a:hist, 1) + call assert_equal('', histget(a:hist, 1)) + call assert_equal('text_4', histget(a:hist, 4)) endfunction function Test_History() @@ -86,6 +93,8 @@ function Test_History() call assert_fails('call histget([])', 'E730:') call assert_equal(-1, histnr('abc')) call assert_fails('call histnr([])', 'E730:') + call assert_fails('history xyz', 'E488:') + call assert_fails('history ,abc', 'E488:') endfunction function Test_Search_history_window() @@ -105,7 +114,122 @@ function Test_Search_history_window() bwipe! endfunc +" Test for :history command option completion function Test_history_completion() call feedkeys(":history \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"history / : = > ? @ all cmd debug expr input search', @:) endfunc + +" Test for increasing the 'history' option value +func Test_history_size() + let save_histsz = &history + set history=10 + call histadd(':', 'ls') + call histdel(':') + for i in range(1, 5) + call histadd(':', 'cmd' .. i) + endfor + call assert_equal(5, histnr(':')) + call assert_equal('cmd5', histget(':', -1)) + + set history=15 + for i in range(6, 10) + call histadd(':', 'cmd' .. i) + endfor + call assert_equal(10, histnr(':')) + call assert_equal('cmd1', histget(':', 1)) + call assert_equal('cmd10', histget(':', -1)) + + set history=5 + call histadd(':', 'abc') + call assert_equal('', histget(':', 6)) + call assert_equal('', histget(':', 12)) + call assert_equal('cmd7', histget(':', 7)) + call assert_equal('abc', histget(':', -1)) + + " This test works only when the language is English + if v:lang == "C" || v:lang =~ '^[Ee]n' + set history=0 + redir => v + call feedkeys(":history\<CR>", 'xt') + redir END + call assert_equal(["'history' option is zero"], split(v, "\n")) + endif + + let &history=save_histsz +endfunc + +" Test for recalling old search patterns in / +func Test_history_search() + call histdel('/') + let g:pat = [] + func SavePat() + call add(g:pat, getcmdline()) + return '' + endfunc + cnoremap <F2> <C-\>eSavePat()<CR> + call histadd('/', 'pat1') + call histadd('/', 'pat2') + let @/ = '' + call feedkeys("/\<Up>\<F2>\<Up>\<F2>\<Down>\<Down>\<F2>\<Esc>", 'xt') + call assert_equal(['pat2', 'pat1', ''], g:pat) + cunmap <F2> + delfunc SavePat + + " Search for a pattern that is not present in the history + call assert_beeps('call feedkeys("/a1b2\<Up>\<CR>", "xt")') + + " Recall patterns with 'history' set to 0 + set history=0 + let @/ = 'abc' + let cmd = 'call feedkeys("/\<Up>\<Down>\<S-Up>\<S-Down>\<CR>", "xt")' + call assert_fails(cmd, 'E486:') + set history& + + " Recall patterns till the end of history + set history=4 + call histadd('/', 'pat') + call histdel('/') + call histadd('/', 'pat1') + call histadd('/', 'pat2') + call assert_beeps('call feedkeys("/\<Up>\<Up>\<Up>\<C-U>\<cr>", "xt")') + call assert_beeps('call feedkeys("/\<Down><cr>", "xt")') + + " Test for wrapping around the history list + for i in range(3, 7) + call histadd('/', 'pat' .. i) + endfor + let upcmd = "\<up>\<up>\<up>\<up>\<up>" + let downcmd = "\<down>\<down>\<down>\<down>\<down>" + try + call feedkeys("/" .. upcmd .. "\<cr>", 'xt') + catch /E486:/ + endtry + call assert_equal('pat4', @/) + try + call feedkeys("/" .. upcmd .. downcmd .. "\<cr>", 'xt') + catch /E486:/ + endtry + call assert_equal('pat4', @/) + + " Test for changing the search command separator in the history + call assert_fails('call feedkeys("/def/\<cr>", "xt")', 'E486:') + call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:') + call assert_equal('def?', histget('/', -1)) + + call assert_fails('call feedkeys("/ghi?\<cr>", "xt")', 'E486:') + call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:') + call assert_equal('ghi\?', histget('/', -1)) + + set history& +endfunc + +" Test for making sure the key value is not stored in history +func Test_history_crypt_key() + CheckFeature cryptv + call feedkeys(":set bs=2 key=abc ts=8\<CR>", 'xt') + call assert_equal('set bs=2 key= ts=8', histget(':')) + set key& bs& ts& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index 12fe52f057..2559654f25 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -776,6 +776,14 @@ func Test_increment_empty_line() call setline(1, ['0', '0', '0', '0', '0', '0', '']) exe "normal Gvgg\<C-A>" call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7)) + + " Ctrl-A/Ctrl-X should do nothing in operator pending mode + %d + call setline(1, 'one two') + exe "normal! c\<C-A>l" + exe "normal! c\<C-X>l" + call assert_equal('one two', getline(1)) + bwipe! endfunc diff --git a/src/nvim/testdir/test_indent.vim b/src/nvim/testdir/test_indent.vim index 516b3fbdd1..3b5b643177 100644 --- a/src/nvim/testdir/test_indent.vim +++ b/src/nvim/testdir/test_indent.vim @@ -121,4 +121,159 @@ func Test_copyindent() close! endfunc +" Test for changing multiple lines with lisp indent +func Test_lisp_indent_change_multiline() + new + setlocal lisp autoindent + call setline(1, ['(if a', ' (if b', ' (return 5)))']) + normal! jc2j(return 4)) + call assert_equal(' (return 4))', getline(2)) + close! +endfunc + +func Test_lisp_indent() + new + setlocal lisp autoindent + call setline(1, ['(if a', ' ;; comment', ' \ abc', '', ' " str1\', ' " st\b', ' (return 5)']) + normal! jo;; comment + normal! jo\ abc + normal! jo;; ret + normal! jostr1" + normal! jostr2" + call assert_equal([' ;; comment', ' ;; comment', ' \ abc', ' \ abc', '', ' ;; ret', ' " str1\', ' str1"', ' " st\b', ' str2"'], getline(2, 11)) + close! +endfunc + +func Test_lisp_indent_quoted() + " This was going past the end of the line + new + setlocal lisp autoindent + call setline(1, ['"[', '=']) + normal Gvk= + + bwipe! +endfunc + +" Test for setting the 'indentexpr' from a modeline +func Test_modeline_indent_expr() + let modeline = &modeline + set modeline + func GetIndent() + return line('.') * 2 + endfunc + call writefile(['# vim: indentexpr=GetIndent()'], 'Xfile.txt') + set modelineexpr + new Xfile.txt + call assert_equal('GetIndent()', &indentexpr) + exe "normal Oa\nb\n" + call assert_equal([' a', ' b'], getline(1, 2)) + + set modelineexpr& + delfunc GetIndent + let &modeline = modeline + close! + call delete('Xfile.txt') +endfunc + +func Test_indent_func_with_gq() + + function GetTeXIndent() + " Sample indent expression for TeX files + let lnum = prevnonblank(v:lnum - 1) + " At the start of the file use zero indent. + if lnum == 0 + return 0 + endif + let line = getline(lnum) + let ind = indent(lnum) + " Add a 'shiftwidth' after beginning of environments. + if line =~ '\\begin{center}' + let ind = ind + shiftwidth() + endif + return ind + endfunction + + new + setl et sw=2 sts=2 ts=2 tw=50 indentexpr=GetTeXIndent() + put =[ '\documentclass{article}', '', '\begin{document}', '', + \ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ut enim non', + \ 'libero efficitur aliquet. Maecenas metus justo, facilisis convallis blandit', + \ 'non, semper eu urna. Suspendisse diam diam, iaculis faucibus lorem eu,', + \ 'fringilla condimentum lectus. Quisque euismod diam at convallis vulputate.', + \ 'Pellentesque laoreet tortor sit amet mauris euismod ornare. Sed varius', + \ 'bibendum orci vel vehicula. Pellentesque tempor, ipsum et auctor accumsan,', + \ 'metus lectus ultrices odio, sed elementum mi ante at arcu.', '', '\begin{center}', '', + \ 'Proin nec risus consequat nunc dapibus consectetur. Mauris lacinia est a augue', + \ 'tristique accumsan. Morbi pretium, felis molestie eleifend condimentum, arcu', + \ 'ipsum congue nisl, quis euismod purus libero in ante.', '', + \ 'Donec id semper purus.', + \ 'Suspendisse eget aliquam nunc. Maecenas fringilla mauris vitae maximus', + \ 'condimentum. Cras a quam in mi dictum eleifend at a lorem. Sed convallis', + \ 'ante a commodo facilisis. Nam suscipit vulputate odio, vel dapibus nisl', + \ 'dignissim facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et', + \ 'ultrices posuere cubilia curae;', '', ''] + 1d_ + call cursor(5, 1) + ka + call cursor(14, 1) + kb + norm! 'agqap + norm! 'bgqG + let expected = [ '\documentclass{article}', '', '\begin{document}', '', + \ 'Lorem ipsum dolor sit amet, consectetur adipiscing', + \ 'elit. Fusce ut enim non libero efficitur aliquet.', + \ 'Maecenas metus justo, facilisis convallis blandit', + \ 'non, semper eu urna. Suspendisse diam diam,', + \ 'iaculis faucibus lorem eu, fringilla condimentum', + \ 'lectus. Quisque euismod diam at convallis', + \ 'vulputate. Pellentesque laoreet tortor sit amet', + \ 'mauris euismod ornare. Sed varius bibendum orci', + \ 'vel vehicula. Pellentesque tempor, ipsum et auctor', + \ 'accumsan, metus lectus ultrices odio, sed', + \ 'elementum mi ante at arcu.', '', '\begin{center}', '', + \ ' Proin nec risus consequat nunc dapibus', + \ ' consectetur. Mauris lacinia est a augue', + \ ' tristique accumsan. Morbi pretium, felis', + \ ' molestie eleifend condimentum, arcu ipsum congue', + \ ' nisl, quis euismod purus libero in ante.', + \ '', + \ ' Donec id semper purus. Suspendisse eget aliquam', + \ ' nunc. Maecenas fringilla mauris vitae maximus', + \ ' condimentum. Cras a quam in mi dictum eleifend', + \ ' at a lorem. Sed convallis ante a commodo', + \ ' facilisis. Nam suscipit vulputate odio, vel', + \ ' dapibus nisl dignissim facilisis. Vestibulum', + \ ' ante ipsum primis in faucibus orci luctus et', + \ ' ultrices posuere cubilia curae;', '', ''] + call assert_equal(expected, getline(1, '$')) + + bwipe! + delmark ab + delfunction GetTeXIndent +endfu + +func Test_formatting_keeps_first_line_indent() + let lines =<< trim END + foo() + { + int x; // manually positioned + // more text that will be formatted + // but not reindented + END + new + call setline(1, lines) + setlocal sw=4 cindent tw=45 et + normal! 4Ggqj + let expected =<< trim END + foo() + { + int x; // manually positioned + // more text that will be + // formatted but not + // reindented + END + call assert_equal(expected, getline(1, '$')) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index c8104c6b73..93ab17955d 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -95,7 +95,7 @@ func Test_ins_complete() call delete('Xtest11.one') call delete('Xtest11.two') call delete('Xtestdata') - set cpt& cot& def& tags& tagbsearch& nohidden + set cpt& cot& def& tags& tagbsearch& hidden& cd .. call delete('Xdir', 'rf') endfunc @@ -601,6 +601,107 @@ func Test_ins_compl_tag_sft() %bwipe! endfunc +" Test for 'completefunc' deleting text +func Test_completefunc_error() + new + " delete text when called for the first time + func CompleteFunc(findstart, base) + if a:findstart == 1 + normal dd + return col('.') - 1 + endif + return ['a', 'b'] + endfunc + set completefunc=CompleteFunc + call setline(1, ['', 'abcd', '']) + call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:') + + " delete text when called for the second time + func CompleteFunc2(findstart, base) + if a:findstart == 1 + return col('.') - 1 + endif + normal dd + return ['a', 'b'] + endfunc + set completefunc=CompleteFunc2 + call setline(1, ['', 'abcd', '']) + call assert_fails('exe "normal 2G$a\<C-X>\<C-U>"', 'E565:') + + " Jump to a different window from the complete function + func CompleteFunc3(findstart, base) + if a:findstart == 1 + return col('.') - 1 + endif + wincmd p + return ['a', 'b'] + endfunc + set completefunc=CompleteFunc3 + new + call assert_fails('exe "normal a\<C-X>\<C-U>"', 'E565:') + close! + + set completefunc& + delfunc CompleteFunc + delfunc CompleteFunc2 + delfunc CompleteFunc3 + close! +endfunc + +" Test for returning non-string values from 'completefunc' +func Test_completefunc_invalid_data() + new + func! CompleteFunc(findstart, base) + if a:findstart == 1 + return col('.') - 1 + endif + return [{}, '', 'moon'] + endfunc + set completefunc=CompleteFunc + exe "normal i\<C-X>\<C-U>" + call assert_equal('moon', getline(1)) + set completefunc& + close! +endfunc + +" Test for errors in using complete() function +func Test_complete_func_error() + call assert_fails('call complete(1, ["a"])', 'E785:') + func ListColors() + call complete(col('.'), "blue") + endfunc + call assert_fails('exe "normal i\<C-R>=ListColors()\<CR>"', 'E474:') + func ListMonths() + call complete(col('.'), test_null_list()) + endfunc + " Nvim allows a NULL list + " call assert_fails('exe "normal i\<C-R>=ListMonths()\<CR>"', 'E474:') + delfunc ListColors + delfunc ListMonths + call assert_fails('call complete_info({})', 'E714:') + call assert_equal([], complete_info(['items']).items) +endfunc + +" Test for completing words following a completed word in a line +func Test_complete_wrapscan() + " complete words from another buffer + new + call setline(1, ['one two', 'three four']) + new + setlocal complete=w + call feedkeys("itw\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>", 'xt') + call assert_equal('two three four', getline(1)) + close! + " complete words from the current buffer + setlocal complete=. + %d + call setline(1, ['one two', '']) + call cursor(2, 1) + call feedkeys("ion\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>", 'xt') + call assert_equal('one two one two', getline(2)) + close! +endfunc + " Test for completing special characters func Test_complete_special_chars() new diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim index ecb969d10a..1f7a0825a5 100644 --- a/src/nvim/testdir/test_join.vim +++ b/src/nvim/testdir/test_join.vim @@ -51,7 +51,7 @@ func Test_join_marks() /^This line/;'}-join call assert_equal([0, 4, 11, 0], getpos("'[")) - call assert_equal([0, 4, 66, 0], getpos("']")) + call assert_equal([0, 4, 67, 0], getpos("']")) enew! endfunc @@ -437,5 +437,11 @@ func Test_join_lines() call setline(1, ['a', 'b', '', 'c', 'd']) normal 5J call assert_equal('a b c d', getline(1)) + call setline(1, ['a', 'b', 'c']) + 2,2join + call assert_equal(['a', 'b', 'c'], getline(1, '$')) + call assert_equal(2, line('.')) + 2join + call assert_equal(['a', 'b c'], getline(1, '$')) bwipe! endfunc diff --git a/src/nvim/testdir/test_langmap.vim b/src/nvim/testdir/test_langmap.vim index eca8a95564..4f831aa40b 100644 --- a/src/nvim/testdir/test_langmap.vim +++ b/src/nvim/testdir/test_langmap.vim @@ -1,5 +1,8 @@ " tests for 'langmap' +source check.vim +CheckFeature langmap + func Test_langmap() new set langmap=}l,^x,%v diff --git a/src/nvim/testdir/test_legacy_filetype.vim b/src/nvim/testdir/test_legacy_filetype.vim new file mode 100644 index 0000000000..772faaadb0 --- /dev/null +++ b/src/nvim/testdir/test_legacy_filetype.vim @@ -0,0 +1,4 @@ +let g:do_legacy_filetype = 1 +filetype on + +source test_filetype.vim diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index 2fda12d8b4..affa0f96fa 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -2,9 +2,9 @@ scriptencoding latin1 -if !exists("+linebreak") || !has("conceal") - finish -endif +source check.vim +CheckOption linebreak +CheckFeature conceal source view_util.vim diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 1f100d6244..df1ed78119 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -3,9 +3,10 @@ set encoding=utf-8 scriptencoding utf-8 -if !exists("+linebreak") || !has("conceal") || !has("signs") - finish -endif +source check.vim +CheckOption linebreak +CheckFeature conceal +CheckFeature signs source view_util.vim diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim index 2b346e0720..c53c07d991 100644 --- a/src/nvim/testdir/test_makeencoding.vim +++ b/src/nvim/testdir/test_makeencoding.vim @@ -4,8 +4,7 @@ source shared.vim let s:python = PythonProg() if s:python == '' - " Can't run this test. - finish + throw 'Skipped: python program missing' endif let s:script = 'test_makeencoding.py' diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index 494f09e0e5..e1d0b9a9ba 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -31,6 +31,7 @@ func Test_abclear() abclear call assert_equal("\n\nNo abbreviation found", execute('abbrev')) + call assert_fails('%abclear', 'E481:') endfunc func Test_abclear_buffer() @@ -642,6 +643,13 @@ func Test_map_error() map <expr> ,f abc call assert_fails('normal ,f', 'E121:') unmap <expr> ,f + + " Recursive use of :normal in a map + set maxmapdepth=100 + map gq :normal gq<CR> + call assert_fails('normal gq', 'E192:') + unmap gq + set maxmapdepth& endfunc " Test for <special> key mapping @@ -948,6 +956,16 @@ func Test_map_cmdkey_redo() ounmap i- endfunc +" Test for using <script> with a map to remap characters in rhs +func Test_script_local_remap() + new + inoremap <buffer> <SID>xyz mno + inoremap <buffer> <script> abc st<SID>xyzre + normal iabc + call assert_equal('stmnore', getline(1)) + bwipe! +endfunc + func Test_abbreviate_multi_byte() new iabbrev foo bar diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 00ee8f6d6a..74e63d9d69 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -215,6 +215,10 @@ func Test_mark_error() call assert_fails('mark', 'E471:') call assert_fails('mark xx', 'E488:') call assert_fails('mark _', 'E191:') + call assert_beeps('normal! m~') + + call setpos("'k", [0, 100, 1, 0]) + call assert_fails("normal 'k", 'E19:') endfunc " Test for :lockmarks when pasting content @@ -232,6 +236,38 @@ func Test_lockmarks_with_put() bwipe! endfunc +" Test for :k command to set a mark +func Test_marks_k_cmd() + new + call setline(1, ['foo', 'bar', 'baz', 'qux']) + 1,3kr + call assert_equal([0, 3, 1, 0], getpos("'r")) + close! +endfunc + +" Test for file marks (A-Z) +func Test_file_mark() + new Xone + call setline(1, ['aaa', 'bbb']) + norm! G$mB + w! + new Xtwo + call setline(1, ['ccc', 'ddd']) + norm! GmD + w! + + enew + normal! `B + call assert_equal('Xone', bufname()) + call assert_equal([2, 3], [line('.'), col('.')]) + normal! 'D + call assert_equal('Xtwo', bufname()) + call assert_equal([2, 1], [line('.'), col('.')]) + + call delete('Xone') + call delete('Xtwo') +endfunc + " Test for the getmarklist() function func Test_getmarklist() new diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim index 1d0c740734..f33c7f694c 100644 --- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim +++ b/src/nvim/testdir/test_matchadd_conceal_utf8.vim @@ -1,7 +1,7 @@ " Test for matchadd() and conceal feature using utf-8. -if !has('conceal') - finish -endif + +source check.vim +CheckFeature conceal func s:screenline(lnum) abort let line = [] diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim index 6b4949ad8c..c836bc87aa 100644 --- a/src/nvim/testdir/test_matchfuzzy.vim +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -59,7 +59,7 @@ func Test_matchfuzzy() %bw! eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)}) - let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl') + let l = getbufinfo()->map({_, v -> fnamemodify(v.name, ':t')})->matchfuzzy('ndl') call assert_equal(1, len(l)) call assert_match('needle', l[0]) diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index de6d4aa359..4af75be514 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -1,8 +1,7 @@ " Test that the system menu can be loaded. -if !has('menu') - finish -endif +source check.vim +CheckFeature menu func Test_load_menu() try @@ -36,3 +35,92 @@ func Test_translate_menu() source $VIMRUNTIME/delmenu.vim endfunc + +func Test_menu_commands() + nmenu 2 Test.FooBar :let g:did_menu = 'normal'<CR> + vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR> + smenu 2 Test.FooBar :let g:did_menu = 'select'<CR> + omenu 2 Test.FooBar :let g:did_menu = 'op-pending'<CR> + tlmenu 2 Test.FooBar :let g:did_menu = 'terminal'<CR> + imenu 2 Test.FooBar :let g:did_menu = 'insert'<CR> + cmenu 2 Test.FooBar :let g:did_menu = 'cmdline'<CR> + emenu n Test.FooBar + + call feedkeys(":menu Test.FooB\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"menu Test.FooBar', @:) + + call assert_equal('normal', g:did_menu) + emenu v Test.FooBar + call assert_equal('visual', g:did_menu) + emenu s Test.FooBar + call assert_equal('select', g:did_menu) + emenu o Test.FooBar + call assert_equal('op-pending', g:did_menu) + emenu t Test.FooBar + call assert_equal('terminal', g:did_menu) + emenu i Test.FooBar + call assert_equal('insert', g:did_menu) + emenu c Test.FooBar + call assert_equal('cmdline', g:did_menu) + + nunmenu Test.FooBar + call assert_fails('emenu n Test.FooBar', 'E335: Menu not defined for Normal mode') + vunmenu Test.FooBar + call assert_fails('emenu v Test.FooBar', 'E335: Menu not defined for Visual mode') + vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR> + sunmenu Test.FooBar + call assert_fails('emenu s Test.FooBar', 'E335: Menu not defined for Select mode') + ounmenu Test.FooBar + call assert_fails('emenu o Test.FooBar', 'E335: Menu not defined for Op-pending mode') + iunmenu Test.FooBar + call assert_fails('emenu i Test.FooBar', 'E335: Menu not defined for Insert mode') + cunmenu Test.FooBar + call assert_fails('emenu c Test.FooBar', 'E335: Menu not defined for Cmdline mode') + tlunmenu Test.FooBar + call assert_fails('emenu t Test.FooBar', 'E335: Menu not defined for Terminal mode') + + aunmenu Test.FooBar + call assert_fails('emenu n Test.FooBar', 'E334:') + + nmenu 2 Test.FooBar.Child :let g:did_menu = 'foobar'<CR> + call assert_fails('emenu n Test.FooBar', 'E333:') + nunmenu Test.FooBar.Child + + unlet g:did_menu +endfun + +" Test for menu item completion in command line +func Test_menu_expand() + " Create the menu itmes for test + for i in range(1, 4) + let m = 'menu Xmenu.A' .. i .. '.A' .. i + for j in range(1, 4) + exe m .. 'B' .. j .. ' :echo "A' .. i .. 'B' .. j .. '"' .. "<CR>" + endfor + endfor + set wildmenu + + " Test for <CR> selecting a submenu + call feedkeys(":emenu Xmenu.A\<Tab>\<CR>\<Right>x\<BS>\<C-B>\"\<CR>", 'xt') + call assert_equal('"emenu Xmenu.A1.A1B2', @:) + + " Test for <Down> selecting a submenu + call feedkeys(":emenu Xmenu.A\<Tab>\<Right>\<Right>\<Down>" .. + \ "\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"emenu Xmenu.A3.A3B1 A3B2 A3B3 A3B4', @:) + + " Test for <Up> to go up a submenu + call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Right>\<Right>" .. + \ "\<Left>\<Down>\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"emenu Xmenu.A2.A2B1 A2B2 A2B3 A2B4', @:) + + " Test for <Up> to go up a menu + call feedkeys(":emenu Xmenu.A\<Tab>\<Down>\<Up>\<Up>\<Up>" .. + \ "\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"emenu Buffers. Xmenu.', @:) + + set wildmenu& + unmenu Xmenu +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 8be0c79499..5670368936 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -112,6 +112,136 @@ func Test_echospace() set ruler& showcmd& endfunc +" Test more-prompt (see :help more-prompt). +func Test_message_more() + CheckRunVimInTerminal + let buf = RunVimInTerminal('', {'rows': 6}) + call term_sendkeys(buf, ":call setline(1, range(1, 100))\n") + + call term_sendkeys(buf, ":%p#\n") + call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))}) + + call term_sendkeys(buf, '?') + call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit ', term_getline(buf, 6))}) + + " Down a line with j, <CR>, <NL> or <Down>. + call term_sendkeys(buf, "j") + call WaitForAssert({-> assert_equal(' 6 6', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))}) + call term_sendkeys(buf, "\<NL>") + call WaitForAssert({-> assert_equal(' 7 7', term_getline(buf, 5))}) + call term_sendkeys(buf, "\<CR>") + call WaitForAssert({-> assert_equal(' 8 8', term_getline(buf, 5))}) + call term_sendkeys(buf, "\<Down>") + call WaitForAssert({-> assert_equal(' 9 9', term_getline(buf, 5))}) + + " Down a screen with <Space>, f, or <PageDown>. + call term_sendkeys(buf, 'f') + call WaitForAssert({-> assert_equal(' 14 14', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))}) + call term_sendkeys(buf, ' ') + call WaitForAssert({-> assert_equal(' 19 19', term_getline(buf, 5))}) + call term_sendkeys(buf, "\<PageDown>") + call WaitForAssert({-> assert_equal(' 24 24', term_getline(buf, 5))}) + + " Down a page (half a screen) with d. + call term_sendkeys(buf, 'd') + call WaitForAssert({-> assert_equal(' 27 27', term_getline(buf, 5))}) + + " Down all the way with 'G'. + call term_sendkeys(buf, 'G') + call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))}) + + " Up a line k, <BS> or <Up>. + call term_sendkeys(buf, 'k') + call WaitForAssert({-> assert_equal(' 99 99', term_getline(buf, 5))}) + call term_sendkeys(buf, "\<BS>") + call WaitForAssert({-> assert_equal(' 98 98', term_getline(buf, 5))}) + call term_sendkeys(buf, "\<Up>") + call WaitForAssert({-> assert_equal(' 97 97', term_getline(buf, 5))}) + + " Up a screen with b or <PageUp>. + call term_sendkeys(buf, 'b') + call WaitForAssert({-> assert_equal(' 92 92', term_getline(buf, 5))}) + call term_sendkeys(buf, "\<PageUp>") + call WaitForAssert({-> assert_equal(' 87 87', term_getline(buf, 5))}) + + " Up a page (half a screen) with u. + call term_sendkeys(buf, 'u') + call WaitForAssert({-> assert_equal(' 84 84', term_getline(buf, 5))}) + + " Up all the way with 'g'. + call term_sendkeys(buf, 'g') + call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))}) + + " All the way down. Pressing f should do nothing but pressing + " space should end the more prompt. + call term_sendkeys(buf, 'G') + call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))}) + call term_sendkeys(buf, 'f') + call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))}) + call term_sendkeys(buf, ' ') + call WaitForAssert({-> assert_equal('100', term_getline(buf, 5))}) + + " Pressing g< shows the previous command output. + call term_sendkeys(buf, 'g<') + call WaitForAssert({-> assert_equal('100 100', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))}) + + call term_sendkeys(buf, ":%p#\n") + call WaitForAssert({-> assert_equal(' 5 5', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))}) + + " Stop command output with q, <Esc> or CTRL-C. + call term_sendkeys(buf, 'q') + call WaitForAssert({-> assert_equal('100', term_getline(buf, 5))}) + + " Execute a : command from the more prompt + call term_sendkeys(buf, ":%p#\n") + call term_wait(buf) + call WaitForAssert({-> assert_equal('-- More --', term_getline(buf, 6))}) + call term_sendkeys(buf, ":") + call term_wait(buf) + call WaitForAssert({-> assert_equal(':', term_getline(buf, 6))}) + call term_sendkeys(buf, "echo 'Hello'\n") + call term_wait(buf) + call WaitForAssert({-> assert_equal('Hello ', term_getline(buf, 5))}) + + call StopVimInTerminal(buf) +endfunc + +func Test_ask_yesno() + CheckRunVimInTerminal + let buf = RunVimInTerminal('', {'rows': 6}) + call term_sendkeys(buf, ":call setline(1, range(1, 2))\n") + + call term_sendkeys(buf, ":2,1s/^/n/\n") + call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))}) + call term_sendkeys(buf, "n") + call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?n *1,1 *All$', term_getline(buf, 6))}) + call WaitForAssert({-> assert_equal('1', term_getline(buf, 1))}) + + call term_sendkeys(buf, ":2,1s/^/Esc/\n") + call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))}) + call term_sendkeys(buf, "\<Esc>") + call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?n *1,1 *All$', term_getline(buf, 6))}) + call WaitForAssert({-> assert_equal('1', term_getline(buf, 1))}) + + call term_sendkeys(buf, ":2,1s/^/y/\n") + call WaitForAssert({-> assert_equal('Backwards range given, OK to swap (y/n)?', term_getline(buf, 6))}) + call term_sendkeys(buf, "y") + call WaitForAssert({-> assert_match('^Backwards range given, OK to swap (y/n)?y *2,1 *All$', term_getline(buf, 6))}) + call WaitForAssert({-> assert_equal('y1', term_getline(buf, 1))}) + call WaitForAssert({-> assert_equal('y2', term_getline(buf, 2))}) + + call StopVimInTerminal(buf) +endfunc + func Test_mapping_at_hit_return_prompt() nnoremap <C-B> :echo "hit ctrl-b"<CR> call feedkeys(":ls\<CR>", "xt") diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index cf90e416c4..8ec408e62e 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -2,9 +2,8 @@ scriptencoding latin1 -if !has('mksession') - finish -endif +source check.vim +CheckFeature mksession source shared.vim source term_util.vim @@ -43,9 +42,9 @@ func Test_mksession() \ ' four leadinG spaces', \ 'two consecutive tabs', \ 'two tabs in one line', - \ 'one ä multibyteCharacter', - \ 'aä Ä two multiByte characters', - \ 'Aäöü three mulTibyte characters', + \ 'one ä multibyteCharacter', + \ 'aä Ä two multiByte characters', + \ 'Aäöü three mulTibyte characters', \ 'short line', \ ]) let tmpfile = 'Xtemp' diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim index 722fd28beb..4e593cc21a 100644 --- a/src/nvim/testdir/test_mksession_utf8.vim +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -3,9 +3,8 @@ set encoding=utf-8 scriptencoding utf-8 -if !has('mksession') - finish -endif +source check.vim +CheckFeature mksession func Test_mksession_utf8() tabnew diff --git a/src/nvim/testdir/test_move.vim b/src/nvim/testdir/test_move.vim index f666a904b0..8c40369dbd 100644 --- a/src/nvim/testdir/test_move.vim +++ b/src/nvim/testdir/test_move.vim @@ -38,6 +38,7 @@ func Test_move() call assert_fails("move -100", 'E16:') call assert_fails("move +100", 'E16:') call assert_fails('move', 'E16:') + call assert_fails("move 'r", 'E20:') %bwipeout! endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index d1cb89bbd4..9fbd1f774a 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1,6 +1,8 @@ " Test for various Normal mode commands source shared.vim +source check.vim +source view_util.vim func Setup_NewWindow() 10new @@ -53,7 +55,7 @@ func OpfuncDummy(type, ...) let g:bufnr=bufnr('%') endfunc -fun! Test_normal00_optrans() +func Test_normal00_optrans() new call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line']) 1 @@ -95,6 +97,12 @@ func Test_normal01_keymodel() 50 call feedkeys("\<S-Up>y", 'tx') call assert_equal(['49', '5'], getreg(0, 0, 1)) + " Use the different Shift special keys + 50 + call feedkeys("\<S-Right>\<S-Left>\<S-Up>\<S-Down>\<S-Home>\<S-End>y", 'tx') + call assert_equal(['50'], getline("'<", "'>")) + call assert_equal(['50', ''], getreg(0, 0, 1)) + " Do not start visual mode when keymodel= set keymodel= 50 @@ -115,8 +123,8 @@ func Test_normal01_keymodel() bw! endfunc +" Test for select mode func Test_normal02_selectmode() - " some basic select mode tests call Setup_NewWindow() 50 norm! gHy @@ -345,7 +353,7 @@ func Test_normal08_fold() bw! endfunc -func Test_normal09_operatorfunc() +func Test_normal09a_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -375,7 +383,7 @@ func Test_normal09_operatorfunc() bw! endfunc -func Test_normal09a_operatorfunc() +func Test_normal09b_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -397,10 +405,45 @@ func Test_normal09a_operatorfunc() " clean up unmap <buffer> ,, set opfunc= + call assert_fails('normal Vg@', 'E774:') bw! unlet! g:opt endfunc +func OperatorfuncRedo(_) + let g:opfunc_count = v:count +endfunc + +func Underscorize(_) + normal! '[V']r_ +endfunc + +func Test_normal09c_operatorfunc() + " Test redoing operatorfunc + new + call setline(1, 'some text') + set operatorfunc=OperatorfuncRedo + normal v3g@ + call assert_equal(3, g:opfunc_count) + let g:opfunc_count = 0 + normal . + call assert_equal(3, g:opfunc_count) + + bw! + unlet g:opfunc_count + + " Test redoing Visual mode + set operatorfunc=Underscorize + new + call setline(1, ['first', 'first', 'third', 'third', 'second']) + normal! 1GVjg@ + normal! 5G. + normal! 3G. + call assert_equal(['_____', '_____', '_____', '_____', '______'], getline(1, '$')) + bwipe! + set operatorfunc= +endfunc + func Test_normal10_expand() " Test for expand() 10new @@ -451,13 +494,33 @@ func Test_normal11_showcmd() bw! endfunc +" Test for nv_error and normal command errors func Test_normal12_nv_error() - " Test for nv_error 10new call setline(1, range(1,5)) " should not do anything, just beep - exe "norm! <c-k>" + call assert_beeps('exe "norm! <c-k>"') call assert_equal(map(range(1,5), 'string(v:val)'), getline(1,'$')) + call assert_beeps('normal! G2dd') + call assert_beeps("normal! g\<C-A>") + call assert_beeps("normal! g\<C-X>") + call assert_beeps("normal! g\<C-B>") + " call assert_beeps("normal! vQ\<Esc>") + call assert_beeps("normal! 2[[") + call assert_beeps("normal! 2]]") + call assert_beeps("normal! 2[]") + call assert_beeps("normal! 2][") + call assert_beeps("normal! 4[z") + call assert_beeps("normal! 4]z") + call assert_beeps("normal! 4[c") + call assert_beeps("normal! 4]c") + call assert_beeps("normal! 200%") + call assert_beeps("normal! %") + call assert_beeps("normal! 2{") + call assert_beeps("normal! 2}") + call assert_beeps("normal! r\<Right>") + call assert_beeps("normal! 8ry") + call assert_beeps('normal! "@') bw! endfunc @@ -619,6 +682,13 @@ func Test_normal16_z_scroll_hor() $put =lineB 1d + " Test for zl and zh with a count + norm! 0z10l + call assert_equal([11, 1], [col('.'), wincol()]) + norm! z4h + call assert_equal([11, 5], [col('.'), wincol()]) + normal! 2gg + " Test for zl 1 norm! 5zl @@ -741,15 +811,134 @@ func Test_normal17_z_scroll_hor2() bw! endfunc +" Test for commands that scroll the window horizontally. Test with folds. +" H, M, L, CTRL-E, CTRL-Y, CTRL-U, CTRL-D, PageUp, PageDown commands +func Test_vert_scroll_cmds() + 15new + call setline(1, range(1, 100)) + exe "normal! 30ggz\<CR>" + set foldenable + 33,36fold + 40,43fold + 46,49fold + let h = winheight(0) + + " Test for H, M and L commands + " Top of the screen = 30 + " Folded lines = 9 + " Bottom of the screen = 30 + h + 9 - 1 + normal! 4L + call assert_equal(35 + h, line('.')) + normal! 4H + call assert_equal(33, line('.')) + + " Test for the CTRL-E and CTRL-Y commands with folds + %d + call setline(1, range(1, 10)) + 3,5fold + exe "normal 6G3\<C-E>" + call assert_equal(6, line('w0')) + exe "normal 2\<C-Y>" + call assert_equal(2, line('w0')) + + " Test for CTRL-Y on a folded line + %d + call setline(1, range(1, 100)) + exe (h + 2) .. "," .. (h + 4) .. "fold" + exe h + 5 + normal z- + exe "normal \<C-Y>\<C-Y>" + call assert_equal(h + 1, line('w$')) + + " Using <PageUp> and <PageDown> in an empty buffer should beep + %d + call assert_beeps('exe "normal \<PageUp>"') + call assert_beeps('exe "normal \<C-B>"') + call assert_beeps('exe "normal \<PageDown>"') + call assert_beeps('exe "normal \<C-F>"') + + " Test for <C-U> and <C-D> with fold + %d + call setline(1, range(1, 100)) + 10,35fold + set scroll=10 + exe "normal \<C-D>" + call assert_equal(36, line('.')) + exe "normal \<C-D>" + call assert_equal(46, line('.')) + exe "normal \<C-U>" + call assert_equal(36, line('.')) + exe "normal \<C-U>" + call assert_equal(10, line('.')) + exe "normal \<C-U>" + call assert_equal(1, line('.')) + set scroll& + + " Test for scrolling to the top of the file with <C-U> and a fold + 10 + normal ztL + exe "normal \<C-U>\<C-U>" + call assert_equal(1, line('w0')) + + " Test for CTRL-D on a folded line + %d + call setline(1, range(1, 100)) + 50,100fold + 75 + normal z- + exe "normal \<C-D>" + call assert_equal(50, line('.')) + call assert_equal(100, line('w$')) + normal z. + let lnum = winline() + exe "normal \<C-D>" + call assert_equal(lnum, winline()) + call assert_equal(50, line('.')) + normal zt + exe "normal \<C-D>" + call assert_equal(50, line('w0')) + + set foldenable& + close! +endfunc + +" Test for the 'sidescroll' option +func Test_sidescroll_opt() + new + 20vnew + + " scroll by 2 characters horizontally + set sidescroll=2 nowrap + call setline(1, repeat('a', 40)) + normal g$l + call assert_equal(19, screenpos(0, 1, 21).col) + normal l + call assert_equal(20, screenpos(0, 1, 22).col) + normal g0h + call assert_equal(2, screenpos(0, 1, 2).col) + call assert_equal(20, screenpos(0, 1, 20).col) + + " when 'sidescroll' is 0, cursor positioned at the center + set sidescroll=0 + normal g$l + call assert_equal(11, screenpos(0, 1, 21).col) + normal g0h + call assert_equal(10, screenpos(0, 1, 10).col) + + %bw! + set wrap& sidescroll& +endfunc + +" basic tests for foldopen/folddelete func Test_normal18_z_fold() - " basic tests for foldopen/folddelete - if !has("folding") - return - endif + CheckFeature folding call Setup_NewWindow() 50 setl foldenable fdm=marker foldlevel=5 + call assert_beeps('normal! zj') + call assert_beeps('normal! zk') + " Test for zF " First fold norm! 4zF @@ -1182,6 +1371,9 @@ func Test_normal22_zet() let a = readfile('Xfile_Test_normal22_zet') call assert_equal(['1', '2'], a) + " Unsupported Z command + call assert_beeps('normal! ZW') + " Nvim: This sometimes hangs the TSAN build. " for file in ['Xfile_Test_normal22_zet'] " call delete(file) @@ -1250,6 +1442,15 @@ func Test_normal23_K() call assert_match("man --pager=cat 'man'", a) endif + " Error cases + call setline(1, '#$#') + call assert_fails('normal! ggK', 'E349:') + call setline(1, '---') + call assert_fails('normal! ggv2lK', 'E349:') + call setline(1, ['abc', 'xyz']) + call assert_fails("normal! gg2lv2h\<C-]>", 'E426:') + call assert_beeps("normal! ggVjK") + " clean up let &keywordprg = k bw! @@ -1383,9 +1584,16 @@ func Test_normal27_bracket() call assert_equal(5, line('.')) call assert_equal(3, col('.')) - " No mark after line 21, cursor moves to first non blank on current line + " No mark before line 1, cursor moves to first non-blank on current line + 1 + norm! 5|[' + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(3, col('.')) + + " No mark after line 21, cursor moves to first non-blank on current line 21 - norm! $]' + norm! 5|]' call assert_equal(' 21 b', getline('.')) call assert_equal(21, line('.')) call assert_equal(3, col('.')) @@ -1402,12 +1610,40 @@ func Test_normal27_bracket() call assert_equal(20, line('.')) call assert_equal(8, col('.')) + " No mark before line 1, cursor does not move + 1 + norm! 5|[` + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(5, col('.')) + + " No mark after line 21, cursor does not move + 21 + norm! 5|]` + call assert_equal(' 21 b', getline('.')) + call assert_equal(21, line('.')) + call assert_equal(5, col('.')) + + " Count too large for [` + " cursor moves to first lowercase mark + norm! 99[` + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(7, col('.')) + + " Count too large for ]` + " cursor moves to last lowercase mark + norm! 99]` + call assert_equal(' 20 b', getline('.')) + call assert_equal(20, line('.')) + call assert_equal(8, col('.')) + " clean up bw! endfunc +" Test for ( and ) sentence movements func Test_normal28_parenthesis() - " basic testing for ( and ) new call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) @@ -1425,12 +1661,43 @@ func Test_normal28_parenthesis() norm! $d( call assert_equal(['With some sentences!', '', ' ', '', 'This is a long sentence', ''], getline(1, '$')) + " Move to the next sentence from a paragraph macro + %d + call setline(1, ['.LP', 'blue sky!. blue sky.', 'blue sky. blue sky.']) + call cursor(1, 1) + normal ) + call assert_equal([2, 1], [line('.'), col('.')]) + normal ) + call assert_equal([2, 12], [line('.'), col('.')]) + normal (( + call assert_equal([1, 1], [line('.'), col('.')]) + + " It is an error if a next sentence is not found + %d + call setline(1, '.SH') + call assert_beeps('normal )') + + " If only dot is present, don't treat that as a sentence + call setline(1, '. This is a sentence.') + normal $(( + call assert_equal(3, col('.')) + + " Jumping to a fold should open the fold + call setline(1, ['', '', 'one', 'two', 'three']) + set foldenable + 2,$fold + call feedkeys(')', 'xt') + call assert_equal(3, line('.')) + call assert_equal(1, foldlevel('.')) + call assert_equal(-1, foldclosed('.')) + set foldenable& + " clean up bw! endfunc -fun! Test_normal29_brace() - " basic test for { and } movements +" Test for { and } paragraph movements +func Test_normal29_brace() let text =<< trim [DATA] A paragraph begins after each empty line, and also at each of a set of paragraph macros, specified by the pairs of characters in the 'paragraphs' @@ -1583,12 +1850,24 @@ fun! Test_normal29_brace() " [DATA] " call assert_equal(expected, getline(1, '$')) + " Jumping to a fold should open the fold + " %d + " call setline(1, ['', 'one', 'two', '']) + " set foldenable + " 2,$fold + " call feedkeys('}', 'xt') + " call assert_equal(4, line('.')) + " call assert_equal(1, foldlevel('.')) + " call assert_equal(-1, foldclosed('.')) + " set foldenable& + " clean up set cpo-={ bw! endfunc -fun! Test_normal30_changecase() +" Test for ~ command +func Test_normal30_changecase() new call append(0, 'This is a simple test: äüöß') norm! 1ggVu @@ -1608,8 +1887,23 @@ fun! Test_normal30_changecase() norm! V~ call assert_equal('THIS IS A simple test: äüöss', getline('.')) - " Turkish ASCII turns to multi-byte. On some systems Turkish locale - " is available but toupper()/tolower() don't do the right thing. + " Test for changing case across lines using 'whichwrap' + call setline(1, ['aaaaaa', 'aaaaaa']) + normal! gg10~ + call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2)) + set whichwrap+=~ + normal! gg10~ + call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) + set whichwrap& + + " clean up + bw! +endfunc + +" Turkish ASCII turns to multi-byte. On some systems Turkish locale +" is available but toupper()/tolower() don't do the right thing. +func Test_normal_changecase_turkish() + new try lang tr_TR.UTF-8 set casemap= @@ -1653,13 +1947,11 @@ fun! Test_normal30_changecase() " can't use Turkish locale throw 'Skipped: Turkish locale not available' endtry - - " clean up - bw! + close! endfunc -fun! Test_normal31_r_cmd() - " Test for r command +" Test for r (replace) command +func Test_normal31_r_cmd() new call append(0, 'This is a simple test: abcd') exe "norm! 1gg$r\<cr>" @@ -1678,13 +1970,29 @@ fun! Test_normal31_r_cmd() exe "norm! 1gg05rf" call assert_equal('fffffis a', getline(1)) + " When replacing characters, copy characters from above and below lines + " using CTRL-Y and CTRL-E. + " Different code paths are used for utf-8 and latin1 encodings + set showmatch + for enc in ['latin1', 'utf-8'] + enew! + let &encoding = enc + call setline(1, [' {a}', 'xxxxxxxxxx', ' [b]']) + exe "norm! 2gg5r\<C-Y>l5r\<C-E>" + call assert_equal(' {a}x [b]x', getline(2)) + endfor + set showmatch& + + " r command should fail in operator pending mode + call assert_beeps('normal! cr') + " clean up set noautoindent bw! endfunc +" Test for g*, g# func Test_normal32_g_cmd1() - " Test for g*, g# new call append(0, ['abc.x_foo', 'x_foobar.abc']) 1 @@ -1699,11 +2007,12 @@ func Test_normal32_g_cmd1() bw! endfunc -fun! Test_normal33_g_cmd2() +" Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, +" gi and gI commands +func Test_normal33_g_cmd2() if !has("jumplist") return endif - " Tests for g cmds call Setup_NewWindow() " Test for g` clearjumps @@ -1715,6 +2024,10 @@ fun! Test_normal33_g_cmd2() call assert_equal('>', a[-1:]) call assert_equal(1, line('.')) call assert_equal('1', getline('.')) + call cursor(10, 1) + norm! g'a + call assert_equal('>', a[-1:]) + call assert_equal(1, line('.')) " Test for g; and g, norm! g; @@ -1745,6 +2058,16 @@ fun! Test_normal33_g_cmd2() norm! g& call assert_equal(['11', '22', '33', '44', '55', '66', '77', '88', '9', '110', 'a', 'b', 'c', 'dd'], getline(1, '$')) + " Jumping to a fold using gg should open the fold + set foldenable + set foldopen+=jump + 5,8fold + call feedkeys('6gg', 'xt') + call assert_equal(1, foldlevel('.')) + call assert_equal(-1, foldclosed('.')) + set foldopen-=jump + set foldenable& + " Test for gv %d call append('$', repeat(['abcdefgh'], 8)) @@ -1756,14 +2079,20 @@ fun! Test_normal33_g_cmd2() exe "norm! G0\<c-v>4k4ly" exe "norm! gvood" call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$')) + " gv cannot be used in operator pending mode + call assert_beeps('normal! cgv') + " gv should beep without a previously selected visual area + new + call assert_beeps('normal! gv') + close " Test for gk/gj %d 15vsp set wrap listchars= sbr= - let lineA='abcdefghijklmnopqrstuvwxyz' - let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let lineA = 'abcdefghijklmnopqrstuvwxyz' + let lineB = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let lineC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $put =lineA $put =lineB @@ -1796,8 +2125,39 @@ fun! Test_normal33_g_cmd2() norm! g^yl call assert_equal(15, col('.')) call assert_equal('l', getreg(0)) + call assert_beeps('normal 5g$') + + " Test for g$ with double-width character half displayed + vsplit + 9wincmd | + setlocal nowrap nonumber + call setline(2, 'asdfasdfヨ') + 2 + normal 0g$ + call assert_equal(8, col('.')) + 10wincmd | + normal 0g$ + call assert_equal(9, col('.')) + + setlocal signcolumn=yes + 11wincmd | + normal 0g$ + call assert_equal(8, col('.')) + 12wincmd | + normal 0g$ + call assert_equal(9, col('.')) + + close - norm! 2ggdd + " Test for g_ + call assert_beeps('normal! 100g_') + call setline(2, [' foo ', ' foobar ']) + normal! 2ggg_ + call assert_equal(5, col('.')) + normal! 2g_ + call assert_equal(8, col('.')) + + norm! 2ggdG $put =lineC " Test for gM @@ -1839,6 +2199,21 @@ fun! Test_normal33_g_cmd2() $put ='third line' norm! gi another word call assert_equal(['foobar next word another word', 'new line', 'third line'], getline(1,'$')) + call setline(1, 'foobar') + normal! Ggifirst line + call assert_equal('foobarfirst line', getline(1)) + " Test gi in 'virtualedit' mode with cursor after the end of the line + set virtualedit=all + call setline(1, 'foo') + exe "normal! Abar\<Right>\<Right>\<Right>\<Right>" + call setline(1, 'foo') + normal! Ggifirst line + call assert_equal('foo first line', getline(1)) + set virtualedit& + + " Test for aboring a g command using CTRL-\ CTRL-G + exe "normal! g\<C-\>\<C-G>" + call assert_equal('foo first line', getline('.')) " clean up bw! @@ -1860,6 +2235,10 @@ func Test_g_ctrl_g() let a = execute(":norm! g\<c-g>") call assert_equal("\n--No lines in buffer--", a) + " Test for CTRL-G (same as :file) + let a = execute(":norm! \<c-g>") + call assert_equal("\n\n\"[No Name]\" --No lines in buffer--", a) + call setline(1, ['first line', 'second line']) " Test g CTRL-g with dos, mac and unix file type. @@ -1927,8 +2306,8 @@ func Test_g_ctrl_g() bwipe! endfunc -fun! Test_normal34_g_cmd3() - " Test for g8 +" Test for g8 +func Test_normal34_g_cmd3() new let a=execute(':norm! 1G0g8') call assert_equal("\nNUL", a) @@ -1945,11 +2324,10 @@ fun! Test_normal34_g_cmd3() bw! endfunc +" Test 8g8 which finds invalid utf8 at or after the cursor. func Test_normal_8g8() new - " Test 8g8 which finds invalid utf8 at or after the cursor. - " With invalid byte. call setline(1, "___\xff___") norm! 1G08g8g @@ -1978,8 +2356,8 @@ func Test_normal_8g8() bw! endfunc -fun! Test_normal35_g_cmd4() - " Test for g< +" Test for g< +func Test_normal35_g_cmd4() " Cannot capture its output, " probably a bug, therefore, test disabled: throw "Skipped: output of g< can't be tested currently" @@ -1988,7 +2366,8 @@ fun! Test_normal35_g_cmd4() call assert_true(!empty(b), 'failed `execute(g<)`') endfunc -fun! Test_normal36_g_cmd5() +" Test for gp gP go +func Test_normal36_g_cmd5() new call append(0, 'abcdefghijklmnopqrstuvwxyz') set ff=unix @@ -2026,8 +2405,8 @@ fun! Test_normal36_g_cmd5() bw! endfunc -fun! Test_normal37_g_cmd6() - " basic test for gt and gT +" Test for gt and gT +func Test_normal37_g_cmd6() tabnew 1.txt tabnew 2.txt tabnew 3.txt @@ -2050,11 +2429,11 @@ fun! Test_normal37_g_cmd6() tabclose endfor " clean up - call assert_fails(':tabclose', 'E784') + call assert_fails(':tabclose', 'E784:') endfunc -fun! Test_normal38_nvhome() - " Test for <Home> and <C-Home> key +" Test for <Home> and <C-Home> key +func Test_normal38_nvhome() new call setline(1, range(10)) $ @@ -2069,12 +2448,28 @@ fun! Test_normal38_nvhome() call assert_equal([0, 5, 1, 0, 1], getcurpos()) exe "norm! \<c-home>" call assert_equal([0, 1, 1, 0, 1], getcurpos()) + exe "norm! G\<c-kHome>" + call assert_equal([0, 1, 1, 0, 1], getcurpos()) " clean up bw! endfunc -fun! Test_normal39_cw() +" Test for <End> and <C-End> keys +func Test_normal_nvend() + new + call setline(1, map(range(1, 10), '"line" .. v:val')) + exe "normal! \<End>" + call assert_equal(5, col('.')) + exe "normal! 4\<End>" + call assert_equal([4, 5], [line('.'), col('.')]) + exe "normal! \<C-End>" + call assert_equal([10, 6], [line('.'), col('.')]) + close! +endfunc + +" Test for cw cW ce +func Test_normal39_cw() " Test for cw and cW on whitespace " and cpo+=w setting new @@ -2095,12 +2490,27 @@ fun! Test_normal39_cw() norm! 2gg0cwfoo call assert_equal('foo', getline('.')) + call setline(1, 'one; two') + call cursor(1, 1) + call feedkeys('cwvim', 'xt') + call assert_equal('vim; two', getline(1)) + call feedkeys('0cWone', 'xt') + call assert_equal('one two', getline(1)) + "When cursor is at the end of a word 'ce' will change until the end of the + "next word, but 'cw' will change only one character + call setline(1, 'one two') + call feedkeys('0ecwce', 'xt') + call assert_equal('once two', getline(1)) + call setline(1, 'one two') + call feedkeys('0ecely', 'xt') + call assert_equal('only', getline(1)) + " clean up bw! endfunc -fun! Test_normal40_ctrl_bsl() - " Basic test for CTRL-\ commands +" Test for CTRL-\ commands +func Test_normal40_ctrl_bsl() new call append(0, 'here are some words') exe "norm! 1gg0a\<C-\>\<C-N>" @@ -2118,15 +2528,19 @@ fun! Test_normal40_ctrl_bsl() exe ":norm! \<c-\>\<c-n>dw" " set noim call assert_equal('are some words', getline(1)) - " call assert_false(&insertmode) + call assert_false(&insertmode) + call assert_beeps("normal! \<C-\>\<C-A>") + + " Using CTRL-\ CTRL-N in cmd window should close the window + call feedkeys("q:\<C-\>\<C-N>", 'xt') + call assert_equal('', getcmdwintype()) " clean up bw! endfunc -fun! Test_normal41_insert_reg() - " Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= - " in insert mode +" Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= in insert mode +func Test_normal41_insert_reg() new set sts=2 sw=2 ts=8 tw=0 call append(0, ["aaa\tbbb\tccc", '', '', '']) @@ -2144,8 +2558,8 @@ fun! Test_normal41_insert_reg() bw! endfunc +" Test for Ctrl-D and Ctrl-U func Test_normal42_halfpage() - " basic test for Ctrl-D and Ctrl-U call Setup_NewWindow() call assert_equal(5, &scroll) exe "norm! \<c-d>" @@ -2181,92 +2595,6 @@ func Test_normal42_halfpage() bw! endfunc -fun! Test_normal43_textobject1() - " basic tests for text object aw - new - call append(0, ['foobar,eins,foobar', 'foo,zwei,foo ']) - " diw - norm! 1gg0diw - call assert_equal([',eins,foobar', 'foo,zwei,foo ', ''], getline(1,'$')) - " daw - norm! 2ggEdaw - call assert_equal([',eins,foobar', 'foo,zwei,', ''], getline(1, '$')) - %d - call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "]) - " diW - norm! 2ggwd2iW - call assert_equal(['foo eins foobar', 'foo foo ', ''], getline(1,'$')) - " daW - norm! 1ggd2aW - call assert_equal(['foobar', 'foo foo ', ''], getline(1,'$')) - - %d - call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "]) - " aw in visual line mode switches to characterwise mode - norm! 2gg$Vawd - call assert_equal(['foo eins foobar', 'foo zwei foo'], getline(1,'$')) - norm! 1gg$Viwd - call assert_equal(['foo eins ', 'foo zwei foo'], getline(1,'$')) - - " clean up - bw! -endfunc - -func Test_normal44_textobjects2() - " basic testing for is and as text objects - new - call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) - " Test for dis - does not remove trailing whitespace - norm! 1gg0dis - call assert_equal([' With some sentences!', '', 'Even with a question? And one more. And no sentence here', ''], getline(1,'$')) - " Test for das - removes leading whitespace - norm! 3ggf?ldas - call assert_equal([' With some sentences!', '', 'Even with a question? And no sentence here', ''], getline(1,'$')) - " when used in visual mode, is made characterwise - norm! 3gg$Visy - call assert_equal('v', visualmode()) - " reset visualmode() - norm! 3ggVy - norm! 3gg$Vasy - call assert_equal('v', visualmode()) - " basic testing for textobjects a< and at - %d - call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' ']) - " a< - norm! 1gg0da< - call assert_equal([' ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) - norm! 1pj - call assert_equal([' <div>', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) - " at - norm! d2at - call assert_equal([' '], getline(1,'$')) - %d - call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' ']) - " i< - norm! 1gg0di< - call assert_equal(['<> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) - norm! 1Pj - call assert_equal(['<div> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) - norm! d2it - call assert_equal(['<div></div>',' '], getline(1,'$')) - " basic testing for a[ and i[ text object - %d - call setline(1, [' ', '[', 'one [two]', 'thre', ']']) - norm! 3gg0di[ - call assert_equal([' ', '[', ']'], getline(1,'$')) - call setline(1, [' ', '[', 'one [two]', 'thre', ']']) - norm! 3gg0ftd2a[ - call assert_equal([' '], getline(1,'$')) - %d - " Test for i" when cursor is in front of a quoted object - call append(0, 'foo "bar"') - norm! 1gg0di" - call assert_equal(['foo ""', ''], getline(1,'$')) - - " clean up - bw! -endfunc - func Test_normal45_drop() if !has('dnd') " The ~ register does not exist @@ -2353,7 +2681,7 @@ func Test_normal49_counts() endfunc func Test_normal50_commandline() - if !has("timers") || !has("cmdline_hist") || !has("vertsplit") + if !has("timers") || !has("cmdline_hist") return endif func! DoTimerWork(id) @@ -2415,6 +2743,8 @@ func Test_normal52_rl() call assert_equal(19, col('.')) call feedkeys("\<right>", 'tx') call assert_equal(18, col('.')) + call feedkeys("\<left>", 'tx') + call assert_equal(19, col('.')) call feedkeys("\<s-right>", 'tx') call assert_equal(13, col('.')) call feedkeys("\<c-right>", 'tx') @@ -2504,6 +2834,18 @@ func Test_gr_command() normal 4gro call assert_equal('ooooecond line', getline(2)) let &cpo = save_cpo + normal! ggvegrx + call assert_equal('xxxxx line', getline(1)) + exe "normal! gggr\<C-V>122" + call assert_equal('zxxxx line', getline(1)) + set virtualedit=all + normal! 15|grl + call assert_equal('zxxxx line l', getline(1)) + set virtualedit& + set nomodifiable + call assert_fails('normal! grx', 'E21:') + call assert_fails('normal! gRx', 'E21:') + set modifiable& enew! endfunc @@ -2526,6 +2868,8 @@ func Test_changelist() normal g; call assert_equal([2, 2], [line('.'), col('.')]) call assert_fails('normal g;', 'E662:') + new + call assert_fails('normal g;', 'E664:') %bwipe! let &ul = save_ul endfunc @@ -2572,6 +2916,10 @@ endfunc " Jumping to beginning and end of methods in Java-like languages func Test_java_motion() new + call assert_beeps('normal! [m') + call assert_beeps('normal! ]m') + call assert_beeps('normal! [M') + call assert_beeps('normal! ]M') a Piece of Java { @@ -2646,7 +2994,7 @@ Piece of Java close! endfunc -fun! Test_normal_gdollar_cmd() +func Test_normal_gdollar_cmd() if !has("jumplist") return endif @@ -2701,10 +3049,11 @@ fun! Test_normal_gdollar_cmd() bw! endfunc -func Test_normal_gk() +func Test_normal_gk_gj() " needs 80 column new window new vert 80new + call assert_beeps('normal gk') put =[repeat('x',90)..' {{{1', 'x {{{1'] norm! gk " In a 80 column wide terminal the window will be only 78 char @@ -2719,12 +3068,12 @@ func Test_normal_gk() norm! gk call assert_equal(95, col('.')) call assert_equal(95, virtcol('.')) - bw! - bw! + %bw! " needs 80 column new window new vert 80new + call assert_beeps('normal gj') set number set numberwidth=10 set cpoptions+=n @@ -2743,9 +3092,191 @@ func Test_normal_gk() call assert_equal(1, col('.')) norm! gj call assert_equal(76, col('.')) - bw! - bw! - set cpoptions& number& numberwidth& + " When 'nowrap' is set, gk and gj behave like k and j + set nowrap + normal! gk + call assert_equal([2, 76], [line('.'), col('.')]) + normal! gj + call assert_equal([3, 76], [line('.'), col('.')]) + %bw! + set cpoptions& number& numberwidth& wrap& +endfunc + +" Test for cursor movement with '-' in 'cpoptions' +func Test_normal_cpo_minus() + throw 'Skipped: Nvim does not support cpoptions flag "-"' + new + call setline(1, ['foo', 'bar', 'baz']) + let save_cpo = &cpo + set cpo+=- + call assert_beeps('normal 10j') + call assert_equal(1, line('.')) + normal G + call assert_beeps('normal 10k') + call assert_equal(3, line('.')) + call assert_fails(10, 'E16:') + let &cpo = save_cpo + close! +endfunc + +" Test for displaying dollar when changing text ('$' flag in 'cpoptions') +func Test_normal_cpo_dollar() + throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return '' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + set cpo+=$ + call setline(1, 'one two three') + redraw! + exe "normal c2w\<F2>vim" + call assert_equal('one tw$ three', g:Line) + call assert_equal('vim three', getline(1)) + set cpo-=$ + call test_override('ALL', 0) + delfunc SaveFirstLine + %bw! +endfunc + +" Test for using : to run a multi-line Ex command in operator pending mode +func Test_normal_yank_with_excmd() + new + call setline(1, ['foo', 'bar', 'baz']) + let @a = '' + call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt') + call assert_equal('f', @a) + close! +endfunc + +" Test for supplying a count to a normal-mode command across a cursorhold call +func Test_normal_cursorhold_with_count() + throw 'Skipped: Nvim removed <CursorHold> key' + func s:cHold() + let g:cHold_Called += 1 + endfunc + new + augroup normalcHoldTest + au! + au CursorHold <buffer> call s:cHold() + augroup END + let g:cHold_Called = 0 + call feedkeys("3\<CursorHold>2ix", 'xt') + call assert_equal(1, g:cHold_Called) + call assert_equal(repeat('x', 32), getline(1)) + augroup normalcHoldTest + au! + augroup END + au! normalcHoldTest + close! + delfunc s:cHold +endfunc + +" Test for using a count and a command with CTRL-W +func Test_wincmd_with_count() + call feedkeys("\<C-W>12n", 'xt') + call assert_equal(12, winheight(0)) +endfunc + +" Test for 'b', 'B' 'ge' and 'gE' commands +func Test_horiz_motion() + new + normal! gg + call assert_beeps('normal! b') + call assert_beeps('normal! B') + call assert_beeps('normal! gE') + call assert_beeps('normal! ge') + " <S-Backspace> moves one word left and <C-Backspace> moves one WORD left + call setline(1, 'one ,two ,three') + exe "normal! $\<S-BS>" + call assert_equal(11, col('.')) + exe "normal! $\<C-BS>" + call assert_equal(10, col('.')) + close! +endfunc + +" Test for using a : command in operator pending mode +func Test_normal_colon_op() + new + call setline(1, ['one', 'two']) + call assert_beeps("normal! Gc:d\<CR>") + close! +endfunc + +" Test for 'w' and 'b' commands +func Test_normal_word_move() + new + call setline(1, ['foo bar a', '', 'foo bar b']) + " copy a single character word at the end of a line + normal 1G$yw + call assert_equal('a', @") + " copy a single character word at the end of a file + normal G$yw + call assert_equal('b', @") + " check for a word movement handling an empty line properly + normal 1G$vwy + call assert_equal("a\n\n", @") + + " copy using 'b' command + %d + " non-empty blank line at the start of file + call setline(1, [' ', 'foo bar']) + normal 2Gyb + call assert_equal(" \n", @") + " try to copy backwards from the start of the file + call setline(1, ['one two', 'foo bar']) + call assert_beeps('normal ggyb') + " 'b' command should stop at an empty line + call setline(1, ['one two', '', 'foo bar']) + normal 3Gyb + call assert_equal("\n", @") + normal 3Gy2b + call assert_equal("two\n", @") + " 'b' command should not stop at a non-empty blank line + call setline(1, ['one two', ' ', 'foo bar']) + normal 3Gyb + call assert_equal("two\n ", @") + + close! +endfunc + +" Test for 'scrolloff' with a long line that doesn't fit in the screen +func Test_normal_scroloff() + 10new + 80vnew + call setline(1, repeat('a', 1000)) + set scrolloff=10 + normal gg10gj + call assert_equal(8, winline()) + normal 10gj + call assert_equal(10, winline()) + normal 10gk + call assert_equal(3, winline()) + set scrolloff& + close! +endfunc + +" Test for vertical scrolling with CTRL-F and CTRL-B with a long line +func Test_normal_vert_scroll_longline() + 10new + 80vnew + call setline(1, range(1, 10)) + call append(5, repeat('a', 1000)) + exe "normal gg\<C-F>" + call assert_equal(6, line('.')) + exe "normal \<C-F>\<C-F>" + call assert_equal(11, line('.')) + call assert_equal(1, winline()) + exe "normal \<C-B>" + call assert_equal(10, line('.')) + call assert_equal(3, winline()) + exe "normal \<C-B>\<C-B>" + call assert_equal(5, line('.')) + call assert_equal(5, winline()) + close! endfunc " Some commands like yy, cc, dd, >>, << and !! accept a count after diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index f73cd5f5e6..1f003041e6 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -238,6 +238,12 @@ func Test_set_completion() call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:) + call feedkeys(":setlocal di\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"setlocal dictionary diff diffexpr diffopt digraph directory display', @:) + + call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:) + " Expand boolan options. When doing :set no<Tab> " vim displays the options names without "no" but completion uses "no...". call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') @@ -268,6 +274,7 @@ func Test_set_completion() call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) + set tags& " Expand values for 'filetype' @@ -283,9 +290,10 @@ func Test_set_errors() call assert_fails('set regexpengine=3', 'E474:') call assert_fails('set history=10001', 'E474:') call assert_fails('set numberwidth=21', 'E474:') - call assert_fails('set colorcolumn=-a') - call assert_fails('set colorcolumn=a') - call assert_fails('set colorcolumn=1,') + call assert_fails('set colorcolumn=-a', 'E474:') + call assert_fails('set colorcolumn=a', 'E474:') + call assert_fails('set colorcolumn=1,', 'E474:') + call assert_fails('set colorcolumn=1;', 'E474:') call assert_fails('set cmdheight=-1', 'E487:') call assert_fails('set cmdwinheight=-1', 'E487:') if has('conceal') @@ -336,9 +344,13 @@ func Test_set_errors() call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') call assert_fails('set guicursor=i-ci', 'E545:') call assert_fails('set guicursor=x', 'E545:') + call assert_fails('set guicursor=x:', 'E546:') call assert_fails('set guicursor=r-cr:horx', 'E548:') call assert_fails('set guicursor=r-cr:hor0', 'E549:') endif + if has('mouseshape') + call assert_fails('se mouseshape=i-r:x', 'E547:') + endif call assert_fails('set backupext=~ patchmode=~', 'E589:') call assert_fails('set winminheight=10 winheight=9', 'E591:') call assert_fails('set winminwidth=10 winwidth=9', 'E592:') @@ -454,9 +466,11 @@ func Test_set_one_column() endfunc func Test_set_values() - " The file is only generated when running "make test" in the src directory. + " opt_test.vim is generated from ../optiondefs.h using gen_opt_test.vim if filereadable('opt_test.vim') source opt_test.vim + else + throw 'Skipped: opt_test.vim does not exist' endif endfunc @@ -623,7 +637,7 @@ func Test_copy_winopt() call assert_equal(4,&numberwidth) bw! - set nohidden + set hidden& endfunc func Test_shortmess_F() @@ -729,7 +743,26 @@ func Test_buftype() call setline(1, ['L1']) set buftype=nowrite call assert_fails('write', 'E382:') - close! + + " for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup'] + for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'prompt'] + exe 'set buftype=' .. val + call writefile(['something'], 'XBuftype') + call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val) + endfor + + call delete('XBuftype') + bwipe! +endfunc + +" Test for the 'shell' option +func Test_shell() + throw 'Skipped: Nvim does not have :shell' + CheckUnix + let save_shell = &shell + set shell= + call assert_fails('shell', 'E91:') + let &shell = save_shell endfunc " Test for the 'shellquote' option @@ -745,6 +778,29 @@ func Test_shellquote() call assert_match(': "#echo Hello#"', v) endfunc +" Test for the 'rightleftcmd' option +func Test_rightleftcmd() + CheckFeature rightleft + set rightleft + set rightleftcmd + + let g:l = [] + func AddPos() + call add(g:l, screencol()) + return '' + endfunc + cmap <expr> <F2> AddPos() + + call feedkeys("/\<F2>abc\<Left>\<F2>\<Right>\<Right>\<F2>" .. + \ "\<Left>\<F2>\<Esc>", 'xt') + call assert_equal([&co - 1, &co - 4, &co - 2, &co - 3], g:l) + + cunmap <F2> + unlet g:l + set rightleftcmd& + set rightleft& +endfunc + " Test for setting option values using v:false and v:true func Test_opt_boolean() set number& @@ -759,6 +815,47 @@ func Test_opt_boolean() set number& endfunc +" Test for the 'window' option +func Test_window_opt() + " Needs only one open widow + %bw! + call setline(1, range(1, 8)) + set window=5 + exe "normal \<C-F>" + call assert_equal(4, line('w0')) + exe "normal \<C-F>" + call assert_equal(7, line('w0')) + exe "normal \<C-F>" + call assert_equal(8, line('w0')) + exe "normal \<C-B>" + call assert_equal(5, line('w0')) + exe "normal \<C-B>" + call assert_equal(2, line('w0')) + exe "normal \<C-B>" + call assert_equal(1, line('w0')) + set window=1 + exe "normal gg\<C-F>" + call assert_equal(2, line('w0')) + exe "normal \<C-F>" + call assert_equal(3, line('w0')) + exe "normal \<C-B>" + call assert_equal(2, line('w0')) + exe "normal \<C-B>" + call assert_equal(1, line('w0')) + enew! + set window& +endfunc + +" Test for the 'winminheight' option +func Test_opt_winminheight() + only! + let &winheight = &lines + 4 + call assert_fails('let &winminheight = &lines + 2', 'E36:') + call assert_true(&winminheight <= &lines) + set winminheight& + set winheight& +endfunc + func Test_opt_winminheight_term() " See test/functional/legacy/options_spec.lua CheckRunVimInTerminal @@ -802,6 +899,16 @@ func Test_opt_winminheight_term_tabs() call delete('Xwinminheight') endfunc +" Test for the 'winminwidth' option +func Test_opt_winminwidth() + only! + let &winwidth = &columns + 4 + call assert_fails('let &winminwidth = &columns + 2', 'E36:') + call assert_true(&winminwidth <= &columns) + set winminwidth& + set winwidth& +endfunc + " Test for setting option value containing spaces with isfname+=32 func Test_isfname_with_options() set isfname+=32 diff --git a/src/nvim/testdir/test_perl.vim b/src/nvim/testdir/test_perl.vim index b911a982f9..558d0a5d6b 100644 --- a/src/nvim/testdir/test_perl.vim +++ b/src/nvim/testdir/test_perl.vim @@ -1,8 +1,8 @@ " Tests for Perl interface -if !has('perl') || has('win32') - finish -endif +source check.vim +CheckFeature perl +CheckNotMSWindows " FIXME: RunTest don't see any error when Perl abort... perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" }; diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim index e31680e7b6..c52044d064 100644 --- a/src/nvim/testdir/test_plus_arg_edit.vim +++ b/src/nvim/testdir/test_plus_arg_edit.vim @@ -18,7 +18,7 @@ func Test_edit_bad() e! ++enc=utf8 Xfile call assert_equal('[?][?][???][??]', getline(1)) - e! ++enc=utf8 ++bad=_ Xfile + e! ++encoding=utf8 ++bad=_ Xfile call assert_equal('[_][_][___][__]', getline(1)) e! ++enc=utf8 ++bad=drop Xfile @@ -32,3 +32,16 @@ func Test_edit_bad() bw! call delete('Xfile') endfunc + +" Test for ++bin and ++nobin arguments +func Test_binary_arg() + new + edit ++bin Xfile1 + call assert_equal(1, &binary) + edit ++nobin Xfile2 + call assert_equal(0, &binary) + call assert_fails('edit ++binabc Xfile3', 'E474:') + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 9a31f61582..a5e4be49f4 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -349,19 +349,17 @@ func DummyCompleteOne(findstart, base) endif endfunc -" Test that nothing happens if the 'completefunc' opens -" a new window (no completion, no crash) +" Test that nothing happens if the 'completefunc' tries to open +" a new window (fails to open window, continues) func Test_completefunc_opens_new_window_one() new let winid = win_getid() setlocal completefunc=DummyCompleteOne call setline(1, 'one') /^one - call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E839:') - call assert_notequal(winid, win_getid()) - q! + call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - call assert_equal('', getline(1)) + call assert_equal('oneDEF', getline(1)) q! endfunc @@ -384,11 +382,11 @@ func Test_completefunc_opens_new_window_two() setlocal completefunc=DummyCompleteTwo call setline(1, 'two') /^two - call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E764:') - call assert_notequal(winid, win_getid()) - q! + call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - call assert_equal('two', getline(1)) + " v8.2.1919 hasn't been ported yet + " call assert_equal('twodef', getline(1)) + call assert_equal('twoDEF', getline(1)) q! endfunc @@ -657,8 +655,8 @@ func Test_complete_func_mess() set completefunc=MessComplete new call setline(1, 'Ju') - call feedkeys("A\<c-x>\<c-u>/\<esc>", 'tx') - call assert_equal('Oct/Oct', getline(1)) + call assert_fails('call feedkeys("A\<c-x>\<c-u>/\<esc>", "tx")', 'E565:') + call assert_equal('Jan/', getline(1)) bwipe! set completefunc= endfunc @@ -913,7 +911,7 @@ func Test_popup_complete_backwards_ctrl_p() bwipe! endfunc -fun! Test_complete_o_tab() +func Test_complete_o_tab() CheckFunction test_override let s:o_char_pressed = 0 @@ -922,7 +920,7 @@ fun! Test_complete_o_tab() let s:o_char_pressed = 0 call feedkeys("\<c-x>\<c-n>", 'i') endif - endf + endfunc set completeopt=menu,noselect new @@ -941,7 +939,21 @@ fun! Test_complete_o_tab() bwipe! set completeopt& delfunc s:act_on_text_changed -endf +endfunc + +func Test_menu_only_exists_in_terminal() + if !exists(':tlmenu') || has('gui_running') + return + endif + tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+ + aunmenu * + try + popup Edit + call assert_false(1, 'command should have failed') + catch + call assert_exception('E328:') + endtry +endfunc func Test_popup_complete_info_01() new diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index a46ef8b3fe..97af3699a8 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -114,6 +114,16 @@ func Test_put_p_indent_visual() bwipe! endfunc +" Test for deleting all the contents of a buffer with a put +func Test_put_visual_delete_all_lines() + new + call setline(1, ['one', 'two', 'three']) + let @r = '' + normal! VG"rgp + call assert_equal(1, line('$')) + close! +endfunc + func Test_gp_with_count_leaves_cursor_at_end() new call setline(1, '<---->') diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim index ae8bc57c7f..745b7da086 100644 --- a/src/nvim/testdir/test_python2.vim +++ b/src/nvim/testdir/test_python2.vim @@ -1,9 +1,8 @@ " Test for python 2 commands. " TODO: move tests from test86.in here. -if !has('python') - finish -endif +source check.vim +CheckFeature python func Test_pydo() " Check deleting lines does not trigger ml_get error. diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index f6a1942e24..69f5f6dcc0 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -1,9 +1,8 @@ " Test for python 3 commands. " TODO: move tests from test87.in here. -if !has('python3') - finish -endif +source check.vim +CheckFeature python3 func Test_py3do() " Check deleting lines does not trigger an ml_get error. diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim index 6a8ebf3da0..eee825fa9b 100644 --- a/src/nvim/testdir/test_pyx2.vim +++ b/src/nvim/testdir/test_pyx2.vim @@ -1,8 +1,7 @@ " Test for pyx* commands and functions with Python 2. -if !has('python') - finish -endif +source check.vim +CheckFeature python set pyx=2 let s:py2pattern = '^2\.[0-7]\.\d\+' diff --git a/src/nvim/testdir/test_pyx3.vim b/src/nvim/testdir/test_pyx3.vim index 2044af3abe..db39f5134a 100644 --- a/src/nvim/testdir/test_pyx3.vim +++ b/src/nvim/testdir/test_pyx3.vim @@ -1,9 +1,8 @@ " Test for pyx* commands and functions with Python 3. set pyx=3 -if !has('python3') - finish -endif +source check.vim +CheckFeature python3 let s:py2pattern = '^2\.[0-7]\.\d\+' let s:py3pattern = '^3\.\d\+\.\d\+' diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 5826666cbb..ddd4229f17 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -301,6 +301,23 @@ func XwindowTests(cchar) call assert_equal(12, winwidth(0)) Xclose + " Horizontally or vertically splitting the quickfix window should create a + " normal window/buffer + Xopen + wincmd s + call assert_equal(0, getwininfo(win_getid())[0].quickfix) + call assert_equal(0, getwininfo(win_getid())[0].loclist) + call assert_notequal('quickfix', &buftype) + close + Xopen + wincmd v + call assert_equal(0, getwininfo(win_getid())[0].quickfix) + call assert_equal(0, getwininfo(win_getid())[0].loclist) + call assert_notequal('quickfix', &buftype) + close + Xopen + Xclose + if a:cchar == 'c' " Opening the quickfix window in multiple tab pages should reuse the " quickfix buffer @@ -499,13 +516,14 @@ func Xtest_browse(cchar) \ 'RegularLine2'] Xfirst + call assert_fails('-5Xcc', 'E16:') call assert_fails('Xprev', 'E553') call assert_fails('Xpfile', 'E553') Xnfile - call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal('Xqftestfile2', @%) call assert_equal(10, line('.')) Xpfile - call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal('Xqftestfile1', @%) call assert_equal(6, line('.')) 5Xcc call assert_equal(5, g:Xgetlist({'idx':0}).idx) @@ -521,7 +539,7 @@ func Xtest_browse(cchar) call assert_equal(6, g:Xgetlist({'idx':0}).idx) Xlast Xprev - call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal('Xqftestfile2', @%) call assert_equal(11, line('.')) call assert_fails('Xnext', 'E553') call assert_fails('Xnfile', 'E553') @@ -534,14 +552,14 @@ func Xtest_browse(cchar) endif call assert_equal(6, g:Xgetlist({'idx':0}).idx) Xrewind - call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal('Xqftestfile1', @%) call assert_equal(5, line('.')) 10Xnext - call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal('Xqftestfile2', @%) call assert_equal(11, line('.')) 10Xprev - call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal('Xqftestfile1', @%) call assert_equal(5, line('.')) " Jumping to an error from the error window using cc command @@ -552,14 +570,23 @@ func Xtest_browse(cchar) Xopen 10Xcc call assert_equal(11, line('.')) - call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal('Xqftestfile2', @%) + Xopen + call cursor(2, 1) + if a:cchar == 'c' + .cc + else + .ll + endif + call assert_equal(6, line('.')) + call assert_equal('Xqftestfile1', @%) " Jumping to an error from the error window (when only the error window is " present) Xopen | only Xlast 1 call assert_equal(5, line('.')) - call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal('Xqftestfile1', @%) Xexpr "" call assert_fails('Xnext', 'E42:') @@ -1934,7 +1961,7 @@ func Test_switchbuf() copen | only cfirst call assert_equal(1, tabpagenr()) - call assert_equal('Xqftestfile1', bufname('')) + call assert_equal('Xqftestfile1', @%) " If opening a file changes 'switchbuf', then the new value should be " retained. @@ -2750,7 +2777,7 @@ func Test_cwindow_jump() wincmd b cfirst call assert_equal(2, winnr()) - call assert_equal('F1', bufname('')) + call assert_equal('F1', @%) enew | only exe 'sb' bnum exe 'botright sb' bnum @@ -2840,7 +2867,7 @@ func XvimgrepTests(cchar) edit +3 Xtestfile2 Xvimgrep +\cemacs+j Xtestfile1 let l = g:Xgetlist() - call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Xtestfile2', @%) call assert_equal('Editor:Emacs EmAcS', l[0].text) " Test for unloading a buffer after vimgrep searched the buffer @@ -2882,6 +2909,21 @@ func Test_vimgrep_incsearch() set noincsearch endfunc +" Test vimgrep with the last search pattern not set +func Test_vimgrep_with_no_last_search_pat() + let lines =<< trim [SCRIPT] + call assert_fails('vimgrep // *', 'E35:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + " Test vimgrep without swap file func Test_vimgrep_without_swap_file() let lines =<< trim [SCRIPT] @@ -3082,20 +3124,80 @@ func Test_file_from_copen() endfunc func Test_resize_from_copen() + augroup QF_Test + au! + au FileType qf resize 5 + augroup END + try + " This should succeed without any exception. No other buffers are + " involved in the autocmd. + copen + finally augroup QF_Test - au! - au FileType qf resize 5 + au! augroup END - try - " This should succeed without any exception. No other buffers are - " involved in the autocmd. - copen - finally - augroup QF_Test - au! - augroup END - augroup! QF_Test - endtry + augroup! QF_Test + endtry +endfunc + +func Test_vimgrep_with_textlock() + new + + " Simple way to execute something with "textlock" set. + " Check that vimgrep without jumping can be executed. + au InsertCharPre * vimgrep /RunTheTest/j runtest.vim + normal ax + let qflist = getqflist() + call assert_true(len(qflist) > 0) + call assert_match('RunTheTest', qflist[0].text) + call setqflist([], 'r') + au! InsertCharPre + + " Check that vimgrepadd without jumping can be executed. + au InsertCharPre * vimgrepadd /RunTheTest/j runtest.vim + normal ax + let qflist = getqflist() + call assert_true(len(qflist) > 0) + call assert_match('RunTheTest', qflist[0].text) + call setqflist([], 'r') + au! InsertCharPre + + " Check that lvimgrep without jumping can be executed. + au InsertCharPre * lvimgrep /RunTheTest/j runtest.vim + normal ax + let qflist = getloclist(0) + call assert_true(len(qflist) > 0) + call assert_match('RunTheTest', qflist[0].text) + call setloclist(0, [], 'r') + au! InsertCharPre + + " Check that lvimgrepadd without jumping can be executed. + au InsertCharPre * lvimgrepadd /RunTheTest/j runtest.vim + normal ax + let qflist = getloclist(0) + call assert_true(len(qflist) > 0) + call assert_match('RunTheTest', qflist[0].text) + call setloclist(0, [], 'r') + au! InsertCharPre + + " trying to jump will give an error + au InsertCharPre * vimgrep /RunTheTest/ runtest.vim + call assert_fails('normal ax', 'E565:') + au! InsertCharPre + + au InsertCharPre * vimgrepadd /RunTheTest/ runtest.vim + call assert_fails('normal ax', 'E565:') + au! InsertCharPre + + au InsertCharPre * lvimgrep /RunTheTest/ runtest.vim + call assert_fails('normal ax', 'E565:') + au! InsertCharPre + + au InsertCharPre * lvimgrepadd /RunTheTest/ runtest.vim + call assert_fails('normal ax', 'E565:') + au! InsertCharPre + + bwipe! endfunc " Tests for the quickfix buffer b:changedtick variable @@ -3494,7 +3596,7 @@ func Xqfjump_tests(cchar) Xopen | only 2Xnext call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) - call assert_equal('F3', bufname('%')) + call assert_equal('F3', @%) Xnext call assert_equal(7, col('.')) Xnext @@ -4188,20 +4290,20 @@ func Xjumpto_first_error_test(cchar) " Test for cexpr/lexpr enew Xexpr l - call assert_equal('Xtestfile1', bufname('')) + call assert_equal('Xtestfile1', @%) call assert_equal(2, line('.')) " Test for cfile/lfile enew call writefile(l, 'Xerr') Xfile Xerr - call assert_equal('Xtestfile1', bufname('')) + call assert_equal('Xtestfile1', @%) call assert_equal(2, line('.')) " Test for cbuffer/lbuffer edit Xerr Xbuffer - call assert_equal('Xtestfile1', bufname('')) + call assert_equal('Xtestfile1', @%) call assert_equal(2, line('.')) call delete('Xerr') @@ -4226,7 +4328,7 @@ func Xautocmd_changelist(cchar) autocmd QuickFixCmdPost * Xolder call writefile(['Xtestfile2:4:Line4'], 'Xerr') Xfile Xerr - call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Xtestfile2', @%) call assert_equal(4, line('.')) autocmd! QuickFixCmdPost @@ -4237,7 +4339,7 @@ func Xautocmd_changelist(cchar) call writefile(['Xtestfile2:4:Line4'], 'Xerr') edit Xerr Xbuffer - call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Xtestfile2', @%) call assert_equal(4, line('.')) autocmd! QuickFixCmdPost @@ -4246,7 +4348,7 @@ func Xautocmd_changelist(cchar) Xexpr 'Xtestfile1:2:Line2' autocmd QuickFixCmdPost * Xolder Xexpr 'Xtestfile2:4:Line4' - call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Xtestfile2', @%) call assert_equal(4, line('.')) autocmd! QuickFixCmdPost @@ -4257,7 +4359,7 @@ func Xautocmd_changelist(cchar) Xexpr 'Xtestfile1:2:Line2' autocmd QuickFixCmdPost * Xolder silent Xgrep Line5 Xtestfile2 - call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Xtestfile2', @%) call assert_equal(5, line('.')) autocmd! QuickFixCmdPost endif @@ -4267,7 +4369,7 @@ func Xautocmd_changelist(cchar) Xexpr 'Xtestfile1:2:Line2' autocmd QuickFixCmdPost * Xolder silent Xvimgrep Line5 Xtestfile2 - call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Xtestfile2', @%) call assert_equal(5, line('.')) autocmd! QuickFixCmdPost @@ -4391,6 +4493,20 @@ func Test_splitview() call assert_equal(0, getloclist(0, {'winid' : 0}).winid) new | only + " Using :split or :vsplit from a quickfix window should behave like a :new + " or a :vnew command + copen + split + call assert_equal(3, winnr('$')) + let l = getwininfo() + call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix]) + close + copen + vsplit + let l = getwininfo() + call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix]) + new | only + call delete('Xtestfile1') call delete('Xtestfile2') endfunc @@ -4568,7 +4684,7 @@ func Test_winonly_autocmd() " positioned correctly. ll 3 call assert_equal(loclistid, getloclist(0, {'id' : 0}).id) - call assert_equal('Xtest1', bufname('')) + call assert_equal('Xtest1', @%) call assert_equal(15, line('.')) " Cleanup autocmd! WinEnter @@ -4629,51 +4745,51 @@ func Xtest_below(cchar) Xexpr ["X1:5:3:L5", "X2:5:2:L5", "X2:10:3:L10", "X2:15:4:L15", "X3:3:5:L3"] edit +7 X2 Xabove - call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_equal(['X2', 5], [@%, line('.')]) call assert_fails('Xabove', 'E553:') normal 7G Xbefore - call assert_equal(['X2', 5, 2], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')]) call assert_fails('Xbefore', 'E553:') normal 2j Xbelow - call assert_equal(['X2', 10], [bufname(''), line('.')]) + call assert_equal(['X2', 10], [@%, line('.')]) normal 7G Xafter - call assert_equal(['X2', 10, 3], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')]) " Last error in this file Xbelow 99 - call assert_equal(['X2', 15], [bufname(''), line('.')]) + call assert_equal(['X2', 15], [@%, line('.')]) call assert_fails('Xbelow', 'E553:') normal gg Xafter 99 - call assert_equal(['X2', 15, 4], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 15, 4], [@%, line('.'), col('.')]) call assert_fails('Xafter', 'E553:') " First error in this file Xabove 99 - call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_equal(['X2', 5], [@%, line('.')]) call assert_fails('Xabove', 'E553:') normal G Xbefore 99 - call assert_equal(['X2', 5, 2], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')]) call assert_fails('Xbefore', 'E553:') normal gg Xbelow 2 - call assert_equal(['X2', 10], [bufname(''), line('.')]) + call assert_equal(['X2', 10], [@%, line('.')]) normal gg Xafter 2 - call assert_equal(['X2', 10, 3], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')]) normal G Xabove 2 - call assert_equal(['X2', 10], [bufname(''), line('.')]) + call assert_equal(['X2', 10], [@%, line('.')]) normal G Xbefore 2 - call assert_equal(['X2', 10, 3], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 10, 3], [@%, line('.'), col('.')]) edit X4 call assert_fails('Xabove', 'E42:') @@ -4697,45 +4813,45 @@ func Xtest_below(cchar) \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"] edit +1 X2 Xbelow 2 - call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')]) normal 1G Xafter 2 - call assert_equal(['X2', 5, 2], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 5, 2], [@%, line('.'), col('.')]) normal gg Xbelow 99 - call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 15, 1], [@%, line('.'), col('.')]) normal gg Xafter 99 - call assert_equal(['X2', 15, 3], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 15, 3], [@%, line('.'), col('.')]) normal G Xabove 2 - call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')]) normal G Xbefore 2 - call assert_equal(['X2', 15, 2], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 15, 2], [@%, line('.'), col('.')]) normal G Xabove 99 - call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')]) normal G Xbefore 99 - call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')]) normal 10G Xabove - call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 5, 1], [@%, line('.'), col('.')]) normal 10G$ 2Xbefore - call assert_equal(['X2', 10, 2], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 10, 2], [@%, line('.'), col('.')]) normal 10G Xbelow - call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 15, 1], [@%, line('.'), col('.')]) normal 9G 5Xafter - call assert_equal(['X2', 15, 2], [bufname(''), line('.'), col('.')]) + call assert_equal(['X2', 15, 2], [@%, line('.'), col('.')]) " Invalid range if a:cchar == 'c' diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index 6e6f91362b..93865869fa 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -1,10 +1,9 @@ " *-register (quotestar) tests -if !has('clipboard') - finish -endif - source shared.vim +source check.vim + +CheckFeature clipboard_working func Do_test_quotestar_for_macunix() if empty(exepath('pbcopy')) || empty(exepath('pbpaste')) diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 45e60a6d44..82d250e8b3 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -122,7 +122,10 @@ endfunc " Tests for regexp patterns without multi-byte support. func Test_regexp_single_line_pat() " tl is a List of Lists with: - " regexp engine + " regexp engines to test + " 0 - test with 'regexpengine' values 0 and 1 + " 1 - test with 'regexpengine' values 0 and 2 + " 2 - test with 'regexpengine' values 0, 1 and 2 " regexp pattern " text to test the pattern on " expected match (optional) @@ -143,6 +146,8 @@ func Test_regexp_single_line_pat() call add(tl, [2, 'c*', 'abdef', '']) call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) call add(tl, [2, 'bc\+', 'abdef']) " no match + " match newline character in a string + call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"]) " operator \| call add(tl, [2, 'a\|ab', 'cabd', 'a']) " alternation is ordered @@ -566,6 +571,9 @@ func Test_regexp_single_line_pat() " Test \%V atom call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt']) + " Test for ignoring case and matching repeated characters + call add(tl, [2, '\cb\+', 'aAbBbBcC', 'bBbB']) + " Run the tests for t in tl let re = t[0] @@ -625,6 +633,14 @@ endfunc " Tests for multi-line regexp patterns without multi-byte support. func Test_regexp_multiline_pat() + " tl is a List of Lists with: + " regexp engines to test + " 0 - test with 'regexpengine' values 0 and 1 + " 1 - test with 'regexpengine' values 0 and 2 + " 2 - test with 'regexpengine' values 0, 1 and 2 + " regexp pattern + " List with text to test the pattern on + " List with the expected match let tl = [] " back references @@ -634,6 +650,70 @@ func Test_regexp_multiline_pat() " line breaks call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']]) + " Any single character or end-of-line + call add(tl, [2, '\_.\+', ['a', 'b', 'c'], ['XX']]) + " Any identifier or end-of-line + call add(tl, [2, '\_i\+', ['a', 'b', ';', '2'], ['XX;XX']]) + " Any identifier but excluding digits or end-of-line + call add(tl, [2, '\_I\+', ['a', 'b', ';', '2'], ['XX;XX2XX']]) + " Any keyword or end-of-line + call add(tl, [2, '\_k\+', ['a', 'b', '=', '2'], ['XX=XX']]) + " Any keyword but excluding digits or end-of-line + call add(tl, [2, '\_K\+', ['a', 'b', '=', '2'], ['XX=XX2XX']]) + " Any filename character or end-of-line + call add(tl, [2, '\_f\+', ['a', 'b', '.', '5'], ['XX']]) + " Any filename character but excluding digits or end-of-line + call add(tl, [2, '\_F\+', ['a', 'b', '.', '5'], ['XX5XX']]) + " Any printable character or end-of-line + call add(tl, [2, '\_p\+', ['a', 'b', '=', '4'], ['XX']]) + " Any printable character excluding digits or end-of-line + call add(tl, [2, '\_P\+', ['a', 'b', '=', '4'], ['XX4XX']]) + " Any whitespace character or end-of-line + call add(tl, [2, '\_s\+', [' ', ' ', 'a', 'b'], ['XXaXXbXX']]) + " Any non-whitespace character or end-of-line + call add(tl, [2, '\_S\+', [' ', ' ', 'a', 'b'], [' XX XX']]) + " Any decimal digit or end-of-line + call add(tl, [2, '\_d\+', ['1', 'a', '2', 'b', '3'], ['XXaXXbXX']]) + " Any non-decimal digit or end-of-line + call add(tl, [2, '\_D\+', ['1', 'a', '2', 'b', '3'], ['1XX2XX3XX']]) + " Any hexadecimal digit or end-of-line + call add(tl, [2, '\_x\+', ['1', 'a', 'g', '9', '8'], ['XXgXX']]) + " Any non-hexadecimal digit or end-of-line + call add(tl, [2, '\_X\+', ['1', 'a', 'g', '9', '8'], ['1XXaXX9XX8XX']]) + " Any octal digit or end-of-line + call add(tl, [2, '\_o\+', ['0', '7', '8', '9', '0'], ['XX8XX9XX']]) + " Any non-octal digit or end-of-line + call add(tl, [2, '\_O\+', ['0', '7', '8', '9', '0'], ['0XX7XX0XX']]) + " Any word character or end-of-line + call add(tl, [2, '\_w\+', ['A', 'B', '=', 'C', 'D'], ['XX=XX']]) + " Any non-word character or end-of-line + call add(tl, [2, '\_W\+', ['A', 'B', '=', 'C', 'D'], ['AXXBXXCXXDXX']]) + " Any head-of-word character or end-of-line + call add(tl, [2, '\_h\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']]) + " Any non-head-of-word character or end-of-line + call add(tl, [2, '\_H\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']]) + " Any alphabetic character or end-of-line + call add(tl, [2, '\_a\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']]) + " Any non-alphabetic character or end-of-line + call add(tl, [2, '\_A\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']]) + " Any lowercase character or end-of-line + call add(tl, [2, '\_l\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']]) + " Any non-lowercase character or end-of-line + call add(tl, [2, '\_L\+', ['a', 'A', 'b', 'B'], ['aXXbXX']]) + " Any uppercase character or end-of-line + call add(tl, [2, '\_u\+', ['a', 'A', 'b', 'B'], ['aXXbXX']]) + " Any non-uppercase character or end-of-line + call add(tl, [2, '\_U\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']]) + " Collection or end-of-line + call add(tl, [2, '\_[a-z]\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']]) + " start of line anywhere in the text + call add(tl, [2, 'one\zs\_s*\_^\zetwo', + \ ['', 'one', ' two', 'one', '', 'two'], + \ ['', 'one', ' two', 'oneXXtwo']]) + " end of line anywhere in the text + call add(tl, [2, 'one\zs\_$\_s*two', + \ ['', 'one', ' two', 'one', '', 'two'], ['', 'oneXX', 'oneXX']]) + " Check that \_[0-9] matching EOL does not break a following \> call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']]) @@ -649,7 +729,7 @@ func Test_regexp_multiline_pat() let before = t[2] let after = t[3] for engine in [0, 1, 2] - if engine == 2 && re == 0 || engine == 1 && re ==1 + if engine == 2 && re == 0 || engine == 1 && re == 1 continue endif let ®expengine = engine @@ -697,9 +777,8 @@ func Test_lookbehind_across_line() bwipe! endfunc -" Check matching Visual area -func Test_matching_visual_area() - new +" Test for the \%V atom (match inside the visual area) +func Regex_Match_Visual_Area() call append(0, ['Visual:', 'thexe the thexethe', 'andaxand andaxand', \ 'oooxofor foroxooo', 'oooxofor foroxooo']) call cursor(1, 1) @@ -708,12 +787,22 @@ func Test_matching_visual_area() exe "normal jfx\<C-V>fxj:s/\\%Vo/O/g\<CR>" call assert_equal(['Visual:', 'thexE thE thExethe', 'AndAxAnd AndAxAnd', \ 'oooxOfOr fOrOxooo', 'oooxOfOr fOrOxooo', ''], getline(1, '$')) + %d +endfunc + +" Check matching Visual area +func Test_matching_visual_area() + new + set regexpengine=1 + call Regex_Match_Visual_Area() + set regexpengine=2 + call Regex_Match_Visual_Area() + set regexpengine& bwipe! endfunc " Check matching marks -func Test_matching_marks() - new +func Regex_Mark() call append(0, ['', '', '', 'Marks:', 'asdfSasdfsadfEasdf', 'asdfSas', \ 'dfsadfEasdf', '', '', '', '', '']) call cursor(4, 1) @@ -721,6 +810,15 @@ func Test_matching_marks() exe "normal jfSmsj0fEme:.-4,.+6s/.\\%>'s\\_.*\\%<'e../again/\<CR>" call assert_equal(['', '', '', 'Marks:', 'asdfhereasdf', 'asdfagainasdf', \ '', '', '', '', '', ''], getline(1, '$')) + %d +endfunc + +func Test_matching_marks() + new + set regexpengine=1 + call Regex_Mark() + set regexpengine=2 + call Regex_Mark() bwipe! endfunc @@ -761,8 +859,7 @@ func Test_matching_curpos() endfunc " Test for matching the start and end of a buffer -func Test_start_end_of_buffer_match() - new +func Regex_start_end_buffer() call setline(1, repeat(['vim edit'], 20)) /\%^ call assert_equal([0, 1, 1, 0], getpos('.')) @@ -772,6 +869,15 @@ func Test_start_end_of_buffer_match() call assert_equal([0, 20, 8, 0], getpos('.')) exe "normal 6gg/..\\%$\<CR>" call assert_equal([0, 20, 7, 0], getpos('.')) + %d +endfunc + +func Test_start_end_of_buffer_match() + new + set regexpengine=1 + call Regex_start_end_buffer() + set regexpengine=2 + call Regex_start_end_buffer() bwipe! endfunc @@ -784,10 +890,20 @@ endfunc " Check for detecting error func Test_regexp_error() - set regexpengine=2 - call assert_fails("call matchlist('x x', ' \\ze*')", 'E888:') - call assert_fails("call matchlist('x x', ' \\zs*')", 'E888:') - set re& + call assert_fails("call matchlist('x x', '\\%#=1 \\zs*')", 'E888:') + call assert_fails("call matchlist('x x', '\\%#=1 \\ze*')", 'E888:') + call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:') + call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:') + call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') +endfunc + +" Test for using the last substitute string pattern (~) +func Test_regexp_last_subst_string() + new + s/bar/baz/e + call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=1\~"), "baz") + call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=2\~"), "baz") + close! endfunc " Check patterns matching cursor position. diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index b852cfd22f..52e745438d 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -416,6 +416,20 @@ func Test_put_reg_restart_mode() bwipe! endfunc +" Test for executing a register using :@ command +func Test_execute_register() + call setreg('r', []) + call assert_beeps('@r') + let i = 1 + let @q = 'let i+= 1' + @q + @ + call assert_equal(3, i) + + " cannot execute a register in operator pending mode + call assert_beeps('normal! c@r') +endfunc + " Test for getting register info func Test_get_reginfo() enew @@ -670,6 +684,16 @@ func Test_insert_small_delete() bwipe! endfunc +" Record in insert mode using CTRL-O +func Test_record_in_insert_mode() + new + let @r = '' + call setline(1, ['foo']) + call feedkeys("i\<C-O>qrbaz\<C-O>q", 'xt') + call assert_equal('baz', @r) + bwipe! +endfunc + func Test_record_in_select_mode() new call setline(1, 'text') diff --git a/src/nvim/testdir/test_reltime.vim b/src/nvim/testdir/test_reltime.vim index 37b9e783c6..b381f1ddbb 100644 --- a/src/nvim/testdir/test_reltime.vim +++ b/src/nvim/testdir/test_reltime.vim @@ -1,8 +1,8 @@ " Tests for reltime() -if !has('reltime') || !has('float') - finish -endif +source check.vim +CheckFeature reltime +CheckFeature float func Test_reltime() let now = reltime() diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 454c956996..3d1bbfb726 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -19,9 +19,9 @@ func Test_search_cmdline() set noincsearch :1 call feedkeys("/foobar\<cr>", 'tx') - call feedkeys("/the\<cr>",'tx') + call feedkeys("/the\<cr>", 'tx') call assert_equal('the', @/) - call feedkeys("/thes\<C-P>\<C-P>\<cr>",'tx') + call feedkeys("/thes\<C-P>\<C-P>\<cr>", 'tx') call assert_equal('foobar', @/) " Test 2 @@ -655,10 +655,49 @@ func Test_search_cmdline7() bw! endfunc -" Tests for regexp with various magic settings -func Test_search_regexp() - enew! +func Test_search_cmdline8() + " Highlighting is cleared in all windows + " since hls applies to all windows + CheckOption incsearch + CheckFeature terminal + CheckNotGui + if has("win32") + throw "Skipped: Bug with sending <ESC> to terminal window not fixed yet" + endif + let h = winheight(0) + if h < 3 + return + endif + " Prepare buffer text + let lines = ['abb vim vim vi', 'vimvivim'] + call writefile(lines, 'Xsearch.txt') + let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) + + call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) + + call term_sendkeys(buf, ":set incsearch hlsearch\<cr>") + call term_sendkeys(buf, ":14vsp\<cr>") + call term_sendkeys(buf, "/vim\<cr>") + call term_sendkeys(buf, "/b\<esc>") + call term_sendkeys(buf, "gg0") + call TermWait(buf, 250) + let screen_line = term_scrape(buf, 1) + let [a0,a1,a2,a3] = [screen_line[3].attr, screen_line[4].attr, + \ screen_line[18].attr, screen_line[19].attr] + call assert_notequal(a0, a1) + call assert_notequal(a0, a3) + call assert_notequal(a1, a2) + call assert_equal(a0, a2) + call assert_equal(a1, a3) + " clean up + call delete('Xsearch.txt') + + bwipe! +endfunc + +" Tests for regexp with various magic settings +func Run_search_regexp_magic_opt() put ='1 a aa abb abbccc' exe 'normal! /a*b\{2}c\+/e' . "\<CR>" call assert_equal([0, 2, 17, 0], getpos('.')) @@ -693,6 +732,18 @@ func Test_search_regexp() exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>" call assert_equal([0, 9, 7, 0], getpos('.')) + %d +endfunc + +func Test_search_regexp() + enew! + + set regexpengine=1 + call Run_search_regexp_magic_opt() + set regexpengine=2 + call Run_search_regexp_magic_opt() + set regexpengine& + set undolevels=100 put ='9 foobar' put ='' @@ -700,12 +751,12 @@ func Test_search_regexp() normal G exe 'normal! dv?bar?' . "\<CR>" call assert_equal('9 foo', getline('.')) - call assert_equal([0, 10, 5, 0], getpos('.')) - call assert_equal(10, line('$')) + call assert_equal([0, 2, 5, 0], getpos('.')) + call assert_equal(2, line('$')) normal u call assert_equal('9 foobar', getline('.')) - call assert_equal([0, 10, 6, 0], getpos('.')) - call assert_equal(11, line('$')) + call assert_equal([0, 2, 6, 0], getpos('.')) + call assert_equal(3, line('$')) set undolevels& enew! @@ -1433,7 +1484,7 @@ func Test_large_hex_chars2() endfunc func Test_one_error_msg() - " This was also giving an internal error + " This was also giving an internal error call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:') endfunc @@ -1478,6 +1529,20 @@ func Test_search_match_at_curpos() close! endfunc +" Test for error cases with the search() function +func Test_search_errors() + call assert_fails("call search('pat', [])", 'E730:') + call assert_fails("call search('pat', 'b', {})", 'E728:') + call assert_fails("call search('pat', 'b', 1, [])", 'E745:') + call assert_fails("call search('pat', 'ns')", 'E475:') + call assert_fails("call search('pat', 'mr')", 'E475:') + + new + call setline(1, ['foo', 'bar']) + call assert_fails('call feedkeys("/foo/;/bar/;\<CR>", "tx")', 'E386:') + bwipe! +endfunc + func Test_search_display_pattern() new call setline(1, ['foo', 'bar', 'foobar']) @@ -1541,6 +1606,160 @@ func Test_search_special() exe "norm /\x80PS" endfunc +" Test for command failures when the last search pattern is not set. +" Need to run this in a new vim instance where last search pattern is not set. +func Test_search_with_no_last_pat() + let lines =<< trim [SCRIPT] + call assert_fails("normal i\<C-R>/\e", 'E35:') + call assert_fails("exe '/'", 'E35:') + call assert_fails("exe '?'", 'E35:') + call assert_fails("/", 'E35:') + call assert_fails("?", 'E35:') + call assert_fails("normal n", 'E35:') + call assert_fails("normal N", 'E35:') + call assert_fails("normal gn", 'E35:') + call assert_fails("normal gN", 'E35:') + call assert_fails("normal cgn", 'E35:') + call assert_fails("normal cgN", 'E35:') + let p = [] + let p = @/ + call assert_equal('', p) + call assert_fails("normal :\<C-R>/", 'E35:') + call assert_fails("//p", 'E35:') + call assert_fails(";//p", 'E35:') + call assert_fails("??p", 'E35:') + call assert_fails(";??p", 'E35:') + call assert_fails('g//p', 'E476:') + call assert_fails('v//p', 'E476:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test for using tilde (~) atom in search. This should use the last used +" substitute pattern +func Test_search_tilde_pat() + let lines =<< trim [SCRIPT] + set regexpengine=1 + call assert_fails('exe "normal /~\<CR>"', 'E33:') + call assert_fails('exe "normal ?~\<CR>"', 'E33:') + set regexpengine=2 + call assert_fails('exe "normal /~\<CR>"', 'E383:') + call assert_fails('exe "normal ?~\<CR>"', 'E383:') + set regexpengine& + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test for searching a pattern that is not present with 'nowrapscan' +func Test_search_pat_not_found() + new + set nowrapscan + let @/ = '1abcxyz2' + call assert_fails('normal n', 'E385:') + call assert_fails('normal N', 'E384:') + set wrapscan& + close +endfunc + +" Test for v:searchforward variable +func Test_searchforward_var() + new + call setline(1, ['foo', '', 'foo']) + call cursor(2, 1) + let @/ = 'foo' + let v:searchforward = 0 + normal N + call assert_equal(3, line('.')) + call cursor(2, 1) + let v:searchforward = 1 + normal N + call assert_equal(1, line('.')) + close! +endfunc + +" Test for invalid regular expressions +func Test_invalid_regexp() + set regexpengine=1 + call assert_fails("call search(repeat('\\(.\\)', 10))", 'E51:') + call assert_fails("call search('\\%(')", 'E53:') + call assert_fails("call search('\\(')", 'E54:') + call assert_fails("call search('\\)')", 'E55:') + call assert_fails("call search('x\\@#')", 'E59:') + call assert_fails('call search(''\v%(%(%(%(%(%(%(%(%(%(%(a){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}'')', 'E60:') + call assert_fails("call search('a\\+*')", 'E61:') + call assert_fails("call search('\\_m')", 'E63:') + call assert_fails("call search('\\+')", 'E64:') + call assert_fails("call search('\\1')", 'E65:') + call assert_fails("call search('\\z\\(\\)')", 'E66:') + call assert_fails("call search('\\z2')", 'E67:') + call assert_fails("call search('\\zx')", 'E68:') + call assert_fails("call search('\\%[ab')", 'E69:') + call assert_fails("call search('\\%[]')", 'E70:') + call assert_fails("call search('\\%a')", 'E71:') + call assert_fails("call search('ab\\%[\\(cd\\)]')", 'E369:') + call assert_fails("call search('ab\\%[\\%(cd\\)]')", 'E369:') + set regexpengine=2 + call assert_fails("call search('\\_')", 'E865:') + call assert_fails("call search('\\+')", 'E866:') + call assert_fails("call search('\\zx')", 'E867:') + call assert_fails("call search('\\%a')", 'E867:') + call assert_fails("call search('x\\@#')", 'E869:') + call assert_fails("call search(repeat('\\(.\\)', 10))", 'E872:') + call assert_fails("call search('\\_m')", 'E877:') + call assert_fails("call search('\\%(')", 'E53:') + call assert_fails("call search('\\(')", 'E54:') + call assert_fails("call search('\\)')", 'E55:') + call assert_fails("call search('\\z\\(\\)')", 'E66:') + call assert_fails("call search('\\%[ab')", 'E69:') + call assert_fails("call search('\\%[]')", 'E70:') + call assert_fails("call search('\\%9999999999999999999999999999v')", 'E951:') + set regexpengine& + call assert_fails("call search('\\%#=3ab')", 'E864:') +endfunc + +" Test for searching with 'smartcase' and 'ignorecase' +func Test_search_smartcase() + new + call setline(1, ['', 'Hello']) + set noignorecase nosmartcase + call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:') + + set ignorecase nosmartcase + exe "normal /\\a\\_.\\(.*\\)O\<CR>" + call assert_equal([2, 1], [line('.'), col('.')]) + + call cursor(1, 1) + set ignorecase smartcase + call assert_fails('exe "normal /\\a\\_.\\(.*\\)O\<CR>"', 'E486:') + + exe "normal /\\a\\_.\\(.*\\)o\<CR>" + call assert_equal([2, 1], [line('.'), col('.')]) + + " Test for using special atoms with 'smartcase' + call setline(1, ['', ' Hello\ ']) + call cursor(1, 1) + call feedkeys('/\_.\%(\uello\)\' .. "\<CR>", 'xt') + call assert_equal([2, 4], [line('.'), col('.')]) + + set ignorecase& smartcase& + close! +endfun + " Test 'smartcase' with utf-8. func Test_search_smartcase_utf8() new @@ -1560,6 +1779,102 @@ func Test_search_smartcase_utf8() close! endfunc +" Test searching past the end of a file +func Test_search_past_eof() + new + call setline(1, ['Line']) + exe "normal /\\n\\zs\<CR>" + call assert_equal([1, 4], [line('.'), col('.')]) + close! +endfunc + +" Test for various search offsets +func Test_search_offset() + " With /e, for a match in the first column of a line, the cursor should be + " placed at the end of the previous line. + new + call setline(1, ['one two', 'three four']) + call search('two\_.', 'e') + call assert_equal([1, 7], [line('.'), col('.')]) + + " with cursor at the beginning of the file, use /s+1 + call cursor(1, 1) + exe "normal /two/s+1\<CR>" + call assert_equal([1, 6], [line('.'), col('.')]) + + " with cursor at the end of the file, use /e-1 + call cursor(2, 10) + exe "normal ?three?e-1\<CR>" + call assert_equal([2, 4], [line('.'), col('.')]) + + " line offset - after the last line + call cursor(1, 1) + exe "normal /three/+1\<CR>" + call assert_equal([2, 1], [line('.'), col('.')]) + + " line offset - before the first line + call cursor(2, 1) + exe "normal ?one?-1\<CR>" + call assert_equal([1, 1], [line('.'), col('.')]) + + " character offset - before the first character in the file + call cursor(2, 1) + exe "normal ?one?s-1\<CR>" + call assert_equal([1, 1], [line('.'), col('.')]) + call cursor(2, 1) + exe "normal ?one?e-3\<CR>" + call assert_equal([1, 1], [line('.'), col('.')]) + + " character offset - after the last character in the file + call cursor(1, 1) + exe "normal /four/s+4\<CR>" + call assert_equal([2, 10], [line('.'), col('.')]) + call cursor(1, 1) + exe "normal /four/e+1\<CR>" + call assert_equal([2, 10], [line('.'), col('.')]) + + close! +endfunc + +" Test for searching for matching parenthesis using % +func Test_search_match_paren() + new + call setline(1, "abc(def')'ghi'('jk'\\t'lm)no") + " searching for a matching parenthesis should skip single quoted characters + call cursor(1, 4) + normal % + call assert_equal([1, 25], [line('.'), col('.')]) + normal % + call assert_equal([1, 4], [line('.'), col('.')]) + call cursor(1, 5) + normal ]) + call assert_equal([1, 25], [line('.'), col('.')]) + call cursor(1, 24) + normal [( + call assert_equal([1, 4], [line('.'), col('.')]) + + " matching parenthesis in 'virtualedit' mode with cursor after the eol + call setline(1, 'abc(defgh)') + set virtualedit=all + normal 20|% + call assert_equal(4, col('.')) + set virtualedit& + close! +endfunc + +" Test for searching a pattern and stopping before a specified line +func Test_search_stopline() + new + call setline(1, ['', '', '', 'vim']) + call assert_equal(0, search('vim', 'n', 3)) + call assert_equal(4, search('vim', 'n', 4)) + call setline(1, ['vim', '', '', '']) + call cursor(4, 1) + call assert_equal(0, search('vim', 'bn', 2)) + call assert_equal(1, search('vim', 'bn', 1)) + close! +endfunc + func Test_incsearch_highlighting_newline() CheckRunVimInTerminal CheckOption incsearch diff --git a/src/nvim/testdir/test_sha256.vim b/src/nvim/testdir/test_sha256.vim index 76d1306836..f6f430b04e 100644 --- a/src/nvim/testdir/test_sha256.vim +++ b/src/nvim/testdir/test_sha256.vim @@ -1,8 +1,8 @@ " Tests for the sha256() function. -if !has('cryptv') || !exists('*sha256') - finish -endif +source check.vim +CheckFeature cryptv +CheckFunction sha256 function Test_sha256() " test for empty string: diff --git a/src/nvim/testdir/test_smartindent.vim b/src/nvim/testdir/test_smartindent.vim index f3650a9ac4..e2d028e828 100644 --- a/src/nvim/testdir/test_smartindent.vim +++ b/src/nvim/testdir/test_smartindent.vim @@ -21,9 +21,7 @@ endfunc func Test_smartindent_has_no_effect() new exe "normal! i\<Tab>one\<Esc>" - set noautoindent - set smartindent - set indentexpr= + setlocal noautoindent smartindent indentexpr= exe "normal! Gotwo\<Esc>" call assert_equal("\ttwo", getline("$")) @@ -32,16 +30,13 @@ func Test_smartindent_has_no_effect() call assert_equal("three", getline("$")) delfunction! MyIndent - set autoindent& - set smartindent& - set indentexpr& bwipe! endfunc " Test for inserting '{' and '} with smartindent func Test_smartindent_braces() new - set smartindent shiftwidth=4 + setlocal smartindent shiftwidth=4 call setline(1, [' if (a)', "\tif (b)", "\t return 1"]) normal 2ggO{ normal 3ggA { @@ -57,7 +52,62 @@ func Test_smartindent_braces() \ "\t}", \ ' }' \ ], getline(1, '$')) - set si& sw& ai& + close! +endfunc + +" Test for adding a new line before and after comments with smartindent +func Test_si_add_line_around_comment() + new + setlocal smartindent shiftwidth=4 + call setline(1, [' A', '# comment1', '# comment2']) + exe "normal GoC\<Esc>2GOB" + call assert_equal([' A', ' B', '# comment1', '# comment2', ' C'], + \ getline(1, '$')) + close! +endfunc + +" After a C style comment, indent for a following line should line up with the +" line containing the start of the comment. +func Test_si_indent_after_c_comment() + new + setlocal smartindent shiftwidth=4 fo+=ro + exe "normal i\<C-t>/*\ncomment\n/\n#define FOOBAR\n75\<Esc>ggOabc" + normal 3jOcont + call assert_equal([' abc', ' /*', ' * comment', ' * cont', + \ ' */', '#define FOOBAR', ' 75'], getline(1, '$')) + close! +endfunc + +" Test for indenting a statement after a if condition split across lines +func Test_si_if_cond_split_across_lines() + new + setlocal smartindent shiftwidth=4 + exe "normal i\<C-t>if (cond1 &&\n\<C-t>cond2) {\ni = 10;\n}" + call assert_equal([' if (cond1 &&', "\t cond2) {", "\ti = 10;", + \ ' }'], getline(1, '$')) + close! +endfunc + +" Test for inserting lines before and after a one line comment +func Test_si_one_line_comment() + new + setlocal smartindent shiftwidth=4 + exe "normal i\<C-t>abc;\n\<C-t>/* comment */" + normal oi = 10; + normal kOj = 1; + call assert_equal([' abc;', "\tj = 1;", "\t/* comment */", "\ti = 10;"], + \ getline(1, '$')) + close! +endfunc + +" Test for smartindent with a comment continued across multiple lines +func Test_si_comment_line_continuation() + new + setlocal smartindent shiftwidth=4 + call setline(1, ['# com1', '# com2 \', ' contd', '# com3', ' xyz']) + normal ggOabc + call assert_equal([' abc', '# com1', '# com2 \', ' contd', '# com3', + \ ' xyz'], getline(1, '$')) close! endfunc diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 540c73a772..9895ad754c 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -1489,6 +1489,22 @@ func Test_sort_last_search_pat() close! endfunc +" Test for :sort with no last search pattern +func Test_sort_with_no_last_search_pat() + let lines =<< trim [SCRIPT] + call setline(1, ['3b', '1c', '2a']) + call assert_fails('sort //', 'E35:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + " Test for retaining marks across a :sort func Test_sort_with_marks() new diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim index 09baec0b7d..ba6fd5ad95 100644 --- a/src/nvim/testdir/test_source.vim +++ b/src/nvim/testdir/test_source.vim @@ -46,3 +46,45 @@ func Test_source_sandbox() bwipe! call delete('Xsourcehello') endfunc + +" When deleting a file and immediately creating a new one the inode may be +" recycled. Vim should not recognize it as the same script. +func Test_different_script() + call writefile(['let s:var = "asdf"'], 'XoneScript') + source XoneScript + call delete('XoneScript') + call writefile(['let g:var = s:var'], 'XtwoScript') + call assert_fails('source XtwoScript', 'E121:') + call delete('XtwoScript') +endfunc + +" When sourcing a vim script, shebang should be ignored. +func Test_source_ignore_shebang() + call writefile(['#!./xyzabc', 'let g:val=369'], 'Xfile.vim') + source Xfile.vim + call assert_equal(g:val, 369) + call delete('Xfile.vim') +endfunc + +" Test for expanding <sfile> in a autocmd and for <slnum> and <sflnum> +func Test_source_autocmd_sfile() + let code =<< trim [CODE] + let g:SfileName = '' + augroup sfiletest + au! + autocmd User UserAutoCmd let g:Sfile = '<sfile>:t' + augroup END + doautocmd User UserAutoCmd + let g:Slnum = expand('<slnum>') + let g:Sflnum = expand('<sflnum>') + augroup! sfiletest + [CODE] + call writefile(code, 'Xscript.vim') + source Xscript.vim + call assert_equal('Xscript.vim', g:Sfile) + call assert_equal('7', g:Slnum) + call assert_equal('8', g:Sflnum) + call delete('Xscript.vim') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 215d4387d6..7744c5bcca 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -2,9 +2,7 @@ " Note: this file uses latin1 encoding, but is used with utf-8 encoding. source check.vim -if !has('spell') - finish -endif +CheckFeature spell source screendump.vim @@ -16,6 +14,8 @@ func TearDown() call delete('Xtest.latin1.add.spl') call delete('Xtest.latin1.spl') call delete('Xtest.latin1.sug') + " set 'encoding' to clear the word list + set encoding=utf-8 endfunc func Test_wrap_search() @@ -131,6 +131,106 @@ foobar/? set spell& endfunc +func Test_spell_file_missing() + let s:spell_file_missing = 0 + augroup TestSpellFileMissing + autocmd! SpellFileMissing * let s:spell_file_missing += 1 + augroup END + + set spell spelllang=ab_cd + let messages = GetMessages() + " This message is not shown in Nvim because of #3027 + " call assert_equal('Warning: Cannot find word list "ab.utf-8.spl" or "ab.ascii.spl"', messages[-1]) + call assert_equal(1, s:spell_file_missing) + + new XTestSpellFileMissing + augroup TestSpellFileMissing + autocmd! SpellFileMissing * bwipe + augroup END + call assert_fails('set spell spelllang=ab_cd', 'E797:') + + " clean up + augroup TestSpellFileMissing + autocmd! SpellFileMissing + augroup END + augroup! TestSpellFileMissing + unlet s:spell_file_missing + set spell& spelllang& + %bwipe! +endfunc + +func Test_spelldump() + " In case the spell file is not found avoid getting the download dialog, we + " would get stuck at the prompt. + let g:en_not_found = 0 + augroup TestSpellFileMissing + au! SpellFileMissing * let g:en_not_found = 1 + augroup END + set spell spelllang=en + spellrare! emacs + if g:en_not_found + call assert_report("Could not find English spell file") + else + spelldump + + " Check assumption about region: 1: us, 2: au, 3: ca, 4: gb, 5: nz. + call assert_equal('/regions=usaucagbnz', getline(1)) + call assert_notequal(0, search('^theater/1$')) " US English only. + call assert_notequal(0, search('^theatre/2345$')) " AU, CA, GB or NZ English. + + call assert_notequal(0, search('^emacs/?$')) " ? for a rare word. + call assert_notequal(0, search('^the the/!$')) " ! for a wrong word. + endif + + " clean up + unlet g:en_not_found + augroup TestSpellFileMissing + autocmd! SpellFileMissing + augroup END + augroup! TestSpellFileMissing + bwipe + set spell& +endfunc + +func Test_spelldump_bang() + new + call setline(1, 'This is a sample sentence.') + redraw + + " In case the spell file is not found avoid getting the download dialog, we + " would get stuck at the prompt. + let g:en_not_found = 0 + augroup TestSpellFileMissing + au! SpellFileMissing * let g:en_not_found = 1 + augroup END + + set spell + + if g:en_not_found + call assert_report("Could not find English spell file") + else + redraw + spelldump! + + " :spelldump! includes the number of times a word was found while updating + " the screen. + " Common word count starts at 10, regular word count starts at 0. + call assert_notequal(0, search("^is\t11$")) " common word found once. + call assert_notequal(0, search("^the\t10$")) " common word never found. + call assert_notequal(0, search("^sample\t1$")) " regular word found once. + call assert_equal(0, search("^screen\t")) " regular word never found. + endif + + " clean up + unlet g:en_not_found + augroup TestSpellFileMissing + autocmd! SpellFileMissing + augroup END + augroup! TestSpellFileMissing + %bwipe! + set spell& +endfunc + func Test_spelllang_inv_region() set spell spelllang=en_xx let messages = GetMessages() @@ -185,6 +285,18 @@ func Test_spellreall() bwipe! endfunc +func Test_spell_dump_word_length() + " this was running over MAXWLEN + new + noremap 0 0a0zW0000000 + sil! norm 0z=0 + sil norm 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + sil! norm 0z=0 + + bwipe! + nunmap 0 +endfunc + " Test spellsuggest({word} [, {max} [, {capital}]]) func Test_spellsuggest() " Verify suggestions are given even when spell checking is not enabled. @@ -626,6 +738,10 @@ func Test_zz_sal_and_addition() set spl=Xtest_ca.latin1.spl call assert_equal("elequint", FirstSpellWord()) call assert_equal("elekwint", SecondSpellWord()) + + bwipe! + set spellfile= + set spl& endfunc func Test_spellfile_value() @@ -699,6 +815,28 @@ func Test_spellsuggest_too_deep() bwipe! endfunc +func Test_spell_good_word_invalid() + " This was adding a word with a 0x02 byte, which causes havoc. + enew + norm o0 + sil! norm rzzWs00/ + 2 + sil! norm VzGprzzW + sil! norm z= + + bwipe! +endfunc + +func Test_spell_good_word_slash() + " This caused E1280. + new + norm afoo / + 1 + norm zG + + bwipe! +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim index 3c07e0782b..b7e3da37cb 100644 --- a/src/nvim/testdir/test_spell_utf8.vim +++ b/src/nvim/testdir/test_spell_utf8.vim @@ -13,6 +13,8 @@ func TearDown() call delete('Xtest.utf-8.add.spl') call delete('Xtest.utf-8.spl') call delete('Xtest.utf-8.sug') + " set 'encoding' to clear the word list + set encoding=utf-8 endfunc let g:test_data_aff1 = [ @@ -484,7 +486,6 @@ let g:test_data_aff_sal = [ \ ] func LoadAffAndDic(aff_contents, dic_contents) - set enc=utf-8 set spellfile= call writefile(a:aff_contents, "Xtest.aff") call writefile(a:dic_contents, "Xtest.dic") @@ -760,6 +761,7 @@ func Test_spell_sal_and_addition() call assert_equal("elequint", FirstSpellWord()) call assert_equal("elekwint", SecondSpellWord()) + bwipe! set spellfile= set spl& endfunc @@ -780,7 +782,12 @@ func Test_no_crash_with_weird_text() € END call setline(1, lines) - exe "%norm \<C-v>ez=>\<C-v>wzG" + try + exe "%norm \<C-v>ez=>\<C-v>wzG" + catch /E1280:/ + let caught = 'yes' + endtry + call assert_equal('yes', caught) bwipe! endfunc @@ -798,10 +805,20 @@ func Test_word_index() sil norm z= bwipe! - " clear the word list - set enc=utf-8 call delete('Xtmpfile') endfunc +func Test_check_empty_line() + " This was using freed memory + enew + spellgood! fl + norm z= + norm yy + sil! norm P]svc + norm P]s + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim index 0f48ab8f6f..b028e9d969 100644 --- a/src/nvim/testdir/test_spellfile.vim +++ b/src/nvim/testdir/test_spellfile.vim @@ -167,8 +167,632 @@ func Test_spell_normal() call assert_equal([], glob('Xspellfile.add',0,1)) call assert_equal([], glob('Xspellfile2.add',0,1)) + set spellfile= spell& spelllang& + bw! +endfunc + +" Spell file content test. Write 'content' to the spell file prefixed by the +" spell file header and then enable spell checking. If 'emsg' is not empty, +" then check for error. +func Spellfile_Test(content, emsg) + let splfile = './Xtest/spell/Xtest.utf-8.spl' + " Add the spell file header and version (VIMspell2) + let v = 0z56494D7370656C6C32 + a:content + call writefile(v, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + if a:emsg != '' + call assert_fails('set spell', a:emsg) + else + " FIXME: With some invalid spellfile contents, there are no error + " messages. So don't know how to check for the test result. + set spell + endif + set nospell spelllang& rtp& +endfunc + +" Test for spell file format errors. +" The spell file format is described in spellfile.c +func Test_spellfile_format_error() + let save_rtp = &rtp + call mkdir('Xtest/spell', 'p') + let splfile = './Xtest/spell/Xtest.utf-8.spl' + + " empty spell file + call writefile([], splfile) + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E757:') + set nospell spelllang& + + " invalid file ID + call writefile(0z56494D, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E757:') + set nospell spelllang& + + " missing version number + call writefile(0z56494D7370656C6C, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E771:') + set nospell spelllang& + + " invalid version number + call writefile(0z56494D7370656C6C7A, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E772:') + set nospell spelllang& + + " no sections + call Spellfile_Test(0z, 'E758:') + + " missing section length + call Spellfile_Test(0z00, 'E758:') + + " unsupported required section + call Spellfile_Test(0z7A0100000004, 'E770:') + + " unsupported not-required section + call Spellfile_Test(0z7A0000000004, 'E758:') + + " SN_REGION: invalid number of region names + call Spellfile_Test(0z0000000000FF, 'E759:') + + " SN_CHARFLAGS: missing <charflagslen> length + call Spellfile_Test(0z010000000004, 'E758:') + + " SN_CHARFLAGS: invalid <charflagslen> length + call Spellfile_Test(0z0100000000010201, '') + + " SN_CHARFLAGS: charflagslen == 0 and folcharslen != 0 + call Spellfile_Test(0z01000000000400000101, 'E759:') + + " SN_CHARFLAGS: missing <folcharslen> length + call Spellfile_Test(0z01000000000100, 'E758:') + + " SN_PREFCOND: invalid prefcondcnt + call Spellfile_Test(0z03000000000100, 'E759:') + + " SN_PREFCOND: invalid condlen + call Spellfile_Test(0z0300000000020001, 'E759:') + + " SN_REP: invalid repcount + call Spellfile_Test(0z04000000000100, 'E758:') + + " SN_REP: missing rep + call Spellfile_Test(0z0400000000020004, 'E758:') + + " SN_REP: zero repfromlen + call Spellfile_Test(0z040000000003000100, 'E759:') + + " SN_REP: invalid reptolen + call Spellfile_Test(0z0400000000050001014101, '') + + " SN_REP: zero reptolen + call Spellfile_Test(0z0400000000050001014100, 'E759:') + + " SN_SAL: missing salcount + call Spellfile_Test(0z05000000000102, 'E758:') + + " SN_SAL: missing salfromlen + call Spellfile_Test(0z050000000003080001, 'E758:') + + " SN_SAL: missing saltolen + call Spellfile_Test(0z0500000000050400010161, 'E758:') + + " SN_WORDS: non-NUL terminated word + call Spellfile_Test(0z0D000000000376696D, 'E758:') + + " SN_WORDS: very long word + let v = eval('0z0D000000012C' .. repeat('41', 300)) + call Spellfile_Test(v, 'E759:') + + " SN_SOFO: missing sofofromlen + call Spellfile_Test(0z06000000000100, 'E758:') + + " SN_SOFO: missing sofotolen + call Spellfile_Test(0z06000000000400016100, 'E758:') + + " SN_SOFO: missing sofoto + call Spellfile_Test(0z0600000000050001610000, 'E759:') + + " SN_COMPOUND: compmax is less than 2 + call Spellfile_Test(0z08000000000101, 'E759:') + + " SN_COMPOUND: missing compsylmax and other options + call Spellfile_Test(0z0800000000020401, 'E759:') + + " SN_COMPOUND: missing compoptions + call Spellfile_Test(0z080000000005040101, 'E758:') + + " SN_INFO: missing info + call Spellfile_Test(0z0F0000000005040101, '') + + " SN_MIDWORD: missing midword + call Spellfile_Test(0z0200000000040102, '') + + " SN_MAP: missing midword + call Spellfile_Test(0z0700000000040102, '') + + " SN_SYLLABLE: missing SYLLABLE item + call Spellfile_Test(0z0900000000040102, '') + + " SN_SYLLABLE: More than SY_MAXLEN size + let v = eval('0z090000000022612F' .. repeat('62', 32)) + call Spellfile_Test(v, '') + + " LWORDTREE: missing + call Spellfile_Test(0zFF, 'E758:') + + " LWORDTREE: missing tree node + call Spellfile_Test(0zFF00000004, 'E758:') + + " LWORDTREE: missing tree node value + call Spellfile_Test(0zFF0000000402, 'E758:') + + " KWORDTREE: missing tree node + call Spellfile_Test(0zFF0000000000000004, 'E758:') + + " PREFIXTREE: missing tree node + call Spellfile_Test(0zFF000000000000000000000004, 'E758:') + + let &rtp = save_rtp + call delete('Xtest', 'rf') +endfunc + +" Test for format errors in suggest file +func Test_sugfile_format_error() + let save_rtp = &rtp + call mkdir('Xtest/spell', 'p') + let splfile = './Xtest/spell/Xtest.utf-8.spl' + let sugfile = './Xtest/spell/Xtest.utf-8.sug' + + " create an empty spell file with a suggest timestamp + call writefile(0z56494D7370656C6C320B00000000080000000000000044FF000000000000000000000000, splfile, 'b') + + " 'encoding' is set before each test to clear the previously loaded suggest + " file from memory. + + " empty suggest file + set encoding=utf-8 + call writefile([], sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E778:') + set nospell spelllang& + + " zero suggest version + set encoding=utf-8 + call writefile(0z56494D73756700, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E779:') + set nospell spelllang& + + " unsupported suggest version + set encoding=utf-8 + call writefile(0z56494D7375671F, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E780:') + set nospell spelllang& + + " missing suggest timestamp + set encoding=utf-8 + call writefile(0z56494D73756701, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E781:') + set nospell spelllang& + + " incorrect suggest timestamp + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000FF, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E781:') + set nospell spelllang& + + " missing suggest wordtree + set encoding=utf-8 + call writefile(0z56494D737567010000000000000044, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + " invalid suggest word count in SUGTABLE + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000440000000022, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + " missing sugline in SUGTABLE + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000440000000000000005, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + let &rtp = save_rtp + call delete('Xtest', 'rf') +endfunc + +" Test for using :mkspell to create a spell file from a list of words +func Test_wordlist_dic() + " duplicate encoding + let lines =<< trim [END] + # This is an example word list + + /encoding=latin1 + /encoding=latin1 + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell Xwordlist.spl Xwordlist.dic') + call assert_match('Duplicate /encoding= line ignored in Xwordlist.dic line 4: /encoding=latin1', output) + + " multiple encoding for a word + let lines =<< trim [END] + example + /encoding=latin1 + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('/encoding= line after word ignored in Xwordlist.dic line 2: /encoding=latin1', output) + + " unsupported encoding for a word + let lines =<< trim [END] + /encoding=Xtest + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Conversion in Xwordlist.dic not supported: from Xtest to utf-8', output) + + " duplicate region + let lines =<< trim [END] + /regions=usca + /regions=usca + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Duplicate /regions= line ignored in Xwordlist.dic line 2: regions=usca', output) + + " maximum regions + let lines =<< trim [END] + /regions=uscauscauscauscausca + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Too many regions in Xwordlist.dic line 1: uscauscauscauscausca', output) + + " unsupported '/' value + let lines =<< trim [END] + /test=abc + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('/ line ignored in Xwordlist.dic line 1: /test=abc', output) + + " unsupported flag + let lines =<< trim [END] + example/+ + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Unrecognized flags in Xwordlist.dic line 1: +', output) + + " non-ascii word + call writefile(["ʀʀ"], 'Xwordlist.dic') + let output = execute('mkspell! -ascii Xwordlist.spl Xwordlist.dic') + call assert_match('Ignored 1 words with non-ASCII characters', output) + + call delete('Xwordlist.spl') + call delete('Xwordlist.dic') +endfunc + +" Test for the :mkspell command +func Test_mkspell() + call assert_fails('mkspell Xtest_us.spl', 'E751:') + call assert_fails('mkspell a b c d e f g h i j k', 'E754:') + + call writefile([], 'Xtest.spl') + call writefile([], 'Xtest.dic') + call assert_fails('mkspell Xtest.spl Xtest.dic', 'E13:') + call delete('Xtest.spl') + call delete('Xtest.dic') + + call mkdir('Xtest.spl') + call assert_fails('mkspell! Xtest.spl Xtest.dic', 'E17:') + call delete('Xtest.spl', 'rf') + + " can't write the .spl file as its directory does not exist + call writefile([], 'Xtest.aff') + call writefile([], 'Xtest.dic') + call assert_fails('mkspell DOES_NOT_EXIT/Xtest.spl Xtest.dic', 'E484:') + call delete('Xtest.aff') + call delete('Xtest.dic') + + call assert_fails('mkspell en en_US abc_xyz', 'E755:') +endfunc + +" Tests for :mkspell with a .dic and .aff file +func Test_aff_file_format_error() + " FIXME: For some reason, the :mkspell command below doesn't fail on the + " MS-Windows CI build. Disable this test on MS-Windows for now. + CheckNotMSWindows + + " No word count in .dic file + call writefile([], 'Xtest.dic') + call writefile([], 'Xtest.aff') + call assert_fails('mkspell! Xtest.spl Xtest', 'E760:') + + " create a .dic file for the tests below + call writefile(['1', 'work'], 'Xtest.dic') + + " Invalid encoding in .aff file + call writefile(['# comment', 'SET Xinvalidencoding'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Conversion in Xtest.aff not supported: from xinvalidencoding', output) + + " Invalid flag in .aff file + call writefile(['FLAG xxx'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Invalid value for FLAG in Xtest.aff line 1: xxx', output) + + " set FLAGS after using flag for an affix + call writefile(['SFX L Y 1', 'SFX L 0 re [^x]', 'FLAG long'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('FLAG after using flags in Xtest.aff line 3: long', output) + + " INFO in affix file + let save_encoding = &encoding + call mkdir('Xrtp/spell', 'p') + call writefile(['1', 'work'], 'Xrtp/spell/Xtest.dic') + call writefile(['NAME klingon', 'VERSION 1.4', 'AUTHOR Spock'], + \ 'Xrtp/spell/Xtest.aff') + silent mkspell! Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest + let save_rtp = &rtp + set runtimepath=./Xrtp + set spelllang=Xtest + set spell + let output = split(execute('spellinfo'), "\n") + call assert_equal("NAME klingon", output[1]) + call assert_equal("VERSION 1.4", output[2]) + call assert_equal("AUTHOR Spock", output[3]) + let &rtp = save_rtp + call delete('Xrtp', 'rf') + set spell& spelllang& spellfile& + %bw! + " 'encoding' must be set again to clear the spell file in memory + let &encoding = save_encoding + + " COMPOUNDFORBIDFLAG flag after PFX in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDFLAG c', 'COMPOUNDFORBIDFLAG x'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in Xtest.aff line 4', output) + + " COMPOUNDPERMITFLAG flag after PFX in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDPERMITFLAG c'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in Xtest.aff line 3', output) + + " Wrong COMPOUNDRULES flag value in an affix file + call writefile(['COMPOUNDRULES a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDRULES value in Xtest.aff line 1: a', output) + + " Wrong COMPOUNDWORDMAX flag value in an affix file + call writefile(['COMPOUNDWORDMAX 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDWORDMAX value in Xtest.aff line 1: 0', output) + + " Wrong COMPOUNDMIN flag value in an affix file + call writefile(['COMPOUNDMIN 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDMIN value in Xtest.aff line 1: 0', output) + + " Wrong COMPOUNDSYLMAX flag value in an affix file + call writefile(['COMPOUNDSYLMAX 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDSYLMAX value in Xtest.aff line 1: 0', output) + + " Wrong CHECKCOMPOUNDPATTERN flag value in an affix file + call writefile(['CHECKCOMPOUNDPATTERN 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong CHECKCOMPOUNDPATTERN value in Xtest.aff line 1: 0', output) + + " Both compounding and NOBREAK specified + call writefile(['COMPOUNDFLAG c', 'NOBREAK'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Warning: both compounding and NOBREAK specified', output) + + " Duplicate affix entry in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L Y 1', 'PFX L 0 re x'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Duplicate affix in Xtest.aff line 3: L', output) + + " Duplicate affix entry in an affix file + call writefile(['PFX L Y 1', 'PFX L Y 1'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Unrecognized or duplicate item in Xtest.aff line 2: PFX', output) + + " Different combining flags in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L N 1'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Different combining flag in continued affix block in Xtest.aff line 3', output) + + " Try to reuse a affix used for BAD flag + call writefile(['BAD x', 'PFX x Y 1', 'PFX x 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in Xtest.aff line 2: x', output) + + " Trailing characters in an affix entry + call writefile(['PFX L Y 1 Test', 'PFX L 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 1: Test', output) + + " Trailing characters in an affix entry + call writefile(['PFX L Y 1', 'PFX L 0 re x Test'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 2: Test', output) + + " Incorrect combine flag in an affix entry + call writefile(['PFX L X 1', 'PFX L 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected Y or N in Xtest.aff line 1: X', output) + + " Invalid count for REP item + call writefile(['REP a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected REP(SAL) count in Xtest.aff line 1', output) + + " Trailing characters in REP item + call writefile(['REP 1', 'REP f ph test'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 2: test', output) + + " Invalid count for MAP item + call writefile(['MAP a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected MAP count in Xtest.aff line 1', output) + + " Duplicate character in a MAP item + call writefile(['MAP 2', 'MAP xx', 'MAP yy'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Duplicate character in MAP in Xtest.aff line 2', output) + + " Use COMPOUNDSYLMAX without SYLLABLE + call writefile(['COMPOUNDSYLMAX 2'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('COMPOUNDSYLMAX used without SYLLABLE', output) + + " Missing SOFOTO + call writefile(['SOFOFROM abcdef'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Missing SOFOTO line in Xtest.aff', output) + + " Length of SOFOFROM and SOFOTO differ + call writefile(['SOFOFROM abcde', 'SOFOTO ABCD'], 'Xtest.aff') + call assert_fails('mkspell! Xtest.spl Xtest', 'E759:') + + " Both SAL and SOFOFROM/SOFOTO items + call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Both SAL and SOFO lines in Xtest.aff', output) + + " use an alphabet flag when FLAG is num + call writefile(['FLAG num', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Flag is not a number in Xtest.aff line 2: L', output) + + " use number and alphabet flag when FLAG is num + call writefile(['FLAG num', 'SFX 4f Y 1', 'SFX 4f 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Affix name too long in Xtest.aff line 2: 4f', output) + + " use a single character flag when FLAG is long + call writefile(['FLAG long', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Illegal flag in Xtest.aff line 2: L', output) + + " duplicate word in the .dic file + call writefile(['2', 'good', 'good', 'good'], 'Xtest.dic') + call writefile(['NAME vim'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('First duplicate word in Xtest.dic line 3: good', output) + call assert_match('2 duplicate word(s) in Xtest.dic', output) + + call delete('Xtest.dic') + call delete('Xtest.aff') + call delete('Xtest.spl') + call delete('Xtest.sug') +endfunc + +func Test_spell_add_word() set spellfile= + call assert_fails('spellgood abc', 'E764:') + + set spellfile=Xtest.utf-8.add + call assert_fails('2spellgood abc', 'E765:') + + edit Xtest.utf-8.add + call setline(1, 'sample') + call assert_fails('spellgood abc', 'E139:') + set spellfile& + %bw! +endfunc + +func Test_spellfile_verbose() + call writefile(['1', 'one'], 'XtestVerbose.dic') + call writefile([], 'XtestVerbose.aff') + mkspell! XtestVerbose-utf8.spl XtestVerbose + set spell + + " First time: the spl file should be read. + let a = execute('3verbose set spelllang=XtestVerbose-utf8.spl') + call assert_match('Reading spell file "XtestVerbose-utf8.spl"', a) + + " Second time time: the spl file should not be read (already read). + let a = execute('3verbose set spelllang=XtestVerbose-utf8.spl') + call assert_notmatch('Reading spell file "XtestVerbose-utf8.spl"', a) + + set spell& spelllang& + call delete('XtestVerbose.dic') + call delete('XtestVerbose.aff') + call delete('XtestVerbose-utf8.spl') +endfunc + +" Test NOBREAK (see :help spell-NOBREAK) +func Test_NOBREAK() + call writefile(['3', 'one', 'two', 'three' ], 'XtestNOBREAK.dic') + call writefile(['NOBREAK' ], 'XtestNOBREAK.aff') + + mkspell! XtestNOBREAK-utf8.spl XtestNOBREAK + set spell spelllang=XtestNOBREAK-utf8.spl + + call assert_equal(['', ''], spellbadword('One two three onetwo onetwothree threetwoone')) + + call assert_equal(['x', 'bad'], spellbadword('x')) + call assert_equal(['y', 'bad'], spellbadword('yone')) + call assert_equal(['z', 'bad'], spellbadword('onez')) + call assert_equal(['zero', 'bad'], spellbadword('Onetwozerothree')) + + new + call setline(1, 'Onetwwothree') + norm! fw1z= + call assert_equal('Onetwothree', getline(1)) + call setline(1, 'Onetwothre') + norm! fh1z= + call assert_equal('Onetwothree', getline(1)) + bw! + set spell& spelllang& + call delete('XtestNOBREAK.dic') + call delete('XtestNOBREAK.aff') + call delete('XtestNOBREAK-utf8.spl') endfunc " Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN) @@ -183,7 +807,7 @@ func Test_spellfile_CHECKCOMPOUNDPATTERN() \ 'CHECKCOMPOUNDPATTERN wo on', \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff') - let output = execute('mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN') + mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl " Check valid words with and without valid compounds. @@ -268,7 +892,7 @@ func Test_spellfile_COMMON() \ 'ted'], 'XtestCOMMON.dic') call writefile(['COMMON the and'], 'XtestCOMMON.aff') - let output = execute('mkspell! XtestCOMMON-utf8.spl XtestCOMMON') + mkspell! XtestCOMMON-utf8.spl XtestCOMMON set spell spelllang=XtestCOMMON-utf8.spl " COMMON words 'and' and 'the' should be the top suggestions. @@ -283,4 +907,121 @@ func Test_spellfile_COMMON() call delete('XtestCOMMON-utf8.spl') endfunc +" Test CIRCUMFIX (see: :help spell-CIRCUMFIX) +func Test_spellfile_CIRCUMFIX() + " Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests + call writefile(['1', + \ 'nagy/C po:adj'], 'XtestCIRCUMFIX.dic') + call writefile(['# circumfixes: ~ obligate prefix/suffix combinations', + \ '# superlative in Hungarian: leg- (prefix) AND -bb (suffix)', + \ '', + \ 'CIRCUMFIX X', + \ '', + \ 'PFX A Y 1', + \ 'PFX A 0 leg/X .', + \ '', + \ 'PFX B Y 1', + \ 'PFX B 0 legesleg/X .', + \ '', + \ 'SFX C Y 3', + \ 'SFX C 0 obb . is:COMPARATIVE', + \ 'SFX C 0 obb/AX . is:SUPERLATIVE', + \ 'SFX C 0 obb/BX . is:SUPERSUPERLATIVE'], 'XtestCIRCUMFIX.aff') + + mkspell! XtestCIRCUMFIX-utf8.spl XtestCIRCUMFIX + set spell spelllang=XtestCIRCUMFIX-utf8.spl + + " From https://catalog.ldc.upenn.edu/docs/LDC2008T01/acta04.pdf: + " Hungarian English + " --------- ------- + " nagy great + " nagyobb greater + " legnagyobb greatest + " legeslegnagyob most greatest + call assert_equal(['', ''], spellbadword('nagy nagyobb legnagyobb legeslegnagyobb')) + + for badword in ['legnagy', 'legeslegnagy', 'legobb', 'legeslegobb'] + call assert_equal([badword, 'bad'], spellbadword(badword)) + endfor + + set spell& spelllang& + call delete('XtestCIRCUMFIX.dic') + call delete('XtestCIRCUMFIX.aff') + call delete('XtestCIRCUMFIX-utf8.spl') +endfunc + +" Test SFX that strips/chops characters +func Test_spellfile_SFX_strip() + " Simplified conjugation of Italian verbs ending in -are (first conjugation). + call writefile(['SFX A Y 4', + \ 'SFX A are iamo [^icg]are', + \ 'SFX A are hiamo [cg]are', + \ 'SFX A re mo iare', + \ 'SFX A re vamo are'], + \ 'XtestSFX.aff') + " Examples of Italian verbs: + " - cantare = to sing + " - cercare = to search + " - odiare = to hate + call writefile(['3', 'cantare/A', 'cercare/A', 'odiare/A'], 'XtestSFX.dic') + + mkspell! XtestSFX-utf8.spl XtestSFX + set spell spelllang=XtestSFX-utf8.spl + + " To sing, we're singing, we were singing. + call assert_equal(['', ''], spellbadword('cantare cantiamo cantavamo')) + + " To search, we're searching, we were searching. + call assert_equal(['', ''], spellbadword('cercare cerchiamo cercavamo')) + + " To hate, we hate, we were hating. + call assert_equal(['', ''], spellbadword('odiare odiamo odiavamo')) + + for badword in ['canthiamo', 'cerciamo', 'cantarevamo', 'odiiamo'] + call assert_equal([badword, 'bad'], spellbadword(badword)) + endfor + + call assert_equal(['cantiamo'], spellsuggest('canthiamo', 1)) + call assert_equal(['cerchiamo'], spellsuggest('cerciamo', 1)) + call assert_equal(['cantavamo'], spellsuggest('cantarevamo', 1)) + call assert_equal(['odiamo'], spellsuggest('odiiamo', 1)) + + set spell& spelllang& + call delete('XtestSFX.dic') + call delete('XtestSFX.aff') + call delete('XtestSFX-utf8.spl') +endfunc + +" When 'spellfile' is not set, adding a new good word will automatically set +" the 'spellfile' +func Test_init_spellfile() + let save_rtp = &rtp + let save_encoding = &encoding + call mkdir('Xrtp/spell', 'p') + call writefile(['vim'], 'Xrtp/spell/Xtest.dic') + silent mkspell Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest.dic + set runtimepath=./Xrtp + set spelllang=Xtest + set spell + silent spellgood abc + call assert_equal('./Xrtp/spell/Xtest.utf-8.add', &spellfile) + call assert_equal(['abc'], readfile('Xrtp/spell/Xtest.utf-8.add')) + call assert_true(filereadable('Xrtp/spell/Xtest.utf-8.spl')) + set spell& spelllang& spellfile& + call delete('Xrtp', 'rf') + let &encoding = save_encoding + let &rtp = save_rtp + %bw! +endfunc + +" Test for the 'mkspellmem' option +func Test_mkspellmem_opt() + call assert_fails('set mkspellmem=1000', 'E474:') + call assert_fails('set mkspellmem=1000,', 'E474:') + call assert_fails('set mkspellmem=1000,50', 'E474:') + call assert_fails('set mkspellmem=1000,50,', 'E474:') + call assert_fails('set mkspellmem=1000,50,10,', 'E474:') + call assert_fails('set mkspellmem=1000,50,0', 'E474:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 8579457d9f..ec35fac964 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -2,13 +2,15 @@ " " Not tested yet: " %N -" %T -" %X source view_util.vim source check.vim source term_util.vim +func SetUp() + set laststatus=2 +endfunc + func s:get_statusline() return ScreenLines(&lines - 1, &columns)[0] endfunc @@ -104,6 +106,18 @@ func Test_statusline() set statusline=%F call assert_match('/testdir/Xstatusline\s*$', s:get_statusline()) + " Test for min and max width with %(. For some reason, if this test is moved + " after the below test for the help buffer flag, then the code to truncate + " the string is not executed. + set statusline=%015(%f%) + call assert_match('^ Xstatusline\s*$', s:get_statusline()) + set statusline=%.6(%f%) + call assert_match('^<sline\s*$', s:get_statusline()) + set statusline=%14f + call assert_match('^ Xstatusline\s*$', s:get_statusline()) + set statusline=%.4L + call assert_match('^10>3\s*$', s:get_statusline()) + " %h: Help buffer flag, text is "[help]". " %H: Help buffer flag, text is ",HLP". set statusline=%h,%H diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 8483435062..f795d1c0cf 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -1,5 +1,7 @@ " Tests for the substitute (:s) command +source shared.vim + func Test_multiline_subst() enew! call append(0, ["1 aa", @@ -140,7 +142,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("gQsc\<CR>y", 'tx') + call feedkeys("Qsc\<CR>y", 'tx') bwipe! endfunc @@ -292,7 +294,7 @@ endfunc " Test for *:s%* on :substitute. func Test_sub_cmd_6() - throw "skipped: Nvim removed POSIX-related 'cpoptions' flags" + throw 'Skipped: Nvim does not support cpoptions flag "/"' set magic& set cpo+=/ @@ -806,6 +808,41 @@ func Test_sub_expand_text() close! endfunc +" Test for command failures when the last substitute pattern is not set. +func Test_sub_with_no_last_pat() + let lines =<< trim [SCRIPT] + call assert_fails('~', 'E33:') + call assert_fails('s//abc/g', 'E476:') + call assert_fails('s\/bar', 'E476:') + call assert_fails('s\&bar&', 'E476:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + + " Nvim does not support cpoptions flag "/"' + " let lines =<< trim [SCRIPT] + " set cpo+=/ + " call assert_fails('s/abc/%/', 'E33:') + " call writefile(v:errors, 'Xresult') + " qall! + " [SCRIPT] + " call writefile(lines, 'Xscript') + " if RunVim([], [], '--clean -S Xscript') + " call assert_equal([], readfile('Xresult')) + " endif + + call delete('Xscript') + call delete('Xresult') +endfunc + +func Test_substitute() + call assert_equal('a1aï¼’a3a', substitute('123', '\zs', 'a', 'g')) +endfunc + func Test_submatch_list_concatenate() let pat = 'A\(.\)' let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])} @@ -851,6 +888,54 @@ func Test_sub_change_window() delfunc Repl endfunc +" This was undoign a change in between computing the length and using it. +func Do_Test_sub_undo_change() + new + norm o0000000000000000000000000000000000000000000000000000 + silent! s/\%')/\=Repl() + bwipe! +endfunc + +func Test_sub_undo_change() + func Repl() + silent! norm g- + endfunc + call Do_Test_sub_undo_change() + + func! Repl() + silent earlier + endfunc + call Do_Test_sub_undo_change() + + delfunc Repl +endfunc + +" This was opening a command line window from the expression +func Test_sub_open_cmdline_win() + " the error only happens in a very specific setup, run a new Vim instance to + " get a clean starting point. + let lines =<< trim [SCRIPT] + set vb t_vb= + norm o0000000000000000000000000000000000000000000000000000 + func Replace() + norm q/ + endfunc + s/\%')/\=Replace() + redir >Xresult + messages + redir END + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '-u NONE -S Xscript') + call assert_match('E565: Not allowed to change text or change window', + \ readfile('Xresult')->join('XX')) + endif + + call delete('Xscript') + call delete('Xresult') +endfunc + " Test for the 2-letter and 3-letter :substitute commands func Test_substitute_short_cmd() new diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index b180f27685..923e1cbf50 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -278,7 +278,6 @@ func Test_swap_recover_ext() autocmd SwapExists * let v:swapchoice = 'r' augroup END - " Create a valid swapfile by editing a file with a special extension. split Xtest.scr call setline(1, ['one', 'two', 'three']) @@ -311,6 +310,46 @@ func Test_swap_recover_ext() augroup! test_swap_recover_ext endfunc +" Test for closing a split window automatically when a swap file is detected +" and 'Q' is selected in the confirmation prompt. +func Test_swap_split_win() + autocmd! SwapExists + augroup test_swap_splitwin + autocmd! + autocmd SwapExists * let v:swapchoice = 'q' + augroup END + + " Create a valid swapfile by editing a file with a special extension. + split Xtest.scr + call setline(1, ['one', 'two', 'three']) + write " file is written, not modified + write " write again to make sure the swapfile is created + " read the swapfile as a Blob + let swapfile_name = swapname('%') + let swapfile_bytes = readfile(swapfile_name, 'B') + + " Close and delete the file and recreate the swap file. + quit + call delete('Xtest.scr') + call writefile(swapfile_bytes, swapfile_name) + " Split edit the file again. This should fail to open the window + try + split Xtest.scr + catch + " E308 should be caught, not E306. + call assert_exception('E308:') " Original file may have been changed + endtry + call assert_equal(1, winnr('$')) + + call delete('Xtest.scr') + call delete(swapfile_name) + + augroup test_swap_splitwin + autocmd! + augroup END + augroup! test_swap_splitwin +endfunc + " Test for selecting 'q' in the attention prompt func Test_swap_prompt_splitwin() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 9f50b3c241..7ba0149971 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -1,5 +1,8 @@ " Test for syntax and syntax iskeyword option +source check.vim +CheckFeature syntax + source view_util.vim source screendump.vim @@ -197,6 +200,12 @@ func Test_syntax_completion() call assert_match('^"syn match Boolean Character ', @:) endfunc +func Test_echohl_completion() + call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"echohl NonText Normal none', @:) + call assert_equal('"echohl NonText Normal NormalFloat NormalNC none', @:) +endfunc + func Test_syntax_arg_skipped() syn clear syntax case ignore diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index 3a18206078..e58a412c5a 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -1,3 +1,4 @@ +" Test for tabline source shared.vim @@ -17,6 +18,9 @@ func TablineWithError() endfunc func Test_caught_error_in_tabline() + if has('gui') + set guioptions-=e + endif let showtabline_save = &showtabline set showtabline=2 let s:func_in_tabline_called = 0 @@ -30,6 +34,9 @@ func Test_caught_error_in_tabline() endfunc func Test_tabline_will_be_disabled_with_error() + if has('gui') + set guioptions-=e + endif let showtabline_save = &showtabline set showtabline=2 let s:func_in_tabline_called = 0 @@ -65,6 +72,47 @@ func Test_redrawtabline() au! Bufadd endfunc +" Test for the "%T" and "%X" flags in the 'tabline' option +func MyTabLine() + let s = '' + for i in range(tabpagenr('$')) + " set the tab page number (for mouse clicks) + let s .= '%' . (i + 1) . 'T' + + " the label is made by MyTabLabel() + let s .= ' %{MyTabLabel(' . (i + 1) . ')} ' + endfor + + " after the last tab fill with TabLineFill and reset tab page nr + let s .= '%T' + + " right-align the label to close the current tab page + if tabpagenr('$') > 1 + let s .= '%=%Xclose' + endif + + return s +endfunc + +func MyTabLabel(n) + let buflist = tabpagebuflist(a:n) + let winnr = tabpagewinnr(a:n) + return bufname(buflist[winnr - 1]) +endfunc + +func Test_tabline_flags() + if has('gui') + set guioptions-=e + endif + set tabline=%!MyTabLine() + edit Xtabline1 + tabnew Xtabline2 + redrawtabline + call assert_match('^ Xtabline1 Xtabline2\s\+close$', Screenline(1)) + set tabline= + %bw! +endfunc + function EmptyTabname() return "" endfunction diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 51ab5c1022..d891684ecb 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -139,7 +139,13 @@ function Test_tabpage() call assert_fails("tabmove -99", 'E474:') call assert_fails("tabmove -3+", 'E474:') call assert_fails("tabmove $3", 'E474:') + call assert_fails("%tabonly", 'E16:') 1tabonly! + tabmove 1 + call assert_equal(1, tabpagenr()) + tabnew + call assert_fails("-2tabmove", 'E474:') + tabonly! endfunc " Test autocommands @@ -607,6 +613,16 @@ func Test_tabpage_cmdheight() call delete('XTest_tabpage_cmdheight') endfunc +" Test for closing the tab page from a command window +func Test_tabpage_close_cmdwin() + tabnew + call feedkeys("q/:tabclose\<CR>\<Esc>", 'xt') + call assert_equal(2, tabpagenr('$')) + call feedkeys("q/:tabonly\<CR>\<Esc>", 'xt') + call assert_equal(2, tabpagenr('$')) + tabonly +endfunc + " Return the terminal key code for selecting a tab page from the tabline. This " sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0), " KS_FILLER (0x58) and then the tab page number. @@ -685,6 +701,69 @@ func Test_tabline_tabmenu() %bw! endfunc +" Test for changing the current tab page from an autocmd when closing a tab +" page. +func Test_tabpage_switchtab_on_close() + only + tabnew + tabnew + " Test for BufLeave + augroup T1 + au! + au BufLeave * tabfirst + augroup END + tabclose + call assert_equal(1, tabpagenr()) + augroup T1 + au! + augroup END + + " Test for WinLeave + $tabnew + augroup T1 + au! + au WinLeave * tabfirst + augroup END + tabclose + call assert_equal(1, tabpagenr()) + augroup T1 + au! + augroup END + + " Test for TabLeave + $tabnew + augroup T1 + au! + au TabLeave * tabfirst + augroup END + tabclose + call assert_equal(1, tabpagenr()) + augroup T1 + au! + augroup END + augroup! T1 + tabonly +endfunc + +" Test for closing the destination tabpage when jumping from one to another. +func Test_tabpage_close_on_switch() + tabnew + tabnew + edit Xfile + augroup T2 + au! + au BufLeave Xfile 1tabclose + augroup END + tabfirst + call assert_equal(2, tabpagenr()) + call assert_equal('Xfile', @%) + augroup T2 + au! + augroup END + augroup! T2 + %bw! +endfunc + " Test for jumping to last accessed tabpage func Test_lastused_tabpage() tabonly! diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 11e32067b2..1dd656ece5 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -5,12 +5,57 @@ source screendump.vim " SEGV occurs in older versions. (At least 7.4.1748 or older) func Test_ptag_with_notagstack() + CheckFeature quickfix + set notagstack call assert_fails('ptag does_not_exist_tag_name', 'E426') set tagstack&vim endfunc +func Test_ptjump() + CheckFeature quickfix + + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile\t1", + \ "three\tXfile\t3", + \ "two\tXfile\t2"], + \ 'Xtags') + call writefile(['one', 'two', 'three'], 'Xfile') + + %bw! + ptjump two + call assert_equal(2, winnr()) + wincmd p + call assert_equal(1, &previewwindow) + call assert_equal('Xfile', expand("%:p:t")) + call assert_equal(2, line('.')) + call assert_equal(2, winnr('$')) + call assert_equal(1, winnr()) + close + call setline(1, ['one', 'two', 'three']) + exe "normal 3G\<C-W>g}" + call assert_equal(2, winnr()) + wincmd p + call assert_equal(1, &previewwindow) + call assert_equal('Xfile', expand("%:p:t")) + call assert_equal(3, line('.')) + call assert_equal(2, winnr('$')) + call assert_equal(1, winnr()) + close + exe "normal 3G5\<C-W>\<C-G>}" + wincmd p + call assert_equal(5, winheight(0)) + close + + call delete('Xtags') + call delete('Xfile') + set tags& +endfunc + func Test_cancel_ptjump() + CheckFeature quickfix + set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "word\tfile1\tcmd1", @@ -70,6 +115,8 @@ func Test_duplicate_tagjump() endfunc func Test_tagjump_switchbuf() + CheckFeature quickfix + set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "second\tXfile1\t2", @@ -935,6 +982,11 @@ func Test_tag_multimatch() tag FIRST tnext call assert_equal(2, line('.')) + tlast + tprev + call assert_equal(2, line('.')) + tNext + call assert_equal(1, line('.')) set ignorecase& call delete('Xtags') @@ -1077,6 +1129,217 @@ Type number and <Enter> (q or empty cancels): %bwipe endfunc +" Test for :isearch, :ilist, :ijump and :isplit commands +" Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands +func Test_inc_search() + new + call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo']) + call cursor(3, 1) + + " Test for [i and ]i + call assert_equal('1:foo', execute('normal [i')) + call assert_equal('2:foo', execute('normal 2[i')) + call assert_fails('normal 3[i', 'E387:') + call assert_equal('3:foo', execute('normal ]i')) + call assert_equal('4:foo', execute('normal 2]i')) + call assert_fails('normal 3]i', 'E389:') + + " Test for :isearch + call assert_equal('1:foo', execute('isearch foo')) + call assert_equal('3:foo', execute('isearch 4 /foo/')) + call assert_fails('isearch 3 foo', 'E387:') + call assert_equal('3:foo', execute('+1,$isearch foo')) + call assert_fails('1,.-1isearch 3 foo', 'E389:') + call assert_fails('isearch bar', 'E389:') + call assert_fails('isearch /foo/3', 'E488:') + + " Test for [I and ]I + call assert_equal([ + \ ' 1: 1 1:foo', + \ ' 2: 2 2:foo', + \ ' 3: 3 foo', + \ ' 4: 4 3:foo', + \ ' 5: 5 4:foo'], split(execute('normal [I'), "\n")) + call assert_equal([ + \ ' 1: 4 3:foo', + \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n")) + + " Test for :ilist + call assert_equal([ + \ ' 1: 1 1:foo', + \ ' 2: 2 2:foo', + \ ' 3: 3 foo', + \ ' 4: 4 3:foo', + \ ' 5: 5 4:foo'], split(execute('ilist foo'), "\n")) + call assert_equal([ + \ ' 1: 4 3:foo', + \ ' 2: 5 4:foo'], split(execute('+1,$ilist /foo/'), "\n")) + call assert_fails('ilist bar', 'E389:') + + " Test for [ CTRL-I and ] CTRL-I + exe "normal [\t" + call assert_equal([1, 3], [line('.'), col('.')]) + exe "normal 2j4[\t" + call assert_equal([4, 3], [line('.'), col('.')]) + call assert_fails("normal k3[\t", 'E387:') + call assert_fails("normal 6[\t", 'E389:') + exe "normal ]\t" + call assert_equal([4, 3], [line('.'), col('.')]) + exe "normal k2]\t" + call assert_equal([5, 3], [line('.'), col('.')]) + call assert_fails("normal 2k3]\t", 'E389:') + + " Test for :ijump + call cursor(3, 1) + ijump foo + call assert_equal([1, 3], [line('.'), col('.')]) + call cursor(3, 1) + ijump 4 /foo/ + call assert_equal([4, 3], [line('.'), col('.')]) + call cursor(3, 1) + call assert_fails('ijump 3 foo', 'E387:') + +,$ijump 2 foo + call assert_equal([5, 3], [line('.'), col('.')]) + call assert_fails('ijump bar', 'E389:') + + " Test for CTRL-W i + call cursor(3, 1) + wincmd i + call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')]) + close + 5wincmd i + call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('3wincmd i', 'E387:') + call assert_fails('6wincmd i', 'E389:') + + " Test for :isplit + isplit foo + call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')]) + close + isplit 5 /foo/ + call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('isplit 3 foo', 'E387:') + call assert_fails('isplit 6 foo', 'E389:') + call assert_fails('isplit bar', 'E389:') + + close! +endfunc + +" this was using a line from ml_get() freed by the regexp +func Test_isearch_copy_line() + new + norm o + norm 0 + 0norm o + sil! norm bc0 + sil! isearch \%') + bwipe! +endfunc + +" Test for :dsearch, :dlist, :djump and :dsplit commands +" Test for [d, ]d, [D, ]D, [ CTRL-D, ] CTRL-D and CTRL-W d commands +func Test_macro_search() + new + call setline(1, ['#define FOO 1', '#define FOO 2', '#define FOO 3', + \ '#define FOO 4', '#define FOO 5']) + call cursor(3, 9) + + " Test for [d and ]d + call assert_equal('#define FOO 1', execute('normal [d')) + call assert_equal('#define FOO 2', execute('normal 2[d')) + call assert_fails('normal 3[d', 'E387:') + call assert_equal('#define FOO 4', execute('normal ]d')) + call assert_equal('#define FOO 5', execute('normal 2]d')) + call assert_fails('normal 3]d', 'E388:') + + " Test for :dsearch + call assert_equal('#define FOO 1', execute('dsearch FOO')) + call assert_equal('#define FOO 5', execute('dsearch 5 /FOO/')) + call assert_fails('dsearch 3 FOO', 'E387:') + call assert_equal('#define FOO 4', execute('+1,$dsearch FOO')) + call assert_fails('1,.-1dsearch 3 FOO', 'E388:') + call assert_fails('dsearch BAR', 'E388:') + + " Test for [D and ]D + call assert_equal([ + \ ' 1: 1 #define FOO 1', + \ ' 2: 2 #define FOO 2', + \ ' 3: 3 #define FOO 3', + \ ' 4: 4 #define FOO 4', + \ ' 5: 5 #define FOO 5'], split(execute('normal [D'), "\n")) + call assert_equal([ + \ ' 1: 4 #define FOO 4', + \ ' 2: 5 #define FOO 5'], split(execute('normal ]D'), "\n")) + + " Test for :dlist + call assert_equal([ + \ ' 1: 1 #define FOO 1', + \ ' 2: 2 #define FOO 2', + \ ' 3: 3 #define FOO 3', + \ ' 4: 4 #define FOO 4', + \ ' 5: 5 #define FOO 5'], split(execute('dlist FOO'), "\n")) + call assert_equal([ + \ ' 1: 4 #define FOO 4', + \ ' 2: 5 #define FOO 5'], split(execute('+1,$dlist /FOO/'), "\n")) + call assert_fails('dlist BAR', 'E388:') + + " Test for [ CTRL-D and ] CTRL-D + exe "normal [\<C-D>" + call assert_equal([1, 9], [line('.'), col('.')]) + exe "normal 2j4[\<C-D>" + call assert_equal([4, 9], [line('.'), col('.')]) + call assert_fails("normal k3[\<C-D>", 'E387:') + call assert_fails("normal 6[\<C-D>", 'E388:') + exe "normal ]\<C-D>" + call assert_equal([4, 9], [line('.'), col('.')]) + exe "normal k2]\<C-D>" + call assert_equal([5, 9], [line('.'), col('.')]) + call assert_fails("normal 2k3]\<C-D>", 'E388:') + + " Test for :djump + call cursor(3, 9) + djump FOO + call assert_equal([1, 9], [line('.'), col('.')]) + call cursor(3, 9) + djump 4 /FOO/ + call assert_equal([4, 9], [line('.'), col('.')]) + call cursor(3, 9) + call assert_fails('djump 3 FOO', 'E387:') + +,$djump 2 FOO + call assert_equal([5, 9], [line('.'), col('.')]) + call assert_fails('djump BAR', 'E388:') + + " Test for CTRL-W d + call cursor(3, 9) + wincmd d + call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')]) + close + 5wincmd d + call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('3wincmd d', 'E387:') + call assert_fails('6wincmd d', 'E388:') + new + call assert_fails("normal \<C-W>d", 'E349:') + call assert_fails("normal \<C-W>\<C-D>", 'E349:') + close + + " Test for :dsplit + dsplit FOO + call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')]) + close + dsplit 5 /FOO/ + call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('dsplit 3 FOO', 'E387:') + call assert_fails('dsplit 6 FOO', 'E388:') + call assert_fails('dsplit BAR', 'E388:') + + close! +endfunc + func Test_define_search() " this was accessing freed memory new @@ -1092,6 +1355,37 @@ func Test_define_search() bwipe! endfunc +" Test for [*, [/, ]* and ]/ +func Test_comment_search() + new + call setline(1, ['', '/*', ' *', ' *', ' */']) + normal! 4gg[/ + call assert_equal([2, 1], [line('.'), col('.')]) + normal! 3gg[* + call assert_equal([2, 1], [line('.'), col('.')]) + normal! 3gg]/ + call assert_equal([5, 3], [line('.'), col('.')]) + normal! 3gg]* + call assert_equal([5, 3], [line('.'), col('.')]) + %d + call setline(1, ['', '/*', ' *', ' *']) + call assert_beeps('normal! 3gg]/') + %d + call setline(1, ['', ' *', ' *', ' */']) + call assert_beeps('normal! 4gg[/') + %d + call setline(1, ' /* comment */') + normal! 15|[/ + call assert_equal(9, col('.')) + normal! 15|]/ + call assert_equal(21, col('.')) + call setline(1, ' comment */') + call assert_beeps('normal! 15|[/') + call setline(1, ' /* comment') + call assert_beeps('normal! 15|]/') + close! +endfunc + " Test for the 'taglength' option func Test_tag_length() set tags=Xtags diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index e11815ff33..be46773256 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -1,5 +1,7 @@ " test taglist(), tagfiles() functions and :tags command +source view_util.vim + func Test_taglist() call writefile([ \ "FFoo\tXfoo\t1", @@ -222,3 +224,21 @@ func Test_format_error() set tags& call delete('Xtags') endfunc + +" Test for :tag command completion with 'wildoptions' set to 'tagfile' +func Test_tag_complete_wildoptions() + call writefile(["foo\ta.c\t10;\"\tf", "bar\tb.c\t20;\"\td"], 'Xtags') + set tags=Xtags + set wildoptions=tagfile + + call feedkeys(":tag \<C-D>\<C-R>=Screenline(&lines - 1)\<CR> : " + \ .. "\<C-R>=Screenline(&lines - 2)\<CR>\<C-B>\"\<CR>", 'xt') + + call assert_equal('"tag bar d b.c : foo f a.c', @:) + + call delete('Xtags') + set wildoptions& + set tags& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_termcodes.vim b/src/nvim/testdir/test_termcodes.vim index f3b10a922e..c0712aa892 100644 --- a/src/nvim/testdir/test_termcodes.vim +++ b/src/nvim/testdir/test_termcodes.vim @@ -1,4 +1,38 @@ +" Test for terminal keycodes that doesn't have termcap entries +func Test_special_term_keycodes() + new + " Test for <xHome>, <S-xHome> and <C-xHome> + " send <K_SPECIAL> <KS_EXTRA> keycode + call feedkeys("i\<C-K>\x80\xfd\x3f\n", 'xt') + " send <K_SPECIAL> <KS_MODIFIER> bitmap <K_SPECIAL> <KS_EXTRA> keycode + call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3f\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3f\n", 'xt') + " Test for <xEnd>, <S-xEnd> and <C-xEnd> + call feedkeys("i\<C-K>\x80\xfd\x3d\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3d\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3d\n", 'xt') + " Test for <zHome>, <S-zHome> and <C-zHome> + call feedkeys("i\<C-K>\x80\xfd\x40\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x40\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x40\n", 'xt') + " Test for <zEnd>, <S-zEnd> and <C-zEnd> + call feedkeys("i\<C-K>\x80\xfd\x3e\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3e\n", 'xt') + call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3e\n", 'xt') + " Test for <xUp>, <xDown>, <xLeft> and <xRight> + call feedkeys("i\<C-K>\x80\xfd\x41\n", 'xt') + call feedkeys("i\<C-K>\x80\xfd\x42\n", 'xt') + call feedkeys("i\<C-K>\x80\xfd\x43\n", 'xt') + call feedkeys("i\<C-K>\x80\xfd\x44\n", 'xt') + call assert_equal(['<Home>', '<S-Home>', '<C-Home>', + \ '<End>', '<S-End>', '<C-End>', + \ '<Home>', '<S-Home>', '<C-Home>', + \ '<End>', '<S-End>', '<C-End>', + \ '<Up>', '<Down>', '<Left>', '<Right>', ''], getline(1, '$')) + bw! +endfunc + func Test_simplify_ctrl_at() " feeding unsimplified CTRL-@ should still trigger i_CTRL-@ call feedkeys("ifoo\<Esc>A\<*C-@>x", 'xt') diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 748af199b2..970f5ae0d0 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -1060,7 +1060,7 @@ func Test_tw_2_fo_tm_replace() endfunc " Test for 'matchpairs' with multibyte chars -func Test_mps() +func Test_mps_multibyte() new let t =<< trim END { @@ -1084,6 +1084,30 @@ func Test_mps() bwipe! endfunc +" Test for 'matchpairs' in latin1 encoding +func Test_mps_latin1() + new + let save_enc = &encoding + " set encoding=latin1 + call setline(1, 'abc(def)ghi') + normal % + call assert_equal(8, col('.')) + normal % + call assert_equal(4, col('.')) + call cursor(1, 6) + normal [( + call assert_equal(4, col('.')) + normal % + call assert_equal(8, col('.')) + call cursor(1, 6) + normal ]) + call assert_equal(8, col('.')) + normal % + call assert_equal(4, col('.')) + let &encoding = save_enc + close! +endfunc + func Test_empty_matchpairs() split set matchpairs= showmatch @@ -1137,8 +1161,103 @@ func Test_whichwrap_multi_byte() bwipe! endfunc -func Test_substitute() - call assert_equal('a1aï¼’a3a', substitute('123', '\zs', 'a', 'g')) +" Test for automatically adding comment leaders in insert mode +func Test_threepiece_comment() + new + setlocal expandtab + call setline(1, ["\t/*"]) + setlocal formatoptions=croql + call cursor(1, 3) + call feedkeys("A\<cr>\<cr>/", 'tnix') + call assert_equal(["\t/*", " *", " */"], getline(1, '$')) + + " If a comment ends in a single line, then don't add it in the next line + %d + call setline(1, '/* line1 */') + call feedkeys("A\<CR>next line", 'xt') + call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) + + %d + " Copy the trailing indentation from the leader comment to a new line + setlocal autoindent noexpandtab + call feedkeys("a\t/*\tone\ntwo\n/", 'xt') + call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) + close! +endfunc + +" Test for the 'f' flag in 'comments' (only the first line has the comment +" string) +func Test_firstline_comment() + new + setlocal comments=f:- fo+=ro + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) + %d + setlocal comments=:- + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) + %bw! +endfunc + +" Test for the 'r' flag in 'comments' (right align comment) +func Test_comment_rightalign() + new + setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro + exe "normal i=\<C-C>o\t /***\nD\n/" + exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" + let expected =<< trim END + = + A + /*** + ** B + ** C + ** D + ** E + ** F + ******/ + G + END + call assert_equal(expected, getline(1, '$')) + %bw! +endfunc + +" Test for the 'b' flag in 'comments' +func Test_comment_blank() + new + setlocal comments=b:* fo+=ro + exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" + let expected =<< trim END + A + *B + * C + * D + * E + * F + *G + H + END + call assert_equal(expected, getline(1, '$')) + %bw! +endfunc + +" Test for the 'n' flag in comments +func Test_comment_nested() + new + setlocal comments=n:> fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" + exe "normal 5GOE\<C-C>6GoG" + let expected =<< trim END + > A + > B + > C + > D + >>>> E + >>>> F + >>>> G + >>>> H + END + call assert_equal(expected, getline(1, '$')) + %bw! endfunc " Test for 'a' and 'w' flags in 'formatoptions' diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 81f2fea026..eeb2946a8b 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -1,8 +1,7 @@ " Test for textobjects -if !has('textobjects') - finish -endif +source check.vim +CheckFeature textobjects func CpoM(line, useM, expected) new @@ -234,6 +233,10 @@ func Test_empty_html_tag() normal 0f<vitsaaa call assert_equal('aaa', getline(1)) + " selecting a tag block in an non-empty blank line should fail + call setline(1, ' ') + call assert_beeps('normal $vaty') + bwipe! endfunc @@ -368,6 +371,168 @@ func Test_sentence_with_cursor_on_delimiter() %delete _ endfunc +" Test for the paragraph (ap) text object +func Test_paragraph() + new + call setline(1, ['First line.', 'Second line.', 'Third line.']) + call cursor(2, 1) + normal vapy + call assert_equal("First line.\nSecond line.\nThird line.\n", @") + + call cursor(2, 1) + call assert_beeps('normal vapapy') + + call setline(1, ['First line.', 'Second line.', ' ', '']) + call cursor(1, 1) + normal vapy + call assert_equal("First line.\nSecond line.\n \n\n", @") + + call setline(1, ['', '', '', 'First line.', 'Second line.']) + call cursor(2, 1) + normal yap + call assert_equal("\n\n\nFirst line.\nSecond line.\n", @") + call assert_beeps('normal 3yap') + exe "normal \<C-C>" + + %d + call setline(1, [' ', ' ', ' ']) + call cursor(2, 1) + normal Vipy + call assert_equal(" \n \n \n", @") + call cursor(2, 1) + call assert_beeps("normal Vipip") + exe "normal \<C-C>" + + close! +endfunc + +" Tests for text object aw +func Test_textobj_a_word() + new + call append(0, ['foobar,eins,foobar', 'foo,zwei,foo ']) + " diw + norm! 1gg0diw + call assert_equal([',eins,foobar', 'foo,zwei,foo ', ''], getline(1,'$')) + " daw + norm! 2ggEdaw + call assert_equal([',eins,foobar', 'foo,zwei,', ''], getline(1, '$')) + " daw the last word in a line + call setline(1, ['foo bar', 'foo bar', '']) + call cursor(1, 5) + normal daw + call assert_equal('foo', getline(1)) + " aw in visual mode + call cursor(2, 5) + normal! vawx + call assert_equal('foo', getline(2)) + %d + call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "]) + " diW + norm! 2ggwd2iW + call assert_equal(['foo eins foobar', 'foo foo ', ''], getline(1,'$')) + " daW + norm! 1ggd2aW + call assert_equal(['foobar', 'foo foo ', ''], getline(1,'$')) + + %d + call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "]) + " aw in visual line mode switches to characterwise mode + norm! 2gg$Vawd + call assert_equal(['foo eins foobar', 'foo zwei foo'], getline(1,'$')) + norm! 1gg$Viwd + call assert_equal(['foo eins ', 'foo zwei foo'], getline(1,'$')) + + " visually selecting a tab before a word with 'selection' set to 'exclusive' + set selection=exclusive + normal gg3lvlawy + call assert_equal("\teins", @") + " visually selecting a tab before a word with 'selection' set to 'inclusive' + set selection=inclusive + normal gg3lvlawy + call assert_equal("\teins\t", @") + set selection& + + " selecting a word with no non-space characters in a buffer fails + %d + call setline(1, ' ') + call assert_beeps('normal 3lyaw') + + " visually selecting words backwards with no more words to select + call setline(1, 'one two') + call assert_beeps('normal 2lvh2aw') + exe "normal \<C-C>" + call assert_beeps('normal $vh3aw') + exe "normal \<C-C>" + call setline(1, ['', 'one two']) + call assert_beeps('normal 2G2lvh3aw') + exe "normal \<C-C>" + + " selecting words forward with no more words to select + %d + call setline(1, 'one a') + call assert_beeps('normal 0y3aw') + call setline(1, 'one two ') + call assert_beeps('normal 0y3aw') + call assert_beeps('normal 03ly2aw') + + " clean up + bw! +endfunc + +" Test for is and as text objects +func Test_textobj_sentence() + new + call append(0, ['This is a test. With some sentences!', '', + \ 'Even with a question? And one more. And no sentence here']) + " Test for dis - does not remove trailing whitespace + norm! 1gg0dis + call assert_equal([' With some sentences!', '', + \ 'Even with a question? And one more. And no sentence here', ''], + \ getline(1,'$')) + " Test for das - removes leading whitespace + norm! 3ggf?ldas + call assert_equal([' With some sentences!', '', + \ 'Even with a question? And no sentence here', ''], getline(1,'$')) + " when used in visual mode, is made characterwise + norm! 3gg$Visy + call assert_equal('v', visualmode()) + " reset visualmode() + norm! 3ggVy + norm! 3gg$Vasy + call assert_equal('v', visualmode()) + " basic testing for textobjects a< and at + %d + call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' ']) + " a< + norm! 1gg0da< + call assert_equal([' ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + norm! 1pj + call assert_equal([' <div>', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + " at + norm! d2at + call assert_equal([' '], getline(1,'$')) + %d + call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' ']) + " i< + norm! 1gg0di< + call assert_equal(['<> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + norm! 1Pj + call assert_equal(['<div> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + norm! d2it + call assert_equal(['<div></div>',' '], getline(1,'$')) + " basic testing for a[ and i[ text object + %d + call setline(1, [' ', '[', 'one [two]', 'thre', ']']) + norm! 3gg0di[ + call assert_equal([' ', '[', ']'], getline(1,'$')) + call setline(1, [' ', '[', 'one [two]', 'thre', ']']) + norm! 3gg0ftd2a[ + call assert_equal([' '], getline(1,'$')) + + " clean up + bw! +endfunc + " Test for quote (', " and `) textobjects func Test_textobj_quote() new diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 09eed4e10d..56a5ec96af 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -1,13 +1,16 @@ " Test for timers -if !has('timers') - finish -endif +source check.vim +CheckFeature timers source shared.vim source term_util.vim source load.vim +func SetUp() + call timer_stopall() +endfunc + func MyHandler(timer) let g:val += 1 endfunc @@ -16,7 +19,7 @@ func MyHandlerWithLists(lists, timer) let x = string(a:lists) endfunc -func Test_oneshot() +func Test_timer_oneshot() let g:val = 0 let timer = timer_start(50, 'MyHandler') let slept = WaitFor('g:val == 1') @@ -28,7 +31,7 @@ func Test_oneshot() endif endfunc -func Test_repeat_three() +func Test_timer_repeat_three() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': 3}) let slept = WaitFor('g:val == 3') @@ -40,8 +43,7 @@ func Test_repeat_three() endif endfunc -func Test_repeat_many() - call timer_stopall() +func Test_timer_repeat_many() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': -1}) if has('mac') @@ -52,7 +54,7 @@ func Test_repeat_many() call assert_inrange((has('mac') ? 1 : 2), LoadAdjust(5), g:val) endfunc -func Test_with_partial_callback() +func Test_timer_with_partial_callback() let g:val = 0 let meow = {'one': 1} function meow.bite(...) @@ -69,13 +71,13 @@ func Test_with_partial_callback() endif endfunc -func Test_retain_partial() +func Test_timer_retain_partial() call timer_start(50, function('MyHandlerWithLists', [['a']])) - call garbagecollect() + call test_garbagecollect_now() sleep 100m endfunc -func Test_info() +func Test_timer_info() let id = timer_start(1000, 'MyHandler') let info = id->timer_info() call assert_equal(id, info[0]['id']) @@ -92,10 +94,11 @@ func Test_info() call timer_stop(id) call assert_equal([], timer_info(id)) + + call assert_fails('call timer_info("abc")', 'E39:') endfunc -func Test_stopall() - call timer_stopall() +func Test_timer_stopall() let id1 = timer_start(1000, 'MyHandler') let id2 = timer_start(2000, 'MyHandler') let info = timer_info() @@ -106,7 +109,7 @@ func Test_stopall() call assert_equal(0, len(info)) endfunc -func Test_paused() +func Test_timer_paused() let g:val = 0 let id = timer_start(50, 'MyHandler') @@ -130,6 +133,8 @@ func Test_paused() else call assert_inrange(0, 10, slept) endif + + call assert_fails('call timer_pause("abc", 1)', 'E39:') endfunc func StopMyself(timer) @@ -139,7 +144,7 @@ func StopMyself(timer) endif endfunc -func Test_delete_myself() +func Test_timer_delete_myself() let g:called = 0 let t = timer_start(10, 'StopMyself', {'repeat': -1}) call WaitForAssert({-> assert_equal(2, g:called)}) @@ -151,33 +156,45 @@ func StopTimer1(timer) let g:timer2 = 10->timer_start('StopTimer2') " avoid maxfuncdepth error call timer_pause(g:timer1, 1) - sleep 40m + sleep 20m endfunc func StopTimer2(timer) call timer_stop(g:timer1) endfunc -func Test_stop_in_callback() +func Test_timer_stop_in_callback() + call assert_equal(0, len(timer_info())) let g:timer1 = timer_start(10, 'StopTimer1') - sleep 40m + let slept = 0 + for i in range(10) + if len(timer_info()) == 0 + break + endif + sleep 10m + let slept += 10 + endfor + " This should take only 30 msec, but on Mac it's often longer + call assert_inrange(0, 50, slept) endfunc func StopTimerAll(timer) call timer_stopall() endfunc -func Test_stop_all_in_callback() - call timer_stopall() - let g:timer1 = timer_start(10, 'StopTimerAll') - let info = timer_info() - call assert_equal(1, len(info)) - if has('mac') - sleep 100m - endif - sleep 40m - let info = timer_info() - call assert_equal(0, len(info)) +func Test_timer_stop_all_in_callback() + call assert_equal(0, len(timer_info())) + call timer_start(10, 'StopTimerAll') + call assert_equal(1, len(timer_info())) + let slept = 0 + for i in range(10) + if len(timer_info()) == 0 + break + endif + sleep 10m + let slept += 10 + endfor + call assert_inrange(0, 30, slept) endfunc func FeedkeysCb(timer) @@ -190,7 +207,7 @@ func InputCb(timer) call Resume() endfunc -func Test_input_in_timer() +func Test_timer_input_in_timer() let g:val = '' call timer_start(10, 'InputCb') call Standby(1000) @@ -212,6 +229,10 @@ func Test_timer_errors() call WaitForAssert({-> assert_equal(3, g:call_count)}) sleep 50m call assert_equal(3, g:call_count) + + call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E475:') + call assert_fails('call timer_start(100, [])', 'E921:') + call assert_fails('call timer_stop("abc")', 'E39:') endfunc func FuncWithCaughtError(timer) @@ -243,7 +264,7 @@ func Interrupt(timer) call nvim_input("\<C-C>") endfunc -func Test_peek_and_get_char() +func Test_timer_peek_and_get_char() if !has('unix') && !has('gui_running') return endif @@ -254,7 +275,7 @@ func Test_peek_and_get_char() eval intr->timer_stop() endfunc -func Test_getchar_zero() +func Test_timer_getchar_zero() if has('win32') && !has('gui_running') " Console: no low-level input return @@ -272,20 +293,20 @@ func Test_getchar_zero() call timer_stop(id) endfunc -func Test_ex_mode() +func Test_timer_ex_mode() " Function with an empty line. func Foo(...) endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal gQsleep 100m\rvi\r" + exe "normal Qsleep 100m\rvi\r" call timer_stop(timer) endfunc -func Test_restore_count() +func Test_timer_restore_count() if !CanRunVimInTerminal() - return + throw 'Skipped: cannot run Vim in a terminal window' endif " Check that v:count is saved and restored, not changed by a timer. call writefile([ @@ -316,9 +337,9 @@ endfunc " Test that the garbage collector isn't triggered if a timer callback invokes " vgetc(). -func Test_nocatch_garbage_collect() - CheckFunction test_garbagecollect_soon - CheckFunction test_override +func Test_timer_nocatch_garbage_collect() + " skipped: Nvim does not support test_garbagecollect_soon(), test_override() + return " 'uptimetime. must be bigger than the timer timeout set ut=200 call test_garbagecollect_soon() @@ -340,7 +361,7 @@ func Test_nocatch_garbage_collect() delfunc FeedChar endfunc -func Test_error_in_timer_callback() +func Test_timer_error_in_timer_callback() if !has('terminal') || (has('win32') && has('gui_running')) throw 'Skipped: cannot run Vim in a terminal window' endif @@ -375,6 +396,15 @@ func Test_error_in_timer_callback() exe buf .. 'bwipe!' endfunc +" Test for garbage collection when a timer is still running +func Test_timer_garbage_collect() + let timer = timer_start(1000, function('MyHandler'), {'repeat' : 10}) + call test_garbagecollect_now() + let l = timer_info(timer) + call assert_equal(function('MyHandler'), l[0].callback) + call timer_stop(timer) +endfunc + func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 205ed095ea..598402fafe 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -1996,6 +1996,47 @@ func Test_reload_in_try_catch() call delete('Xreload') endfunc +" Test for errors with :catch, :throw, :finally {{{1 +func Test_try_catch_errors() + call assert_fails('throw |', 'E471:') + call assert_fails("throw \n ", 'E471:') + call assert_fails('catch abc', 'E603:') + call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:') + call assert_fails('finally', 'E606:') + call assert_fails('try | finally | finally | endtry', 'E607:') + " v8.2.3486 has been ported, but v8.2.1183 hasn't, so E170 appears here. + " call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') + call assert_fails('try | for i in range(5) | endif | endtry', 'E170:') + call assert_fails('try | while v:true | endtry', 'E170:') + call assert_fails('try | if v:true | endtry', 'E171:') +endfunc + +" Test for verbose messages with :try :catch, and :finally {{{1 +func Test_try_catch_verbose() + " This test works only when the language is English + if v:lang != "C" && v:lang !~ '^[Ee]n' + return + endif + + set verbose=14 + redir => msg + try + echo i + catch /E121:/ + finally + endtry + redir END + let expected = [ + \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', + \ '', + \ 'Exception caught: Vim(echo):E121: Undefined variable: i', + \ '', + \ 'Exception finished: Vim(echo):E121: Undefined variable: i' + \ ] + call assert_equal(expected, split(msg, "\n")) + set verbose& +endfunc + " Test for using throw in a called function with following error {{{1 func Test_user_command_throw_in_function_call() let lines =<< trim END diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 848860649e..a9ec405aa4 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -3,8 +3,6 @@ " undo-able pieces. Do that by setting 'undolevels'. " Also tests :earlier and :later. -source check.vim - func Test_undotree() new @@ -137,8 +135,7 @@ func BackOne(expected) endfunc func Test_undo_del_chars() - CheckFunction test_settime - + throw 'Skipped: Nvim does not support test_settime()' " Setup a buffer without creating undo entries new set ul=-1 @@ -299,6 +296,8 @@ func Test_undo_write() close! call delete('Xtest') bwipe! Xtest + + call assert_fails('earlier xyz', 'E475:') endfunc func Test_insert_expr() @@ -332,8 +331,9 @@ func Test_insert_expr() endfunc func Test_undofile_earlier() - CheckFunction test_settime - + throw 'Skipped: Nvim does not support test_settime()' + " Issue #1254 + " create undofile with timestamps older than Vim startup time. let t0 = localtime() - 43200 call test_settime(t0) new Xfile @@ -366,7 +366,7 @@ func Test_wundo_errors() bwipe! endfunc -" Check that reading a truncted undo file doesn't hang. +" Check that reading a truncated undo file doesn't hang. func Test_undofile_truncated() new call setline(1, 'hello') @@ -429,6 +429,59 @@ func Test_cmd_in_reg_undo() let @a = '' endfunc +" This used to cause an illegal memory access +func Test_undo_append() + new + call feedkeys("axx\<Esc>v", 'xt') + undo + norm o + quit +endfunc + +func Test_undo_0() + new + set ul=100 + normal i1 + undo + normal i2 + undo + normal i3 + + undo 0 + let d = undotree() + call assert_equal('', getline(1)) + call assert_equal(0, d.seq_cur) + + redo + let d = undotree() + call assert_equal('3', getline(1)) + call assert_equal(3, d.seq_cur) + + undo 2 + undo 0 + let d = undotree() + call assert_equal('', getline(1)) + call assert_equal(0, d.seq_cur) + + redo + let d = undotree() + call assert_equal('2', getline(1)) + call assert_equal(2, d.seq_cur) + + undo 1 + undo 0 + let d = undotree() + call assert_equal('', getline(1)) + call assert_equal(0, d.seq_cur) + + redo + let d = undotree() + call assert_equal('1', getline(1)) + call assert_equal(1, d.seq_cur) + + bwipe! +endfunc + " undo or redo are noop if there is nothing to undo or redo func Test_undo_redo_noop() new @@ -454,15 +507,6 @@ func Test_redo_empty_line() bwipe! endfunc -" This used to cause an illegal memory access -func Test_undo_append() - new - call feedkeys("axx\<Esc>v", 'xt') - undo - norm o - quit -endfunc - funct Test_undofile() " Test undofile() without setting 'undodir'. if has('persistent_undo') @@ -504,50 +548,6 @@ funct Test_undofile() set undodir& endfunc -func Test_undo_0() - new - set ul=100 - normal i1 - undo - normal i2 - undo - normal i3 - - undo 0 - let d = undotree() - call assert_equal('', getline(1)) - call assert_equal(0, d.seq_cur) - - redo - let d = undotree() - call assert_equal('3', getline(1)) - call assert_equal(3, d.seq_cur) - - undo 2 - undo 0 - let d = undotree() - call assert_equal('', getline(1)) - call assert_equal(0, d.seq_cur) - - redo - let d = undotree() - call assert_equal('2', getline(1)) - call assert_equal(2, d.seq_cur) - - undo 1 - undo 0 - let d = undotree() - call assert_equal('', getline(1)) - call assert_equal(0, d.seq_cur) - - redo - let d = undotree() - call assert_equal('1', getline(1)) - call assert_equal(1, d.seq_cur) - - bwipe! -endfunc - " Tests for the undo file " Explicitly break changes up in undo-able pieces by setting 'undolevels'. func Test_undofile_2() @@ -579,7 +579,7 @@ func Test_undofile_2() " add 10 lines, delete 6 lines, undo 3 set undofile - call setbufline(0, 1, ['one', 'two', 'three', 'four', 'five', 'six', + call setbufline('%', 1, ['one', 'two', 'three', 'four', 'five', 'six', \ 'seven', 'eight', 'nine', 'ten']) set undolevels=100 normal 3Gdd @@ -733,6 +733,29 @@ func Test_undofile_cryptmethod_blowfish2() set undofile& undolevels& cryptmethod& endfunc +" Test for redoing with incrementing numbered registers +func Test_redo_repeat_numbered_register() + new + for [i, v] in [[1, 'one'], [2, 'two'], [3, 'three'], + \ [4, 'four'], [5, 'five'], [6, 'six'], + \ [7, 'seven'], [8, 'eight'], [9, 'nine']] + exe 'let @' .. i .. '="' .. v .. '\n"' + endfor + call feedkeys('"1p.........', 'xt') + call assert_equal(['', 'one', 'two', 'three', 'four', 'five', 'six', + \ 'seven', 'eight', 'nine', 'nine'], getline(1, '$')) + bwipe! +endfunc + +" Test for redo in insert mode using CTRL-O with multibyte characters +func Test_redo_multibyte_in_insert_mode() + new + call feedkeys("a\<C-K>ft", 'xt') + call feedkeys("uiHe\<C-O>.llo", 'xt') + call assert_equal("He\ufb05llo", getline(1)) + bwipe! +endfunc + func Test_undo_mark() new " The undo is applied to the only line. diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 596b8b53aa..e37fe43b22 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -56,7 +56,10 @@ function Test_cmdmods() call assert_equal('lockmarks', g:mods) loc MyCmd call assert_equal('lockmarks', g:mods) - " noautocmd MyCmd + noautocmd MyCmd + call assert_equal('noautocmd', g:mods) + noa MyCmd + call assert_equal('noautocmd', g:mods) noswapfile MyCmd call assert_equal('noswapfile', g:mods) nos MyCmd @@ -70,29 +73,43 @@ function Test_cmdmods() call assert_equal('silent', g:mods) sil MyCmd call assert_equal('silent', g:mods) + silent! MyCmd + call assert_equal('silent!', g:mods) + sil! MyCmd + call assert_equal('silent!', g:mods) tab MyCmd call assert_equal('tab', g:mods) topleft MyCmd call assert_equal('topleft', g:mods) to MyCmd call assert_equal('topleft', g:mods) - " unsilent MyCmd + unsilent MyCmd + call assert_equal('unsilent', g:mods) + uns MyCmd + call assert_equal('unsilent', g:mods) verbose MyCmd call assert_equal('verbose', g:mods) verb MyCmd call assert_equal('verbose', g:mods) + 0verbose MyCmd + call assert_equal('0verbose', g:mods) + 3verbose MyCmd + call assert_equal('3verbose', g:mods) + 999verbose MyCmd + call assert_equal('999verbose', g:mods) vertical MyCmd call assert_equal('vertical', g:mods) vert MyCmd call assert_equal('vertical', g:mods) aboveleft belowright botright browse confirm hide keepalt keepjumps - \ keepmarks keeppatterns lockmarks noswapfile silent tab - \ topleft verbose vertical MyCmd + \ keepmarks keeppatterns lockmarks noautocmd noswapfile silent + \ tab topleft unsilent verbose vertical MyCmd call assert_equal('browse confirm hide keepalt keepjumps ' . - \ 'keepmarks keeppatterns lockmarks noswapfile silent ' . - \ 'verbose aboveleft belowright botright tab topleft vertical', g:mods) + \ 'keepmarks keeppatterns lockmarks noswapfile unsilent noautocmd ' . + \ 'silent verbose aboveleft belowright botright tab topleft vertical', + \ g:mods) let g:mods = '' command! -nargs=* MyQCmd let g:mods .= '<q-mods> ' @@ -296,7 +313,7 @@ func CustomComplete(A, L, P) endfunc func CustomCompleteList(A, L, P) - return [ "Monday", "Tuesday", "Wednesday" ] + return [ "Monday", "Tuesday", "Wednesday", {}] endfunc func Test_CmdCompletion() diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 6af199a512..68fe15ff93 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -1,8 +1,7 @@ " Test for variable tabstops -if !has("vartabs") - finish -endif +source check.vim +CheckFeature vartabs source view_util.vim diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index f93eb6e274..1323288676 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1771,6 +1771,125 @@ func Test_function_defined_line() call delete('Xtest.vim') endfunc +" Test for missing :endif, :endfor, :endwhile and :endtry {{{1 +func Test_missing_end() + call writefile(['if 2 > 1', 'echo ">"'], 'Xscript') + call assert_fails('source Xscript', 'E171:') + call writefile(['for i in range(5)', 'echo i'], 'Xscript') + call assert_fails('source Xscript', 'E170:') + call writefile(['while v:true', 'echo "."'], 'Xscript') + call assert_fails('source Xscript', 'E170:') + call writefile(['try', 'echo "."'], 'Xscript') + call assert_fails('source Xscript', 'E600:') + call delete('Xscript') + + " Using endfor with :while + let caught_e732 = 0 + try + while v:true + endfor + catch /E732:/ + let caught_e732 = 1 + endtry + call assert_equal(1, caught_e732) + + " Using endwhile with :for + let caught_e733 = 0 + try + for i in range(1) + endwhile + catch /E733:/ + let caught_e733 = 1 + endtry + call assert_equal(1, caught_e733) + + " Missing 'in' in a :for statement + call assert_fails('for i range(1) | endfor', 'E690:') +endfunc + +" Test for deep nesting of if/for/while/try statements {{{1 +func Test_deep_nest() + if !CanRunVimInTerminal() + throw 'Skipped: cannot run vim in terminal' + endif + + let lines =<< trim [SCRIPT] + " Deep nesting of if ... endif + func Test1() + let @a = join(repeat(['if v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endif'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of for ... endfor + func Test2() + let @a = join(repeat(['for i in [1]'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endfor'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of while ... endwhile + func Test3() + let @a = join(repeat(['while v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endwhile'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of try ... endtry + func Test4() + let @a = join(repeat(['try'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endtry'], 51), "\n") + @a + let @a = '' + endfunc + [SCRIPT] + call writefile(lines, 'Xscript') + + let buf = RunVimInTerminal('-S Xscript', {'rows': 6}) + + " Deep nesting of if ... endif + call term_sendkeys(buf, ":call Test1()\n") + call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))}) + + " Deep nesting of for ... endfor + call term_sendkeys(buf, ":call Test2()\n") + call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) + + " Deep nesting of while ... endwhile + call term_sendkeys(buf, ":call Test3()\n") + call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) + + " Deep nesting of try ... endtry + call term_sendkeys(buf, ":call Test4()\n") + call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))}) + + "let l = '' + "for i in range(1, 6) + " let l ..= term_getline(buf, i) . "\n" + "endfor + "call assert_report(l) + + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + +" Test for <sfile>, <slnum> in a function {{{1 +func Test_sfile_in_function() + func Xfunc() + call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) + call assert_equal('2', expand('<slnum>')) + endfunc + call Xfunc() + delfunc Xfunc +endfunc + func Test_for_over_string() let res = '' for c in 'aécÌ€d' diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 250b896532..522ca17675 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -301,6 +301,9 @@ func Test_replace_on_tab() call append(0, "'r'\t") normal gg^5lrxAy call assert_equal("'r' x y", getline(1)) + call setline(1, 'aaaaaaaaaaaa') + exe "normal! gg2lgR\<Tab>" + call assert_equal("aa\taaaa", getline(1)) bwipe! set virtualedit= endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 41c29c5bb0..f9ac0e0884 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -284,6 +284,15 @@ func Test_virtual_replace2() call assert_equal(['abcd', \ 'efgh', \ 'ijkl'], getline(1, '$')) + + " Test for truncating spaces in a newly added line using 'autoindent' if + " characters are not added to that line. + %d_ + call setline(1, [' app', ' bee', ' cat']) + setlocal autoindent + exe "normal gg$gRt\n\nr" + call assert_equal([' apt', '', ' rat'], getline(1, '$')) + " clean up %d_ set bs&vim @@ -700,7 +709,6 @@ func Test_linewise_select_mode() exe "normal GkkgH\<Del>" call assert_equal(['', 'b', 'c'], getline(1, '$')) - " linewise select mode: delete middle two lines call deletebufline('', 1, '$') call append('$', ['a', 'b', 'c']) @@ -722,6 +730,15 @@ func Test_linewise_select_mode() bwipe! endfunc +" Test for blockwise select mode (g CTRL-H) +func Test_blockwise_select_mode() + new + call setline(1, ['foo', 'bar']) + call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') + call assert_equal(['mmo', 'mmr'], getline(1, '$')) + close! +endfunc + func Test_visual_mode_put() new @@ -1105,6 +1122,96 @@ func Test_block_insert_replace_tabs() bwipe! endfunc +" Test for * register in : +func Test_star_register() + call assert_fails('*bfirst', 'E16:') + new + call setline(1, ['foo', 'bar', 'baz', 'qux']) + exe "normal jVj\<ESC>" + *yank r + call assert_equal("bar\nbaz\n", @r) + + delmarks < > + call assert_fails('*yank', 'E20:') + close! +endfunc + +" Test for using visual mode maps in select mode +func Test_select_mode_map() + new + vmap <buffer> <F2> 3l + call setline(1, 'Test line') + call feedkeys("gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + + vmap <buffer> <F2> ygV + call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') + call assert_equal('abc line', getline(1)) + + vmap <buffer> <F2> :<C-U>let v=100<CR> + call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') + call assert_equal('foo line', getline(1)) + + " reselect the select mode using gv from a visual mode map + vmap <buffer> <F2> gv + set selectmode=cmd + call feedkeys("0gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + set selectmode& + + close! +endfunc + +" Test for changing text in visual mode with 'exclusive' selection +func Test_exclusive_selection() + new + call setline(1, ['one', 'two']) + set selection=exclusive + call feedkeys("vwcabc", 'xt') + call assert_equal('abctwo', getline(1)) + call setline(1, ["\tone"]) + set virtualedit=all + call feedkeys('0v2lcl', 'xt') + call assert_equal('l one', getline(1)) + set virtualedit& + set selection& + close! +endfunc + +" Test for starting visual mode with a count. +" This test should be run without any previous visual modes. So this should be +" run as a first test. +func Test_AAA_start_visual_mode_with_count() + new + call setline(1, ['aaaaaaa', 'aaaaaaa', 'aaaaaaa', 'aaaaaaa']) + normal! gg2Vy + call assert_equal("aaaaaaa\naaaaaaa\n", @") + close! +endfunc + +" Test for visually selecting an inner block (iB) +func Test_visual_inner_block() + new + call setline(1, ['one', '{', 'two', '{', 'three', '}', 'four', '}', 'five']) + call cursor(5, 1) + " visually select all the lines in the block and then execute iB + call feedkeys("ViB\<C-C>", 'xt') + call assert_equal([0, 5, 1, 0], getpos("'<")) + call assert_equal([0, 5, 6, 0], getpos("'>")) + " visually select two inner blocks + call feedkeys("ViBiB\<C-C>", 'xt') + call assert_equal([0, 3, 1, 0], getpos("'<")) + call assert_equal([0, 7, 5, 0], getpos("'>")) + " try to select non-existing inner block + call cursor(5, 1) + call assert_beeps('normal ViBiBiB') + " try to select a unclosed inner block + 8,9d + call cursor(5, 1) + call assert_beeps('normal ViBiB') + close! +endfunc + func Test_visual_put_in_block() new call setline(1, ['xxxx', 'y∞yy', 'zzzz']) @@ -1431,5 +1538,17 @@ func Test_visual_paste_clipboard() bwipe! endfunc +func Test_visual_area_adjusted_when_hiding() + " The Visual area ended after the end of the line after :hide + call setline(1, 'xxx') + vsplit Xfile + call setline(1, 'xxxxxxxx') + norm! $o + hid + norm! zW + bwipe! + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 0fe5fc59eb..7decac2c36 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -34,7 +34,16 @@ func Test_window_cmd_cmdwin_with_vsp() set ls&vim endfunc -function Test_window_cmd_wincmd_gf() +" Test for jumping to windows +func Test_window_jump() + new + " jumping to a window with a count greater than the max windows + exe "normal 4\<C-W>w" + call assert_equal(2, winnr()) + only +endfunc + +func Test_window_cmd_wincmd_gf() let fname = 'test_gf.txt' let swp_fname = '.' . fname . '.swp' call writefile([], fname) @@ -172,6 +181,35 @@ func Test_window_split_edit_bufnr() %bw! endfunc +func Test_window_split_no_room() + " N horizontal windows need >= 2*N + 1 lines: + " - 1 line + 1 status line in each window + " - 1 Ex command line + " + " 2*N + 1 <= &lines + " N <= (lines - 1)/2 + " + " Beyond that number of windows, E36: Not enough room is expected. + let hor_win_count = (&lines - 1)/2 + let hor_split_count = hor_win_count - 1 + for s in range(1, hor_split_count) | split | endfor + call assert_fails('split', 'E36:') + + " N vertical windows need >= 2*(N - 1) + 1 columns: + " - 1 column + 1 separator for each window (except last window) + " - 1 column for the last window which does not have separator + " + " 2*(N - 1) + 1 <= &columns + " 2*N - 1 <= &columns + " N <= (&columns + 1)/2 + let ver_win_count = (&columns + 1)/2 + let ver_split_count = ver_win_count - 1 + for s in range(1, ver_split_count) | vsplit | endfor + call assert_fails('vsplit', 'E36:') + + %bw! +endfunc + func Test_window_exchange() e Xa @@ -612,7 +650,7 @@ func Test_window_prevwin() " reset q call delete('tmp.txt') - set nohidden autoread&vim + set hidden&vim autoread&vim delfunc Fun_RenewFile endfunc @@ -886,6 +924,155 @@ func Test_floatwin_splitmove() bwipe endfunc +" Test for the :only command +func Test_window_only() + new + set modified + new + call assert_fails('only', 'E445:') + only! + " Test for :only with a count + let wid = win_getid() + new + new + 3only + call assert_equal(1, winnr('$')) + call assert_equal(wid, win_getid()) + call assert_fails('close', 'E444:') + call assert_fails('%close', 'E16:') +endfunc + +" Test for errors with :wincmd +func Test_wincmd_errors() + call assert_fails('wincmd g', 'E474:') + call assert_fails('wincmd ab', 'E474:') +endfunc + +" Test for errors with :winpos +func Test_winpos_errors() + throw 'Skipped: Nvim does not have :winpos' + if !has("gui_running") && !has('win32') + call assert_fails('winpos', 'E188:') + endif + call assert_fails('winpos 10', 'E466:') +endfunc + +" Test for +cmd in a :split command +func Test_split_cmd() + split +set\ readonly + call assert_equal(1, &readonly) + call assert_equal(2, winnr('$')) + close +endfunc + +" Create maximum number of horizontally or vertically split windows and then +" run commands that create a new horizontally/vertically split window +func Run_noroom_for_newwindow_test(dir_arg) + let dir = (a:dir_arg == 'v') ? 'vert ' : '' + + " Open as many windows as possible + for i in range(500) + try + exe dir . 'new' + catch /E36:/ + break + endtry + endfor + + call writefile(['first', 'second', 'third'], 'Xfile1') + call writefile([], 'Xfile2') + call writefile([], 'Xfile3') + + " Argument list related commands + args Xfile1 Xfile2 Xfile3 + next + for cmd in ['sargument 2', 'snext', 'sprevious', 'sNext', 'srewind', + \ 'sfirst', 'slast'] + call assert_fails(dir .. cmd, 'E36:') + endfor + %argdelete + + " Buffer related commands + set modified + hide enew + for cmd in ['sbuffer Xfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind', + \ 'sbfirst', 'sblast', 'sball', 'sbmodified', 'sunhide'] + call assert_fails(dir .. cmd, 'E36:') + endfor + + " Window related commands + for cmd in ['split', 'split Xfile2', 'new', 'new Xfile3', 'sview Xfile1', + \ 'sfind runtest.vim'] + call assert_fails(dir .. cmd, 'E36:') + endfor + + " Help + call assert_fails(dir .. 'help', 'E36:') + call assert_fails(dir .. 'helpgrep window', 'E36:') + + " Command-line window + if a:dir_arg == 'h' + " Cmd-line window is always a horizontally split window + call assert_beeps('call feedkeys("q:\<CR>", "xt")') + endif + + " Quickfix and location list window + if has('quickfix') + cexpr '' + call assert_fails(dir .. 'copen', 'E36:') + lexpr '' + call assert_fails(dir .. 'lopen', 'E36:') + + " Preview window + call assert_fails(dir .. 'pedit Xfile2', 'E36:') + call setline(1, 'abc') + call assert_fails(dir .. 'psearch abc', 'E36:') + endif + + " Window commands (CTRL-W ^ and CTRL-W f) + if a:dir_arg == 'h' + call assert_fails('call feedkeys("\<C-W>^", "xt")', 'E36:') + call setline(1, 'Xfile1') + call assert_fails('call feedkeys("gg\<C-W>f", "xt")', 'E36:') + endif + enew! + + " Tag commands (:stag, :stselect and :stjump) + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "second\tXfile1\t2", + \ "third\tXfile1\t3",], + \ 'Xtags') + set tags=Xtags + call assert_fails(dir .. 'stag second', 'E36:') + call assert_fails('call feedkeys(":" .. dir .. "stselect second\n1\n", "xt")', 'E36:') + call assert_fails(dir .. 'stjump second', 'E36:') + call assert_fails(dir .. 'ptag second', 'E36:') + set tags& + call delete('Xtags') + + " :isplit and :dsplit + call setline(1, ['#define FOO 1', 'FOO']) + normal 2G + call assert_fails(dir .. 'isplit FOO', 'E36:') + call assert_fails(dir .. 'dsplit FOO', 'E36:') + + " terminal + if has('terminal') + call assert_fails(dir .. 'terminal', 'E36:') + endif + + %bwipe! + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') + only +endfunc + +func Test_split_cmds_with_no_room() + call Run_noroom_for_newwindow_test('h') + call Run_noroom_for_newwindow_test('v') +endfunc + func Test_window_resize() throw 'Skipped: Nvim supports cmdheight=0' " Vertical :resize (absolute, relative, min and max size). @@ -941,6 +1128,181 @@ func Test_window_resize() %bwipe! endfunc +" Test for adjusting the window width when a window is closed with some +" windows using 'winfixwidth' +func Test_window_width_adjust() + only + " Three vertical windows. Windows 1 and 2 have 'winfixwidth' set and close + " window 2. + wincmd v + vert resize 10 + set winfixwidth + wincmd v + set winfixwidth + wincmd c + call assert_inrange(10, 12, winwidth(1)) + " Three vertical windows. Windows 2 and 3 have 'winfixwidth' set and close + " window 3. + only + set winfixwidth + wincmd v + vert resize 10 + set winfixwidth + wincmd v + set nowinfixwidth + wincmd b + wincmd c + call assert_inrange(10, 12, winwidth(2)) + + new | only +endfunc + +" Test for jumping to a vertical/horizontal neighbor window based on the +" current cursor position +func Test_window_goto_neightbor() + %bw! + + " Vertical window movement + + " create the following window layout: + " +--+--+ + " |w1|w3| + " +--+ | + " |w2| | + " +--+--+ + " |w4 | + " +-----+ + new + vsplit + split + " vertically jump from w4 + wincmd b + call setline(1, repeat(' ', &columns)) + call cursor(1, 1) + wincmd k + call assert_equal(2, winnr()) + wincmd b + call cursor(1, &columns) + redraw! + wincmd k + call assert_equal(3, winnr()) + %bw! + + " create the following window layout: + " +--+--+--+ + " |w1|w2|w3| + " +--+--+--+ + " |w4 | + " +--------+ + new + vsplit + vsplit + wincmd b + call setline(1, repeat(' ', &columns)) + call cursor(1, 1) + wincmd k + call assert_equal(1, winnr()) + wincmd b + call cursor(1, &columns / 2) + redraw! + wincmd k + call assert_equal(2, winnr()) + wincmd b + call cursor(1, &columns) + redraw! + wincmd k + call assert_equal(3, winnr()) + %bw! + + " Horizontal window movement + + " create the following window layout: + " +--+--+--+ + " |w1|w2|w4| + " +--+--+ | + " |w3 | | + " +-----+--+ + vsplit + split + vsplit + 4wincmd l + call setline(1, repeat([' '], &lines)) + call cursor(1, 1) + redraw! + wincmd h + call assert_equal(2, winnr()) + 4wincmd l + call cursor(&lines, 1) + redraw! + wincmd h + call assert_equal(3, winnr()) + %bw! + + " create the following window layout: + " +--+--+ + " |w1|w4| + " +--+ + + " |w2| | + " +--+ + + " |w3| | + " +--+--+ + vsplit + split + split + wincmd l + call setline(1, repeat([' '], &lines)) + call cursor(1, 1) + redraw! + wincmd h + call assert_equal(1, winnr()) + wincmd l + call cursor(&lines / 2, 1) + redraw! + wincmd h + call assert_equal(2, winnr()) + wincmd l + call cursor(&lines, 1) + redraw! + wincmd h + call assert_equal(3, winnr()) + %bw! +endfunc + +" Test for an autocmd closing the destination window when jumping from one +" window to another. +func Test_close_dest_window() + split + edit Xfile + + " Test for BufLeave + augroup T1 + au! + au BufLeave Xfile $wincmd c + augroup END + wincmd b + call assert_equal(1, winnr('$')) + call assert_equal('Xfile', @%) + augroup T1 + au! + augroup END + + " Test for WinLeave + new + wincmd p + augroup T1 + au! + au WinLeave * 1wincmd c + augroup END + wincmd t + call assert_equal(1, winnr('$')) + call assert_equal('Xfile', @%) + augroup T1 + au! + augroup END + augroup! T1 + %bw! +endfunc + func Test_win_move_separator() edit a leftabove vsplit b diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index b42665c9b5..bfbba1f793 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -206,6 +206,8 @@ func Test_write_errors() call assert_fails('1,2write', 'E140:') close! + call assert_fails('w > Xtest', 'E494:') + " Try to overwrite a directory if has('unix') call mkdir('Xdir1') diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 9207ebe73b..69b687e44f 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -110,9 +110,13 @@ static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) /// 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, assert_type_T atype) + typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype) { char_u *tofree; + typval_T *exp_tv = exp_tv_arg; + typval_T *got_tv = got_tv_arg; + bool did_copy = false; + int omitted = 0; if (opt_msg_tv->v_type != VAR_UNKNOWN) { tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); @@ -130,6 +134,55 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s } if (exp_str == NULL) { + // When comparing dictionaries, drop the items that are equal, so that + // it's a lot easier to see what differs. + if (atype != ASSERT_NOTEQUAL + && exp_tv->v_type == VAR_DICT && got_tv->v_type == VAR_DICT + && exp_tv->vval.v_dict != NULL && got_tv->vval.v_dict != NULL) { + dict_T *exp_d = exp_tv->vval.v_dict; + dict_T *got_d = got_tv->vval.v_dict; + + did_copy = true; + exp_tv->vval.v_dict = tv_dict_alloc(); + got_tv->vval.v_dict = tv_dict_alloc(); + + int todo = (int)exp_d->dv_hashtab.ht_used; + for (const hashitem_T *hi = exp_d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + dictitem_T *item2 = tv_dict_find(got_d, (const char *)hi->hi_key, -1); + if (item2 == NULL + || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) { + // item of exp_d not present in got_d or values differ. + const size_t key_len = STRLEN(hi->hi_key); + tv_dict_add_tv(exp_tv->vval.v_dict, (const char *)hi->hi_key, key_len, + &TV_DICT_HI2DI(hi)->di_tv); + if (item2 != NULL) { + tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len, + &item2->di_tv); + } + } else { + omitted++; + } + todo--; + } + } + + // Add items only present in got_d. + todo = (int)got_d->dv_hashtab.ht_used; + for (const hashitem_T *hi = got_d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + dictitem_T *item2 = tv_dict_find(exp_d, (const char *)hi->hi_key, -1); + if (item2 == NULL) { + // item of got_d not present in exp_d + const size_t key_len = STRLEN(hi->hi_key); + tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len, + &TV_DICT_HI2DI(hi)->di_tv); + } + todo--; + } + } + } + tofree = (char_u *)encode_tv2string(exp_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); @@ -148,6 +201,18 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s tofree = (char_u *)encode_tv2string(got_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); + + if (omitted != 0) { + char buf[100]; + vim_snprintf(buf, sizeof(buf), " - %d equal item%s omitted", omitted, + omitted == 1 ? "" : "s"); + ga_concat(gap, buf); + } + } + + if (did_copy) { + tv_clear(exp_tv); + tv_clear(got_tv); } } @@ -405,15 +470,15 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; int save_trylevel = trylevel; + const int called_emsg_before = called_emsg; // trylevel must be zero for a ":throw" command to be considered failed trylevel = 0; - called_emsg = false; suppress_errthrow = true; emsg_silent = true; do_cmdline_cmd(cmd); - if (!called_emsg) { + if (called_emsg == called_emsg_before) { prepare_assert_error(&ga); ga_concat(&ga, "command did not fail: "); assert_append_cmd_or_arg(&ga, argvars, cmd); @@ -438,7 +503,6 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) } trylevel = save_trylevel; - called_emsg = false; suppress_errthrow = false; emsg_silent = false; emsg_on_display = false; diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 3e24e892b8..61a59bcf06 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -23,6 +23,91 @@ #define KEY_BUFFER_SIZE 0xfff +static const struct kitty_key_map_entry { + KittyKey key; + const char *name; +} kitty_key_map_entry[] = { + { KITTY_KEY_ESCAPE, "Esc" }, + { KITTY_KEY_ENTER, "CR" }, + { KITTY_KEY_TAB, "Tab" }, + { KITTY_KEY_BACKSPACE, "BS" }, + { KITTY_KEY_INSERT, "Insert" }, + { KITTY_KEY_DELETE, "Del" }, + { KITTY_KEY_LEFT, "Left" }, + { KITTY_KEY_RIGHT, "Right" }, + { KITTY_KEY_UP, "Up" }, + { KITTY_KEY_DOWN, "Down" }, + { KITTY_KEY_PAGE_UP, "PageUp" }, + { KITTY_KEY_PAGE_DOWN, "PageDown" }, + { KITTY_KEY_HOME, "Home" }, + { KITTY_KEY_END, "End" }, + { KITTY_KEY_F1, "F1" }, + { KITTY_KEY_F2, "F2" }, + { KITTY_KEY_F3, "F3" }, + { KITTY_KEY_F4, "F4" }, + { KITTY_KEY_F5, "F5" }, + { KITTY_KEY_F6, "F6" }, + { KITTY_KEY_F7, "F7" }, + { KITTY_KEY_F8, "F8" }, + { KITTY_KEY_F9, "F9" }, + { KITTY_KEY_F10, "F10" }, + { KITTY_KEY_F11, "F11" }, + { KITTY_KEY_F12, "F12" }, + { KITTY_KEY_F13, "F13" }, + { KITTY_KEY_F14, "F14" }, + { KITTY_KEY_F15, "F15" }, + { KITTY_KEY_F16, "F16" }, + { KITTY_KEY_F17, "F17" }, + { KITTY_KEY_F18, "F18" }, + { KITTY_KEY_F19, "F19" }, + { KITTY_KEY_F20, "F20" }, + { KITTY_KEY_F21, "F21" }, + { KITTY_KEY_F22, "F22" }, + { KITTY_KEY_F23, "F23" }, + { KITTY_KEY_F24, "F24" }, + { KITTY_KEY_F25, "F25" }, + { KITTY_KEY_F26, "F26" }, + { KITTY_KEY_F27, "F27" }, + { KITTY_KEY_F28, "F28" }, + { KITTY_KEY_F29, "F29" }, + { KITTY_KEY_F30, "F30" }, + { KITTY_KEY_F31, "F31" }, + { KITTY_KEY_F32, "F32" }, + { KITTY_KEY_F33, "F33" }, + { KITTY_KEY_F34, "F34" }, + { KITTY_KEY_F35, "F35" }, + { KITTY_KEY_KP_0, "k0" }, + { KITTY_KEY_KP_1, "k1" }, + { KITTY_KEY_KP_2, "k2" }, + { KITTY_KEY_KP_3, "k3" }, + { KITTY_KEY_KP_4, "k4" }, + { KITTY_KEY_KP_5, "k5" }, + { KITTY_KEY_KP_6, "k6" }, + { KITTY_KEY_KP_7, "k7" }, + { KITTY_KEY_KP_8, "k8" }, + { KITTY_KEY_KP_9, "k9" }, + { KITTY_KEY_KP_DECIMAL, "kPoint" }, + { KITTY_KEY_KP_DIVIDE, "kDivide" }, + { KITTY_KEY_KP_MULTIPLY, "kMultiply" }, + { KITTY_KEY_KP_SUBTRACT, "kMinus" }, + { KITTY_KEY_KP_ADD, "kPlus" }, + { KITTY_KEY_KP_ENTER, "kEnter" }, + { KITTY_KEY_KP_EQUAL, "kEqual" }, + { KITTY_KEY_KP_LEFT, "kLeft" }, + { KITTY_KEY_KP_RIGHT, "kRight" }, + { KITTY_KEY_KP_UP, "kUp" }, + { KITTY_KEY_KP_DOWN, "kDown" }, + { KITTY_KEY_KP_PAGE_UP, "kPageUp" }, + { KITTY_KEY_KP_PAGE_DOWN, "kPageDown" }, + { KITTY_KEY_KP_HOME, "kHome" }, + { KITTY_KEY_KP_END, "kEnd" }, + { KITTY_KEY_KP_INSERT, "kInsert" }, + { KITTY_KEY_KP_DELETE, "kDel" }, + { KITTY_KEY_KP_BEGIN, "kOrigin" }, +}; + +static Map(KittyKey, cstr_t) kitty_key_map = MAP_INIT; + #ifndef UNIT_TESTING typedef enum { kIncomplete = -1, @@ -50,6 +135,11 @@ void tinput_init(TermInput *input, Loop *loop) uv_mutex_init(&input->key_buffer_mutex); uv_cond_init(&input->key_buffer_cond); + for (size_t i = 0; i < ARRAY_SIZE(kitty_key_map_entry); i++) { + map_put(KittyKey, cstr_t)(&kitty_key_map, kitty_key_map_entry[i].key, + kitty_key_map_entry[i].name); + } + // If stdin is not a pty, switch to stderr. For cases like: // echo q | nvim -es // ls *.md | xargs nvim @@ -89,6 +179,7 @@ void tinput_init(TermInput *input, Loop *loop) void tinput_destroy(TermInput *input) { + map_destroy(KittyKey, cstr_t)(&kitty_key_map); rbuffer_free(input->key_buffer); uv_mutex_destroy(&input->key_buffer_mutex); uv_cond_destroy(&input->key_buffer_cond); @@ -206,19 +297,46 @@ static void tinput_enqueue(TermInput *input, char *buf, size_t size) rbuffer_write(input->key_buffer, buf, size); } +static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key) +{ + const char *name = map_get(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint); + if (name) { + char buf[64]; + size_t len = 0; + buf[len++] = '<'; + if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-"); + } + if (key->modifiers & TERMKEY_KEYMOD_ALT) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-"); + } + if (key->modifiers & TERMKEY_KEYMOD_CTRL) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-"); + } + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%s>", name); + tinput_enqueue(input, buf, len); + } +} + static void forward_simple_utf8(TermInput *input, TermKeyKey *key) { size_t len = 0; char buf[64]; char *ptr = key->utf8; - while (*ptr) { - if (*ptr == '<') { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); - } else { - buf[len++] = *ptr; + if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF + && map_has(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint)) { + handle_kitty_key_protocol(input, key); + return; + } else { + while (*ptr) { + if (*ptr == '<') { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); + } else { + buf[len++] = *ptr; + } + ptr++; } - ptr++; } tinput_enqueue(input, buf, len); @@ -236,19 +354,26 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); } else { assert(key->modifiers); - // Termkey doesn't include the S- modifier for ASCII characters (e.g., - // ctrl-shift-l is <C-L> instead of <C-S-L>. Vim, on the other hand, - // treats <C-L> and <C-l> the same, requiring the S- modifier. - len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); - if ((key->modifiers & TERMKEY_KEYMOD_CTRL) - && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) - && ASCII_ISUPPER(key->code.codepoint)) { - assert(len <= 62); - // Make room for the S- - memmove(buf + 3, buf + 1, len - 1); - buf[1] = 'S'; - buf[2] = '-'; - len += 2; + if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF + && map_has(KittyKey, cstr_t)(&kitty_key_map, + (KittyKey)key->code.codepoint)) { + handle_kitty_key_protocol(input, key); + return; + } else { + // Termkey doesn't include the S- modifier for ASCII characters (e.g., + // ctrl-shift-l is <C-L> instead of <C-S-L>. Vim, on the other hand, + // treats <C-L> and <C-l> the same, requiring the S- modifier. + len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); + if ((key->modifiers & TERMKEY_KEYMOD_CTRL) + && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) + && ASCII_ISUPPER(key->code.codepoint)) { + assert(len <= 62); + // Make room for the S- + memmove(buf + 3, buf + 1, len - 1); + buf[1] = 'S'; + buf[2] = '-'; + len += 2; + } } } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 84daf40744..51df57938c 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -6,6 +6,7 @@ #include "nvim/event/stream.h" #include "nvim/event/time.h" +#include "nvim/tui/input_defs.h" #include "nvim/tui/tui.h" typedef enum { diff --git a/src/nvim/tui/input_defs.h b/src/nvim/tui/input_defs.h new file mode 100644 index 0000000000..846cf45350 --- /dev/null +++ b/src/nvim/tui/input_defs.h @@ -0,0 +1,118 @@ +#ifndef NVIM_TUI_INPUT_DEFS_H +#define NVIM_TUI_INPUT_DEFS_H + +typedef enum { + KITTY_KEY_ESCAPE = 57344, + KITTY_KEY_ENTER = 57345, + KITTY_KEY_TAB = 57346, + KITTY_KEY_BACKSPACE = 57347, + KITTY_KEY_INSERT = 57348, + KITTY_KEY_DELETE = 57349, + KITTY_KEY_LEFT = 57350, + KITTY_KEY_RIGHT = 57351, + KITTY_KEY_UP = 57352, + KITTY_KEY_DOWN = 57353, + KITTY_KEY_PAGE_UP = 57354, + KITTY_KEY_PAGE_DOWN = 57355, + KITTY_KEY_HOME = 57356, + KITTY_KEY_END = 57357, + KITTY_KEY_CAPS_LOCK = 57358, + KITTY_KEY_SCROLL_LOCK = 57359, + KITTY_KEY_NUM_LOCK = 57360, + KITTY_KEY_PRINT_SCREEN = 57361, + KITTY_KEY_PAUSE = 57362, + KITTY_KEY_MENU = 57363, + KITTY_KEY_F1 = 57364, + KITTY_KEY_F2 = 57365, + KITTY_KEY_F3 = 57366, + KITTY_KEY_F4 = 57367, + KITTY_KEY_F5 = 57368, + KITTY_KEY_F6 = 57369, + KITTY_KEY_F7 = 57370, + KITTY_KEY_F8 = 57371, + KITTY_KEY_F9 = 57372, + KITTY_KEY_F10 = 57373, + KITTY_KEY_F11 = 57374, + KITTY_KEY_F12 = 57375, + KITTY_KEY_F13 = 57376, + KITTY_KEY_F14 = 57377, + KITTY_KEY_F15 = 57378, + KITTY_KEY_F16 = 57379, + KITTY_KEY_F17 = 57380, + KITTY_KEY_F18 = 57381, + KITTY_KEY_F19 = 57382, + KITTY_KEY_F20 = 57383, + KITTY_KEY_F21 = 57384, + KITTY_KEY_F22 = 57385, + KITTY_KEY_F23 = 57386, + KITTY_KEY_F24 = 57387, + KITTY_KEY_F25 = 57388, + KITTY_KEY_F26 = 57389, + KITTY_KEY_F27 = 57390, + KITTY_KEY_F28 = 57391, + KITTY_KEY_F29 = 57392, + KITTY_KEY_F30 = 57393, + KITTY_KEY_F31 = 57394, + KITTY_KEY_F32 = 57395, + KITTY_KEY_F33 = 57396, + KITTY_KEY_F34 = 57397, + KITTY_KEY_F35 = 57398, + KITTY_KEY_KP_0 = 57399, + KITTY_KEY_KP_1 = 57400, + KITTY_KEY_KP_2 = 57401, + KITTY_KEY_KP_3 = 57402, + KITTY_KEY_KP_4 = 57403, + KITTY_KEY_KP_5 = 57404, + KITTY_KEY_KP_6 = 57405, + KITTY_KEY_KP_7 = 57406, + KITTY_KEY_KP_8 = 57407, + KITTY_KEY_KP_9 = 57408, + KITTY_KEY_KP_DECIMAL = 57409, + KITTY_KEY_KP_DIVIDE = 57410, + KITTY_KEY_KP_MULTIPLY = 57411, + KITTY_KEY_KP_SUBTRACT = 57412, + KITTY_KEY_KP_ADD = 57413, + KITTY_KEY_KP_ENTER = 57414, + KITTY_KEY_KP_EQUAL = 57415, + KITTY_KEY_KP_SEPARATOR = 57416, + KITTY_KEY_KP_LEFT = 57417, + KITTY_KEY_KP_RIGHT = 57418, + KITTY_KEY_KP_UP = 57419, + KITTY_KEY_KP_DOWN = 57420, + KITTY_KEY_KP_PAGE_UP = 57421, + KITTY_KEY_KP_PAGE_DOWN = 57422, + KITTY_KEY_KP_HOME = 57423, + KITTY_KEY_KP_END = 57424, + KITTY_KEY_KP_INSERT = 57425, + KITTY_KEY_KP_DELETE = 57426, + KITTY_KEY_KP_BEGIN = 57427, + KITTY_KEY_MEDIA_PLAY = 57428, + KITTY_KEY_MEDIA_PAUSE = 57429, + KITTY_KEY_MEDIA_PLAY_PAUSE = 57430, + KITTY_KEY_MEDIA_REVERSE = 57431, + KITTY_KEY_MEDIA_STOP = 57432, + KITTY_KEY_MEDIA_FAST_FORWARD = 57433, + KITTY_KEY_MEDIA_REWIND = 57434, + KITTY_KEY_MEDIA_TRACK_NEXT = 57435, + KITTY_KEY_MEDIA_TRACK_PREVIOUS = 57436, + KITTY_KEY_MEDIA_RECORD = 57437, + KITTY_KEY_LOWER_VOLUME = 57438, + KITTY_KEY_RAISE_VOLUME = 57439, + KITTY_KEY_MUTE_VOLUME = 57440, + KITTY_KEY_LEFT_SHIFT = 57441, + KITTY_KEY_LEFT_CONTROL = 57442, + KITTY_KEY_LEFT_ALT = 57443, + KITTY_KEY_LEFT_SUPER = 57444, + KITTY_KEY_LEFT_HYPER = 57445, + KITTY_KEY_LEFT_META = 57446, + KITTY_KEY_RIGHT_SHIFT = 57447, + KITTY_KEY_RIGHT_CONTROL = 57448, + KITTY_KEY_RIGHT_ALT = 57449, + KITTY_KEY_RIGHT_SUPER = 57450, + KITTY_KEY_RIGHT_HYPER = 57451, + KITTY_KEY_RIGHT_META = 57452, + KITTY_KEY_ISO_LEVEL3_SHIFT = 57453, + KITTY_KEY_ISO_LEVEL5_SHIFT = 57454, +} KittyKey; + +#endif // NVIM_TUI_INPUT_DEFS_H diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f492792b20..e2289eb9ce 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -137,8 +137,7 @@ struct TUIData { char *space_buf; }; -static bool volatile got_winch = false; -static bool did_user_set_dimensions = false; +static int got_winch = 0; static bool cursor_style_enabled = false; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -535,7 +534,7 @@ static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) { - got_winch = true; + got_winch++; UI *ui = data; if (tui_is_stopped(ui)) { return; @@ -987,7 +986,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) r->right = MIN(r->right, grid->width); } - if (!got_winch && (!data->is_starting || did_user_set_dimensions)) { + if (!got_winch && !data->is_starting) { // Resize the _host_ terminal. UNIBI_SET_NUM_VAR(data->params[0], (int)height); UNIBI_SET_NUM_VAR(data->params[1], (int)width); @@ -997,7 +996,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) reset_scroll_region(ui, ui->width == grid->width); } } else { // Already handled the SIGWINCH signal; avoid double-resize. - got_winch = false; + got_winch = got_winch > 0 ? got_winch - 1 : 0; grid->row = -1; } } @@ -1504,23 +1503,13 @@ static void tui_guess_size(UI *ui) TUIData *data = ui->data; int width = 0, height = 0; - // 1 - look for non-default 'columns' and 'lines' options during startup - if (data->is_starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { - did_user_set_dimensions = true; - assert(Columns >= 0); - assert(Rows >= 0); - width = Columns; - height = Rows; - goto end; - } - - // 2 - try from a system call(ioctl/TIOCGWINSZ on unix) + // 1 - try from a system call(ioctl/TIOCGWINSZ on unix) if (data->out_isatty && !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) { goto end; } - // 3 - use $LINES/$COLUMNS if available + // 2 - use $LINES/$COLUMNS if available const char *val; int advance; if ((val = os_getenv("LINES")) @@ -1530,7 +1519,7 @@ static void tui_guess_size(UI *ui) goto end; } - // 4 - read from terminfo if available + // 3 - read from terminfo if available height = unibi_get_num(data->ut, unibi_lines); width = unibi_get_num(data->ut, unibi_columns); @@ -1653,6 +1642,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. || nsterm; + bool hterm = terminfo_is_term_family(term, "hterm"); bool kitty = terminfo_is_term_family(term, "xterm-kitty"); bool linuxvt = terminfo_is_term_family(term, "linux"); bool bsdvt = terminfo_is_bsd_console(term); @@ -1716,7 +1706,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col unibi_set_bool(ut, unibi_back_color_erase, false); } - if (xterm) { + if (xterm || hterm) { // Termit, LXTerminal, GTKTerm2, GNOME Terminal, MATE Terminal, roxterm, // and EvilVTE falsely claim to be xterm and do not support important xterm // control sequences that we use. In an ideal world, these would have @@ -1725,9 +1715,13 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col // treatable as xterm. // 2017-04 terminfo.src lacks these. Xterm-likes have them. - unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;"); - unibi_set_if_empty(ut, unibi_from_status_line, "\x07"); - unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr"); + if (!hterm) { + // hterm doesn't have a status line. + unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;"); + unibi_set_if_empty(ut, unibi_from_status_line, "\x07"); + // TODO(aktau): patch this in when DECSTBM is fixed (https://crbug.com/1298796) + unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr"); + } unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); unibi_set_if_empty(ut, unibi_exit_italics_mode, "\x1b[23m"); @@ -1738,6 +1732,9 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds"); } else { // Fix things advertised via TERM=xterm, for non-xterm. + // + // TODO(aktau): stop patching this out for hterm when it gains support + // (https://crbug.com/1175065). if (unibi_get_str(ut, unibi_set_lr_margin)) { ILOG("Disabling smglr with TERM=xterm for non-xterm."); unibi_set_str(ut, unibi_set_lr_margin, NULL); @@ -1886,6 +1883,8 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col && ((xterm && !vte_version) // anything claiming xterm compat // per MinTTY 0.4.3-1 release notes from 2009 || putty + // per https://chromium.googlesource.com/apps/libapps/+/a5fb83c190aa9d74f4a9bca233dac6be2664e9e9/hterm/doc/ControlSequences.md + || hterm // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 || (vte_version >= 3900) || (konsolev >= 180770) // #9364 @@ -1970,6 +1969,7 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. || nsterm; + bool hterm = terminfo_is_term_family(term, "hterm"); bool bsdvt = terminfo_is_bsd_console(term); bool dtterm = terminfo_is_term_family(term, "dtterm"); bool rxvt = terminfo_is_term_family(term, "rxvt"); @@ -1999,7 +1999,7 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, "ext.resize_screen", "\x1b[8;%p1%d;%p2%dt"); } - if (putty || xterm || rxvt) { + if (putty || xterm || hterm || rxvt) { data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, "ext.reset_scroll_region", "\x1b[r"); @@ -2048,22 +2048,27 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, } } - if (iterm || iterm_pretending_xterm) { - // FIXME: Bypassing tmux like this affects the cursor colour globally, in - // all panes, which is not particularly desirable. A better approach - // would use a tmux control sequence and an extra if(screen) test. - data->unibi_ext.set_cursor_color = - (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); - } else if ((xterm || rxvt || tmux || alacritty) - && (vte_version == 0 || vte_version >= 3900)) { - // Supported in urxvt, newer VTE. - data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", - "\033]12;#%p1%06x\007"); + data->unibi_ext.set_cursor_color = unibi_find_ext_str(ut, "Cs"); + if (-1 == data->unibi_ext.set_cursor_color) { + if (iterm || iterm_pretending_xterm) { + // FIXME: Bypassing tmux like this affects the cursor colour globally, in + // all panes, which is not particularly desirable. A better approach + // would use a tmux control sequence and an extra if(screen) test. + data->unibi_ext.set_cursor_color = + (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); + } else if ((xterm || hterm || rxvt || tmux || alacritty) + && (vte_version == 0 || vte_version >= 3900)) { + // Supported in urxvt, newer VTE. + data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", + "\033]12;#%p1%06x\007"); + } } - if (-1 != data->unibi_ext.set_cursor_color) { - data->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(ut, "ext.reset_cursor_color", - "\x1b]112\x07"); + data->unibi_ext.reset_cursor_color = unibi_find_ext_str(ut, "Cr"); + if (-1 == data->unibi_ext.reset_cursor_color) { + data->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(ut, "ext.reset_cursor_color", + "\x1b]112\x07"); + } } data->unibi_ext.save_title = (int)unibi_add_ext_str(ut, "ext.save_title", "\x1b[22;0t"); diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 078cb6a210..8324db37c6 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -89,6 +89,7 @@ #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/edit.h" +#include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" @@ -297,7 +298,7 @@ bool undo_allowed(buf_T *buf) // Don't allow changes in the buffer while editing the cmdline. The // caller of getcmdline() may get confused. if (textlock != 0) { - emsg(_(e_secure)); + emsg(_(e_textlock)); return false; } @@ -693,8 +694,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) // When not reading use the first directory that exists or ".". dirp = (char *)p_udir; while (*dirp != NUL) { - size_t dir_len = copy_option_part((char_u **)&dirp, (char_u *)dir_name, - MAXPATHL, ","); + size_t dir_len = copy_option_part(&dirp, dir_name, MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { // Use same directory as the ffname, // "dir/name" -> "dir/.name.un~" @@ -1186,7 +1186,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, bufinfo_T bi; if (name == NULL) { - file_name = u_get_undo_file_name((char *)buf->b_ffname, false); + file_name = u_get_undo_file_name(buf->b_ffname, false); if (file_name == NULL) { if (p_verbose > 0) { verbose_enter(); @@ -1291,7 +1291,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, FileInfo file_info_old; FileInfo file_info_new; if (buf->b_ffname != NULL - && os_fileinfo((char *)buf->b_ffname, &file_info_old) + && os_fileinfo(buf->b_ffname, &file_info_old) && os_fileinfo(file_name, &file_info_new) && file_info_old.stat.st_gid != file_info_new.stat.st_gid && os_fchown(fd, (uv_uid_t)-1, (uv_gid_t)file_info_old.stat.st_gid)) { @@ -1370,10 +1370,8 @@ write_error: #ifdef HAVE_ACL if (buf->b_ffname != NULL) { - vim_acl_T acl; - // For systems that support ACL: get the ACL from the original file. - acl = mch_get_acl(buf->b_ffname); + vim_acl_T acl = mch_get_acl((char_u *)buf->b_ffname); mch_set_acl((char_u *)file_name, acl); mch_free_acl(acl); } @@ -1398,7 +1396,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT char *file_name; if (name == NULL) { - file_name = u_get_undo_file_name((char *)curbuf->b_ffname, true); + file_name = u_get_undo_file_name(curbuf->b_ffname, true); if (file_name == NULL) { return; } @@ -1957,6 +1955,11 @@ void undo_time(long step, bool sec, bool file, bool absolute) bool above = false; bool did_undo = true; + if (text_locked()) { + text_locked_msg(); + return; + } + // First make sure the current undoable change is synced. if (curbuf->b_u_synced == false) { u_sync(true); diff --git a/src/nvim/version.c b/src/nvim/version.c index a70778fdd9..3ffae6592c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -332,7 +332,7 @@ static const int included_patches[] = { 1591, 1590, 1589, - // 1588, + 1588, 1587, 1586, 1585, @@ -346,11 +346,11 @@ static const int included_patches[] = { 1577, 1576, 1575, - // 1574, + 1574, 1573, 1572, 1571, - // 1570, + 1570, 1569, 1568, 1567, @@ -362,7 +362,7 @@ static const int included_patches[] = { 1561, 1560, 1559, - // 1558, + 1558, 1557, 1556, 1555, diff --git a/src/nvim/window.c b/src/nvim/window.c index 9b9a2126ff..06231150d5 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -331,6 +331,7 @@ newwindow: // move window to new tab page case 'T': + CHECK_CMDWIN; if (one_window(curwin)) { msg(_(m_onlyone)); } else { @@ -783,8 +784,8 @@ void win_config_float(win_T *wp, FloatConfig fconfig) } if (!ui_has(kUIMultigrid)) { - wp->w_height = MIN(wp->w_height, Rows - 1 - win_extra_height(wp)); - wp->w_width = MIN(wp->w_width, Columns - win_extra_width(wp)); + wp->w_height = MIN(wp->w_height, Rows - 1 - win_border_height(wp)); + wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp)); } win_set_inner_size(wp); @@ -1266,8 +1267,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } else if (wp->w_floating) { new_frame(wp); wp->w_floating = false; - // non-floating window doesn't store float config. + // non-floating window doesn't store float config or have a border. wp->w_float_config = FLOAT_CONFIG_INIT; + memset(wp->w_border_adj, 0, sizeof(wp->w_border_adj)); } /* @@ -1554,9 +1556,9 @@ static void win_init(win_T *newp, win_T *oldp, int flags) copy_loclist_stack(oldp, newp); } newp->w_localdir = (oldp->w_localdir == NULL) - ? NULL : vim_strsave(oldp->w_localdir); + ? NULL : xstrdup(oldp->w_localdir); newp->w_prevdir = (oldp->w_prevdir == NULL) - ? NULL : vim_strsave(oldp->w_prevdir); + ? NULL : xstrdup(oldp->w_prevdir); // copy tagstack and folds for (i = 0; i < oldp->w_tagstacklen; i++) { @@ -1940,7 +1942,7 @@ static void win_totop(int size, int flags) } else { // No longer a float, a non-multigrid UI shouldn't draw it as such ui_call_win_hide(curwin->w_grid_alloc.handle); - win_free_grid(curwin, false); + win_free_grid(curwin, true); } } else { // Remove the window and frame from the tree of frames. @@ -1980,7 +1982,7 @@ void win_move_after(win_T *win1, win_T *win2) return; } - // may need move the status line, window bar, horizontal or vertical separator of the last + // may need to move the status line, window bar, horizontal or vertical separator of the last // window if (win1 == lastwin) { height = win1->w_prev->w_status_height; @@ -2741,6 +2743,8 @@ int win_close(win_T *win, bool free_buf, bool force) * to be the last one left, return now. */ if (wp->w_buffer != curbuf) { + reset_VIsual_and_resel(); // stop Visual mode + other_buffer = true; win->w_closing = true; apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf); @@ -4074,7 +4078,7 @@ int win_new_tabpage(int after, char_u *filename) } newtp->tp_localdir = old_curtab->tp_localdir - ? vim_strsave(old_curtab->tp_localdir) : NULL; + ? xstrdup(old_curtab->tp_localdir) : NULL; curtab = newtp; @@ -4569,12 +4573,8 @@ void win_goto(win_T *wp) { win_T *owp = curwin; - if (text_locked()) { + if (text_or_buf_locked()) { beep_flush(); - text_locked_msg(); - return; - } - if (curbuf_locked()) { return; } @@ -4897,8 +4897,7 @@ static void win_enter_ext(win_T *const wp, const int flags) void fix_current_dir(void) { // New directory is either the local directory of the window, tab or NULL. - char *new_dir = (char *)(curwin->w_localdir - ? curwin->w_localdir : curtab->tp_localdir); + char *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->tp_localdir; char cwd[MAXPATHL]; if (os_dirname((char_u *)cwd, MAXPATHL) != OK) { cwd[0] = NUL; @@ -5181,8 +5180,7 @@ void win_free_grid(win_T *wp, bool reinit) } grid_free(&wp->w_grid_alloc); if (reinit) { - // if a float is turned into a split and back into a float, the grid - // data structure will be reused + // if a float is turned into a split, the grid data structure will be reused memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc)); } } @@ -5560,9 +5558,10 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line if (height > ROWS_AVAIL) { - // If height is greater than the available space, try to create space for the frame by - // reducing 'cmdheight' if possible, while making sure `cmdheight` doesn't go below 1. - height = MIN(ROWS_AVAIL + (p_ch - 1), height); + // If height is greater than the available space, try to create space for + // the frame by reducing 'cmdheight' if possible, while making sure + // `cmdheight` doesn't go below 1. + height = MIN((p_ch > 0 ? ROWS_AVAIL + (p_ch - 1) : ROWS_AVAIL), height); } if (height > 0) { frame_new_height(curfrp, height, false, false); @@ -6332,18 +6331,18 @@ void win_set_inner_size(win_T *wp) terminal_check_size(wp->w_buffer->terminal); } - wp->w_height_outer = (wp->w_height_inner + win_extra_height(wp)); - wp->w_width_outer = (wp->w_width_inner + win_extra_width(wp)); + wp->w_height_outer = (wp->w_height_inner + win_border_height(wp) + wp->w_winbar_height); + wp->w_width_outer = (wp->w_width_inner + win_border_width(wp)); wp->w_winrow_off = wp->w_border_adj[0] + wp->w_winbar_height; wp->w_wincol_off = wp->w_border_adj[3]; } -static int win_extra_height(win_T *wp) +static int win_border_height(win_T *wp) { - return wp->w_border_adj[0] + wp->w_border_adj[2] + wp->w_winbar_height; + return wp->w_border_adj[0] + wp->w_border_adj[2]; } -static int win_extra_width(win_T *wp) +static int win_border_width(win_T *wp) { return wp->w_border_adj[1] + wp->w_border_adj[3]; } @@ -6481,7 +6480,7 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) *file_lnum = getdigits_long(&p, false, 0); } - return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); + return find_file_name_in_path(ptr, len, options, count, (char_u *)curbuf->b_ffname); } return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); } @@ -6502,7 +6501,7 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum) { return file_name_in_line(get_cursor_line_ptr(), - curwin->w_cursor.col, options, count, curbuf->b_ffname, + curwin->w_cursor.col, options, count, (char_u *)curbuf->b_ffname, file_lnum); } |