diff options
Diffstat (limited to 'src')
54 files changed, 1166 insertions, 307 deletions
diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c index 87acf46592..126f2f3824 100644 --- a/src/mpack/lmpack.c +++ b/src/mpack/lmpack.c @@ -595,6 +595,7 @@ static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node) /* push the pair */ result = lua_next(L, -2); assert(result); /* should not be here if the map was fully processed */ + (void)result; /* ignore unused warning */ if (parent->key_visited) { /* release the current key */ lmpack_unref(L, packer->reg, (int)parent->data[1].i); @@ -1010,6 +1011,7 @@ static int lmpack_session_reply(lua_State *L) "invalid request id"); result = mpack_rpc_reply(session->session, &b, &bl, (mpack_uint32_t)id); assert(result == MPACK_OK); + (void)result; /* ignore unused warning */ lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } @@ -1027,6 +1029,7 @@ static int lmpack_session_notify(lua_State *L) session = lmpack_check_session(L, 1); result = mpack_rpc_notify(session->session, &b, &bl); assert(result == MPACK_OK); + (void)result; /* ignore unused warning */ lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index bb16459a7f..9c4b778169 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -326,7 +326,9 @@ add_custom_command( add_custom_command( OUTPUT ${VIM_MODULE_FILE} - COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE} + COMMAND ${CMAKE_COMMAND} -E env + "LUAC_PRG=${LUAC_PRG}" + ${LUA_PRG} ${CHAR_BLOB_GENERATOR} -c ${VIM_MODULE_FILE} ${LUA_VIM_MODULE_SOURCE} vim_module ${LUA_SHARED_MODULE_SOURCE} shared_module ${LUA_INSPECT_MODULE_SOURCE} inspect_module @@ -339,6 +341,7 @@ add_custom_command( ${LUA_INSPECT_MODULE_SOURCE} ${LUA_F_MODULE_SOURCE} ${LUA_META_MODULE_SOURCE} + VERBATIM ) list(APPEND NVIM_GENERATED_SOURCES diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 4f7c320129..f81cdf9deb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -696,7 +696,17 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) rv = INTEGER_OBJ(numval); break; case 2: - rv = BOOLEAN_OBJ(!!numval); + switch (numval) { + case 0: + case 1: + rv = BOOLEAN_OBJ(numval); + break; + default: + // Boolean options that return something other than 0 or 1 should return nil. Currently this + // only applies to 'autoread' which uses -1 as a local value to indicate "unset" + rv = NIL; + break; + } break; default: api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data); @@ -749,7 +759,7 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error stringval = value.data.string.data; break; case kObjectTypeNil: - // Do nothing + scope |= OPT_CLEAR; break; default: api_set_error(err, kErrorTypeValidation, "invalid value for option"); diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 6227b08b26..8fe623fc96 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -75,6 +75,8 @@ return { 'QuickFixCmdPost', -- after :make, :grep etc. 'QuickFixCmdPre', -- before :make, :grep etc. 'QuitPre', -- before :quit + 'RecordingEnter', -- when starting to record a macro + 'RecordingLeave', -- just before a macro stops recording 'RemoteReply', -- upon string reception from a remote vim 'SearchWrapped', -- after the search wrapped around 'SessionLoadPost', -- after loading a session file @@ -131,6 +133,8 @@ return { BufModifiedSet=true, DiagnosticChanged=true, DirChanged=true, + RecordingEnter=true, + RecordingLeave=true, Signal=true, TabClosed=true, TabNew=true, diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3780cad1d6..463bd5e0e6 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -932,6 +932,12 @@ static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, c last_mode = get_mode(); } + // If the event is CursorMoved, update the last cursor position + // position to avoid immediately triggering the autocommand + if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) { + curwin->w_last_cursormoved = curwin->w_cursor; + } + ap->cmds = NULL; *prev_ap = ap; last_autopat[(int)event] = ap; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 2135d0bcd2..2e3eec3642 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -643,7 +643,10 @@ static int insert_check(VimState *state) update_curswant(); s->old_topline = curwin->w_topline; s->old_topfill = curwin->w_topfill; - s->lastc = s->c; // remember previous char for CTRL-D + + if (s->c != K_EVENT) { + s->lastc = s->c; // remember previous char for CTRL-D + } // After using CTRL-G U the next cursor key will not break undo. if (dont_sync_undo == kNone) { @@ -1685,7 +1688,7 @@ static void init_prompt(int cmdchar_todo) // Insert always starts after the prompt, allow editing text after it. if (Insstart_orig.lnum != curwin->w_cursor.lnum || Insstart_orig.col != (colnr_T)STRLEN(prompt)) { Insstart.lnum = curwin->w_cursor.lnum; - Insstart.col = STRLEN(prompt); + Insstart.col = (colnr_T)STRLEN(prompt); Insstart_orig = Insstart; Insstart_textlen = Insstart.col; Insstart_blank_vcol = MAXCOL; @@ -1696,7 +1699,7 @@ static void init_prompt(int cmdchar_todo) coladvance(MAXCOL); } if (curwin->w_cursor.col < (colnr_T)STRLEN(prompt)) { - curwin->w_cursor.col = STRLEN(prompt); + curwin->w_cursor.col = (colnr_T)STRLEN(prompt); } // Make sure the cursor is in a valid position. check_cursor(); diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 9a76b67de6..e445a08227 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -133,6 +133,7 @@ return { foldtext={}, foldtextresult={args=1, base=1}, foreground={}, + fullcommand={args=1, base=1}, funcref={args={1, 3}, base=1}, ['function']={args={1, 3}, base=1}, garbagecollect={args={0, 1}}, @@ -277,6 +278,7 @@ return { readfile={args={1, 3}, base=1}, reg_executing={}, reg_recording={}, + reg_recorded={}, reltime={args={0, 2}, base=1}, reltimefloat={args=1, base=1}, reltimestr={args=1, base=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 5252c940f7..32026282cf 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1082,15 +1082,13 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Return the current directory cwd = xmalloc(MAXPATHL); - if (cwd != NULL) { - if (os_dirname(cwd, MAXPATHL) != FAIL) { + if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME - slash_adjust(cwd); + slash_adjust(cwd); #endif - rettv->vval.v_string = vim_strsave(cwd); - } - xfree(cwd); + rettv->vval.v_string = vim_strsave(cwd); } + xfree(cwd); if (curwin->w_localdir != NULL) { scope = kCdScopeWindow; @@ -3920,34 +3918,46 @@ static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_qf_loc_list(true, NULL, &argvars[0], rettv); } -/// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Common between getreg(), getreginfo() and getregtype(): get the register +/// name from the first argument. +/// Returns zero on error. +static int getreg_get_regname(typval_T *argvars) { - const char *strregname; - int arg2 = false; - bool return_list = false; - bool error = false; + const char_u *strregname; if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - arg2 = tv_get_number_chk(&argvars[1], &error); - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - return_list = tv_get_number_chk(&argvars[2], &error); - } + strregname = (const char_u *)tv_get_string_chk(&argvars[0]); + if (strregname == NULL) { // type error; errmsg already given + return 0; } } else { - strregname = _(get_vim_var_str(VV_REG)); + // Default to v:register + strregname = get_vim_var_str(VV_REG); } - if (error) { + return *strregname == 0 ? '"' : *strregname; +} + +/// "getreg()" function +static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int arg2 = false; + bool return_list = false; + + int regname = getreg_get_regname(argvars); + if (regname == 0) { return; } - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; + if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_UNKNOWN) { + bool error = false; + arg2 = (int)tv_get_number_chk(&argvars[1], &error); + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + return_list = (bool)tv_get_number_chk(&argvars[2], &error); + } + if (error) { + return; + } } if (return_list) { @@ -3964,28 +3974,16 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getregtype()" function - */ +/// "getregtype()" function static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *strregname; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { // Type error; errmsg already given. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - return; - } - } else { - // Default to v:register. - strregname = _(get_vim_var_str(VV_REG)); - } + // on error return an empty string + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); + int regname = getreg_get_regname(argvars); if (regname == 0) { - regname = '"'; + return; } colnr_T reglen = 0; @@ -7333,18 +7331,12 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *strregname; - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { - return; - } - } else { - strregname = (const char *)get_vim_var_str(VV_REG); + int regname = getreg_get_regname(argvars); + if (regname == 0) { + return; } - int regname = (strregname == NULL ? '"' : *strregname); - if (regname == 0 || regname == '@') { + if (regname == '@') { regname = '"'; } @@ -7398,6 +7390,11 @@ static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) return_register(reg_recording, rettv); } +static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_recorded, rettv); +} + /// list2proftime - convert a List to proftime_T /// /// @param arg The input list, must be of type VAR_LIST and have diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index c0cb17fa61..4965eb9c20 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4584,6 +4584,9 @@ void ex_global(exarg_T *eap) // a match on this line? match = vim_regexec_multi(®match, curwin, curbuf, lnum, (colnr_T)0, NULL, NULL); + if (regmatch.regprog == NULL) { + break; // re-compiling regprog failed + } if ((type == 'g' && match) || (type == 'v' && !match)) { ml_setmarked(lnum); ndone++; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 9f0f8d93a3..230cbd4b60 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2900,6 +2900,31 @@ int cmd_exists(const char *const name) return ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1); } +// "fullcommand" function +void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + exarg_T ea; + char_u *name = argvars[0].vval.v_string; + + while (name[0] != NUL && name[0] == ':') { + name++; + } + name = skip_range(name, NULL); + + rettv->v_type = VAR_STRING; + + ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmdidx = (cmdidx_T)0; + char_u *p = find_command(&ea, NULL); + if (p == NULL || ea.cmdidx == CMD_SIZE) { + return; + } + + rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx) + ? get_user_commands(NULL, ea.useridx) + : cmdnames[ea.cmdidx].cmd_name); +} + /// This is all pretty much copied from do_one_cmd(), with all the extra stuff /// we don't need/want deleted. Maybe this could be done better if we didn't /// repeat all this stuff. The only problem is that they may not stay @@ -7807,17 +7832,21 @@ bool changedir_func(char_u *new_dir, CdScope scope) prev_dir = pdir; } + // For UNIX ":cd" means: go to home directory. + // On other systems too if 'cdhome' is set. #if defined(UNIX) - // On Unix ":cd" means: go to home directory. if (*new_dir == NUL) { +#else + if (*new_dir == NUL && p_cdh) { +#endif // Use NameBuff for home directory name. expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); new_dir = NameBuff; } -#endif - if (vim_chdir(new_dir) == 0) { - bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; + bool dir_differs = new_dir == NULL || pdir == NULL + || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; + if (new_dir != NULL && (!dir_differs || vim_chdir(new_dir) == 0)) { post_chdir(scope, dir_differs); retval = true; } else { @@ -7833,9 +7862,9 @@ void ex_cd(exarg_T *eap) { char_u *new_dir; new_dir = eap->arg; -#if !defined(UNIX) && !defined(VMS) - // for non-UNIX ":cd" means: print current directory - if (*new_dir == NUL) { +#if !defined(UNIX) + // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set + if (*new_dir == NUL && !p_cdh) { ex_pwd(NULL); } else #endif diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index 7ec4fad277..a302d4a3c5 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -2,6 +2,7 @@ #define NVIM_EX_DOCMD_H #include "nvim/ex_cmds_defs.h" +#include "nvim/eval/funcs.h" #include "nvim/globals.h" // flags for do_cmdline() diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index d31021b3ef..1884dd49c5 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1667,14 +1667,19 @@ int vim_chdirfile(char_u *fname, CdCause cause) NameBuff[0] = NUL; } - if (os_chdir(dir) == 0) { - if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) { - do_autocmd_dirchanged(dir, kCdScopeWindow, cause); - } - } else { + if (pathcmp(dir, (char *)NameBuff, -1) == 0) { + // nothing to do + return OK; + } + + if (os_chdir(dir) != 0) { return FAIL; } + if (cause != kCdCauseOther) { + do_autocmd_dirchanged(dir, kCdScopeWindow, cause); + } + return OK; } diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua index a7dad50d48..3ec1ff2caf 100644 --- a/src/nvim/generators/gen_char_blob.lua +++ b/src/nvim/generators/gen_char_blob.lua @@ -1,12 +1,26 @@ if arg[1] == '--help' then print('Usage:') - print(' '..arg[0]..' target source varname [source varname]...') + print(' '..arg[0]..' [-c] target source varname [source varname]...') print('') print('Generates C file with big uint8_t blob.') print('Blob will be stored in a static const array named varname.') os.exit() end +-- Recognized options: +-- -c compile Lua bytecode +local options = {} + +while true do + local opt = string.match(arg[1], "^-(%w)") + if not opt then + break + end + + options[opt] = true + table.remove(arg, 1) +end + assert(#arg >= 3 and (#arg - 1) % 2 == 0) local target_file = arg[1] or error('Need a target file') @@ -14,6 +28,7 @@ local target = io.open(target_file, 'w') target:write('#include <stdint.h>\n\n') +local warn_on_missing_compiler = true local varnames = {} for argi = 2, #arg, 2 do local source_file = arg[argi] @@ -23,11 +38,26 @@ for argi = 2, #arg, 2 do end varnames[varname] = source_file - local source = io.open(source_file, 'r') - or error(string.format("source_file %q doesn't exist", source_file)) - target:write(('static const uint8_t %s[] = {\n'):format(varname)) + local output + if options.c then + local luac = os.getenv("LUAC_PRG") + if luac and luac ~= "" then + output = io.popen(luac:format(source_file), "r"):read("*a") + elseif warn_on_missing_compiler then + print("LUAC_PRG is missing, embedding raw source") + warn_on_missing_compiler = false + end + end + + if not output then + local source = io.open(source_file, "r") + or error(string.format("source_file %q doesn't exist", source_file)) + output = source:read("*a") + source:close() + end + local num_bytes = 0 local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line target:write(' ') @@ -41,19 +71,13 @@ for argi = 2, #arg, 2 do end end - for line in source:lines() do - for i = 1, string.len(line) do - local byte = line:byte(i) - assert(byte ~= 0) - target:write(string.format(' %3u,', byte)) - increase_num_bytes() - end - target:write(string.format(' %3u,', string.byte('\n', 1))) + for i = 1, string.len(output) do + local byte = output:byte(i) + target:write(string.format(' %3u,', byte)) increase_num_bytes() end - target:write(' 0};\n') - source:close() + target:write(' 0};\n') end target:close() diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 05c38a5233..6b1150cefa 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1894,7 +1894,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // buffer right here. Otherwise, use the mapping (loop around). if (mp == NULL) { *keylenp = keylen; - return map_result_get; // got character, break for loop + return map_result_get; // get character from typeahead } else { keylen = mp_match_len; } @@ -2166,7 +2166,7 @@ static int vgetorpeek(bool advance) KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; del_typebuf(1, 0); } - break; + break; // got character, break the for loop } // not enough characters, get more diff --git a/src/nvim/globals.h b/src/nvim/globals.h index dfbc80066e..697d4b11a7 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -632,6 +632,7 @@ EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. EXTERN int reg_recording INIT(= 0); // register for recording or zero EXTERN int reg_executing INIT(= 0); // register being executed or zero +EXTERN int reg_recorded INIT(= 0); // last recorded register or zero EXTERN int no_mapping INIT(= false); // currently no mapping allowed EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index b09d133495..107ff22913 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -404,9 +404,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const char *code = (char *)&shared_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/shared.lua") + if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua") || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); + nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n")); return 1; } } @@ -416,18 +416,18 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_getfield(lstate, -1, "loaded"); // [package, loaded] const char *code = (char *)&inspect_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/inspect.lua") + if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua") || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s")); + nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n")); return 1; } // [package, loaded, inspect] lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] code = (char *)&lua_F_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/F.lua") + if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua") || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s")); + nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n")); return 1; } // [package, loaded, module] @@ -438,9 +438,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") + if (luaL_loadbuffer(lstate, code, sizeof(vim_module) - 1, "@vim.lua") || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); + nlua_error(lstate, _("E5106: Error while creating vim module: %.*s\n")); return 1; } } @@ -450,9 +450,9 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_getfield(lstate, -1, "loaded"); // [package, loaded] const char *code = (char *)&lua_meta_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/_meta.lua") + if (luaL_loadbuffer(lstate, code, sizeof(lua_meta_module) - 1, "@vim/_meta.lua") || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim._meta module: %.*s")); + nlua_error(lstate, _("E5106: Error while creating vim._meta module: %.*s\n")); return 1; } // [package, loaded, module] diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c new file mode 100644 index 0000000000..b84124bc19 --- /dev/null +++ b/src/nvim/lua/spell.c @@ -0,0 +1,99 @@ + +#include <lua.h> +#include <lauxlib.h> + +#include "nvim/spell.h" +#include "nvim/vim.h" +#include "nvim/lua/spell.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/spell.c.generated.h" +#endif + +int nlua_spell_check(lua_State *lstate) +{ + if (lua_gettop(lstate) < 1) { + return luaL_error(lstate, "Expected 1 argument"); + } + + if (lua_type(lstate, 1) != LUA_TSTRING) { + luaL_argerror(lstate, 1, "expected string"); + } + + const char *str = lua_tolstring(lstate, 1, NULL); + + // spell.c requires that 'spell' is enabled, so we need to temporarily enable + // it before we can call spell functions. + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + // Check 'spelllang' + if (*curwin->w_s->b_p_spl == NUL) { + emsg(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return 0; + } + + hlf_T attr = HLF_COUNT; + size_t len = 0; + size_t pos = 0; + int capcol = -1; + int no_res = 0; + const char * result; + + lua_createtable(lstate, 0, 0); + + while (*str != NUL) { + attr = HLF_COUNT; + len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + assert(len <= INT_MAX); + + if (attr != HLF_COUNT) { + lua_createtable(lstate, 3, 0); + + lua_pushlstring(lstate, str, len); + lua_rawseti(lstate, -2, 1); + + result = attr == HLF_SPB ? "bad" : + attr == HLF_SPR ? "rare" : + attr == HLF_SPL ? "local" : + attr == HLF_SPC ? "caps" : + NULL; + + assert(result != NULL); + + lua_pushstring(lstate, result); + lua_rawseti(lstate, -2, 2); + + // +1 for 1-indexing + lua_pushinteger(lstate, (long)pos + 1); + lua_rawseti(lstate, -2, 3); + + lua_rawseti(lstate, -2, ++no_res); + } + + str += len; + pos += len; + capcol -= (int)len; + } + + // Restore 'spell' + curwin->w_p_spell = wo_spell_save; + return 1; +} + +static const luaL_Reg spell_functions[] = { + { "check", nlua_spell_check }, + { NULL , NULL } +}; + +int luaopen_spell(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, spell_functions); + return 1; +} diff --git a/src/nvim/lua/spell.h b/src/nvim/lua/spell.h new file mode 100644 index 0000000000..8f798a5191 --- /dev/null +++ b/src/nvim/lua/spell.h @@ -0,0 +1,12 @@ +#ifndef NVIM_LUA_SPELL_H +#define NVIM_LUA_SPELL_H + +#include <lauxlib.h> +#include <lua.h> +#include <lualib.h> + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/spell.h.generated.h" +#endif + +#endif // NVIM_LUA_SPELL_H diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index b746e03625..18a579ed0f 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -30,6 +30,7 @@ #include "nvim/lua/stdlib.h" #include "nvim/lua/treesitter.h" #include "nvim/lua/xdiff.h" +#include "nvim/lua/spell.h" #include "nvim/macros.h" #include "nvim/map.h" #include "nvim/memline.h" @@ -230,8 +231,8 @@ static int nlua_str_utf_start(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_head_off((char_u *)s1, (char_u *)s1 + (char_u)offset - 1); - lua_pushinteger(lstate, tail_offset); + int head_offset = mb_head_off((char_u *)s1, (char_u *)s1 + offset - 1); + lua_pushinteger(lstate, head_offset); return 1; } @@ -250,7 +251,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 + (char_u)offset - 1); + int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + offset - 1); lua_pushinteger(lstate, tail_offset); return 1; } @@ -518,6 +519,10 @@ void nlua_state_add_stdlib(lua_State *const lstate) lua_pushcfunction(lstate, &nlua_xdl_diff); lua_setfield(lstate, -2, "diff"); + // vim.spell + luaopen_spell(lstate); + lua_setfield(lstate, -2, "spell"); + lua_cjson_new(lstate); lua_setfield(lstate, -2, "json"); } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index ce44f6c619..5eb209a6f6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1609,7 +1609,8 @@ void show_utf8(void) msg((char *)IObuff); } -/// Return offset from "p" to the first byte of the character it points into. +/// Return offset from "p" to the start of a character, including composing characters. +/// "base" must be the start of the string, which must be NUL terminated. /// If "p" points to the NUL at the end of the string return 0. /// Returns 0 when already at the first byte of a character. int utf_head_off(const char_u *base, const char_u *p) @@ -1850,10 +1851,9 @@ int mb_off_next(char_u *base, char_u *p) return i; } -/* - * Return the offset from "p" to the last byte of the character it points - * into. Can start anywhere in a stream of bytes. - */ +/// 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(char_u *base, char_u *p) { int i; diff --git a/src/nvim/move.c b/src/nvim/move.c index 0a672000e4..15ba6645f5 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1011,7 +1011,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, col -= wp->w_leftcol; if (col >= 0 && col < wp->w_width) { - coloff = col - scol + (local ? 0 : wp->w_wincol) + 1; + coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_border_adj[3]) + 1; } else { scol = ccol = ecol = 0; // character is left or right of the window @@ -1022,7 +1022,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, } } } - *rowp = (local ? 0 : wp->w_winrow) + row + rowoff; + *rowp = (local ? 0 : wp->w_winrow + wp->w_border_adj[0]) + row + rowoff; *scolp = scol + coloff; *ccolp = ccol + coloff; *ecolp = ecol + coloff; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 95a521c0d8..3246596f16 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -229,7 +229,7 @@ static const struct nv_cmd { { 'N', nv_next, 0, SEARCH_REV }, { 'O', nv_open, 0, 0 }, { 'P', nv_put, 0, 0 }, - { 'Q', nv_exmode, NV_NCW, 0 }, + { 'Q', nv_regreplay, 0, 0 }, { 'R', nv_Replace, 0, false }, { 'S', nv_subst, NV_KEEPREG, 0 }, { 'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD }, @@ -4028,15 +4028,18 @@ dozet: /* * "Q" command. */ -static void nv_exmode(cmdarg_T *cap) +static void nv_regreplay(cmdarg_T *cap) { - /* - * Ignore 'Q' in Visual mode, just give a beep. - */ - if (VIsual_active) { - vim_beep(BO_EX); - } else if (!checkclearop(cap->oap)) { - do_exmode(); + if (checkclearop(cap->oap)) { + return; + } + + while (cap->count1-- && !got_int) { + if (do_execreg(reg_recorded, false, false, false) == false) { + clearopbeep(cap->oap); + break; + } + line_breakcheck(); } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 9bc63477e9..c6f9c5f04f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -912,13 +912,14 @@ int do_record(int c) showmode(); regname = c; retval = OK; - } - } else { // stop recording - /* - * Get the recorded key hits. K_SPECIAL and CSI will be escaped, this - * needs to be removed again to put it in a register. exec_reg then - * adds the escaping back later. - */ + apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf); + } + } else { // stop recording + // Get the recorded key hits. K_SPECIAL and CSI will be escaped, this + // needs to be removed again to put it in a register. exec_reg then + // adds the escaping back later. + apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf); + reg_recorded = reg_recording; reg_recording = 0; if (ui_has(kUIMessages)) { showmode(); @@ -932,10 +933,8 @@ int do_record(int c) // Remove escaping for CSI and K_SPECIAL in multi-byte chars. vim_unescape_csi(p); - /* - * We don't want to change the default register here, so save and - * restore the current register name. - */ + // We don't want to change the default register here, so save and + // restore the current register name. old_y_previous = y_previous; retval = stuff_yank(regname, p); diff --git a/src/nvim/option.c b/src/nvim/option.c index 2ceb1bd992..65adda3c01 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2991,7 +2991,7 @@ ambw_end: } } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { // 'foldcolumn' - if (check_opt_strings(*varp, p_fdc_values, false) != OK) { + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { errmsg = e_invarg; } } else if (varp == &p_pt) { @@ -3370,6 +3370,9 @@ static int int_cmp(const void *a, const void *b) /// @return OK when the value is valid, FAIL otherwise int check_signcolumn(char_u *val) { + if (*val == NUL) { + return FAIL; + } // check for basic match if (check_opt_strings(val, p_scl_values, false) == OK) { return OK; @@ -3610,7 +3613,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) c2 = c3 = 0; s = p + len + 1; c1 = get_encoded_char_adv(&s); - if (c1 == 0 || utf_char2cells(c1) > 1) { + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { @@ -3618,12 +3621,12 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) return e_invarg; } c2 = get_encoded_char_adv(&s); - if (c2 == 0 || utf_char2cells(c2) > 1) { + if (c2 == 0 || char2cells(c2) > 1) { return e_invarg; } if (!(*s == ',' || *s == NUL)) { c3 = get_encoded_char_adv(&s); - if (c3 == 0 || utf_char2cells(c3) > 1) { + if (c3 == 0 || char2cells(c3) > 1) { return e_invarg; } } @@ -3657,7 +3660,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) multispace_len = 0; while (*s != NUL && *s != ',') { c1 = get_encoded_char_adv(&s); - if (c1 == 0 || utf_char2cells(c1) > 1) { + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } multispace_len++; @@ -5059,6 +5062,9 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o /// @param[in] number New value for the number or boolean option. /// @param[in] string New value for string option. /// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). +/// If OPT_CLEAR is set, the value of the option +/// is cleared (the exact semantics of this depend +/// on the option). /// /// @return NULL on success, error message on error. char *set_option_value(const char *const name, const long number, const char *const string, @@ -5084,7 +5090,7 @@ char *set_option_value(const char *const name, const long number, const char *co } if (flags & P_STRING) { const char *s = string; - if (s == NULL) { + if (s == NULL || opt_flags & OPT_CLEAR) { s = ""; } return set_string_option(opt_idx, s, opt_flags); @@ -5106,10 +5112,23 @@ char *set_option_value(const char *const name, const long number, const char *co return NULL; // do nothing as we hit an error } } + long numval = number; + if (opt_flags & OPT_CLEAR) { + if ((int *)varp == &curbuf->b_p_ar) { + numval = -1; + } else if ((long *)varp == &curbuf->b_p_ul) { + numval = NO_LOCAL_UNDOLEVEL; + } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { + numval = -1; + } else { + char *s = NULL; + (void)get_option_value(name, &numval, (char_u **)&s, OPT_GLOBAL); + } + } if (flags & P_NUM) { - return set_num_option(opt_idx, varp, number, NULL, 0, opt_flags); + return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); } else { - return set_bool_option(opt_idx, varp, (int)number, opt_flags); + return set_bool_option(opt_idx, varp, (int)numval, opt_flags); } } } diff --git a/src/nvim/option.h b/src/nvim/option.h index 452494172f..f7dbaafeec 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -22,6 +22,7 @@ typedef enum { OPT_ONECOLUMN = 64, ///< list options one per line OPT_NO_REDRAW = 128, ///< ignore redraw flags on option OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions' + OPT_CLEAR = 512, ///< Clear local value of an option. } OptionFlags; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 19cb33a354..09c3bf3800 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -743,6 +743,7 @@ EXTERN int p_write; // 'write' EXTERN int p_wa; // 'writeany' EXTERN int p_wb; // 'writebackup' EXTERN long p_wd; // 'writedelay' +EXTERN int p_cdh; // 'cdhome' EXTERN int p_force_on; ///< options that cannot be turned off. EXTERN int p_force_off; ///< options that cannot be turned on. diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 71208dfc68..28b4eb9fe2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -275,6 +275,14 @@ return { defaults={if_true="internal,keepascii"} }, { + full_name='cdhome', abbreviation='cdh', + short_desc=N_(":cd without argument goes to the home directory"), + type='bool', scope={'global'}, + secure=true, + varname='p_cdh', + defaults={if_true=false} + }, + { full_name='cdpath', abbreviation='cd', short_desc=N_("list of directories searched with \":cd\""), type='string', list='comma', scope={'global'}, diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 450bc75ffb..3459646bad 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -15,7 +15,13 @@ # include <libutil.h> #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include <util.h> -#elif !defined(__sun) +#elif defined(__sun) +# include <sys/stream.h> +# include <sys/syscall.h> +# include <fcntl.h> +# include <unistd.h> +# include <signal.h> +#else # include <pty.h> #endif @@ -38,6 +44,118 @@ # include "os/pty_process_unix.c.generated.h" #endif +#if defined(__sun) && !defined(HAVE_FORKPTY) + +// this header defines STR, just as nvim.h, but it is defined as ('S'<<8), +// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the +// inclusion of the header even though it gets include out of order. +#include <sys/stropts.h> + +static int openpty(int *amaster, int *aslave, char *name, + struct termios *termp, struct winsize *winp) +{ + int slave = -1; + int master = open("/dev/ptmx", O_RDWR); + if (master == -1) { + goto error; + } + + // grantpt will invoke a setuid program to change permissions + // and might fail if SIGCHLD handler is set, temporarily reset + // while running + void(*sig_saved)(int) = signal(SIGCHLD, SIG_DFL); + int res = grantpt(master); + signal(SIGCHLD, sig_saved); + + if (res == -1 || unlockpt(master) == -1) { + goto error; + } + + char *slave_name = ptsname(master); + if (slave_name == NULL) { + goto error; + } + + slave = open(slave_name, O_RDWR|O_NOCTTY); + if (slave == -1) { + goto error; + } + + // ptem emulates a terminal when used on a pseudo terminal driver, + // must be pushed before ldterm + ioctl(slave, I_PUSH, "ptem"); + // ldterm provides most of the termio terminal interface + ioctl(slave, I_PUSH, "ldterm"); + // ttcompat compatability with older terminal ioctls + ioctl(slave, I_PUSH, "ttcompat"); + + if (termp) { + tcsetattr(slave, TCSAFLUSH, termp); + } + if (winp) { + ioctl(slave, TIOCSWINSZ, winp); + } + + *amaster = master; + *aslave = slave; + // ignoring name, not passed and size is unknown in the API + + return 0; + +error: + if (slave != -1) { + close(slave); + } + if (master != -1) { + close(master); + } + return -1; +} + +static int login_tty(int fd) +{ + setsid(); + if (ioctl(fd, TIOCSCTTY, NULL) == -1) { + return -1; + } + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) { + close(fd); + } + + return 0; +} + +static pid_t forkpty(int *amaster, char *name, + struct termios *termp, struct winsize *winp) +{ + int master, slave; + if (openpty(&master, &slave, name, termp, winp) == -1) { + return -1; + } + + pid_t pid = fork(); + switch (pid) { + case -1: + close(master); + close(slave); + return -1; + case 0: + close(master); + login_tty(slave); + return 0; + default: + close(slave); + *amaster = master; + return pid; + } +} + +#endif + /// termios saved at startup (for TUI) or initialized by pty_process_spawn(). static struct termios termios_default; diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 606c03f838..da2ada791f 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -386,7 +386,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i void pum_redraw(void) { int row = 0; - int col; + int grid_col; int attr_norm = win_hl_attr(curwin, HLF_PNI); int attr_select = win_hl_attr(curwin, HLF_PSI); int attr_scroll = win_hl_attr(curwin, HLF_PSB); @@ -479,7 +479,7 @@ void pum_redraw(void) // Display each entry, use two spaces for a Tab. // Do this 3 times: For the main text, kind and extra info - col = col_off; + grid_col = col_off; totwidth = 0; for (round = 1; round <= 3; ++round) { @@ -537,24 +537,15 @@ void pum_redraw(void) } } grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, - col - size + 1, attr); + grid_col - size + 1, attr); xfree(rt_start); xfree(st); - col -= width; + grid_col -= width; } else { - int size = (int)STRLEN(st); - int cells = (int)mb_string2cells(st); - - // only draw the text that fits - while (size > 0 && col + cells > pum_width + pum_col) { - size--; - size -= utf_head_off(st, st + size); - cells -= utf_ptr2cells(st + size); - } - - grid_puts_len(&pum_grid, st, size, row, col, attr); + // use grid_puts_len() to truncate the text + grid_puts(&pum_grid, st, row, grid_col, attr); xfree(st); - col += width; + grid_col += width; } if (*p != TAB) { @@ -563,12 +554,12 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1, + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col - 1, attr); - col -= 2; + grid_col -= 2; } else { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col, attr); - col += 2; + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col, attr); + grid_col += 2; } totwidth += 2; // start text at next char @@ -599,21 +590,21 @@ void pum_redraw(void) if (pum_rl) { grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, - col + 1, ' ', ' ', attr); - col = col_off - pum_base_width - n + 1; + grid_col + 1, ' ', ' ', attr); + grid_col = col_off - pum_base_width - n + 1; } else { - grid_fill(&pum_grid, row, row + 1, col, + grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_base_width + n, ' ', ' ', attr); - col = col_off + pum_base_width + n; + grid_col = col_off + pum_base_width + n; } totwidth = pum_base_width + n; } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1, ' ', ' ', attr); } else { - grid_fill(&pum_grid, row, row + 1, col, col_off + pum_width, ' ', ' ', + grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ', attr); } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 2204e2a62a..32d0ebe8eb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -657,7 +657,8 @@ static int qf_get_next_str_line(qfstate_T *state) state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, p_str, state->linelen + 1); + memcpy(state->linebuf, p_str, state->linelen); + state->linebuf[state->linelen] = '\0'; // Increment using len in order to discard the rest of the line if it // exceeds LINE_MAXLEN. diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 3e7306bad3..41c927eaa6 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -18,22 +18,20 @@ #include "nvim/garray.h" #include "nvim/os/input.h" -/* - * Logging of NFA engine. - * - * The NFA engine can write four log files: - * - Error log: Contains NFA engine's fatal errors. - * - Dump log: Contains compiled NFA state machine's information. - * - Run log: Contains information of matching procedure. - * - Debug log: Contains detailed information of matching procedure. Can be - * disabled by undefining NFA_REGEXP_DEBUG_LOG. - * The first one can also be used without debug mode. - * The last three are enabled when compiled as debug mode and individually - * disabled by commenting them out. - * The log files can get quite big! - * Do disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in - * regexp.c - */ +// Logging of NFA engine. +// +// The NFA engine can write four log files: +// - Error log: Contains NFA engine's fatal errors. +// - Dump log: Contains compiled NFA state machine's information. +// - Run log: Contains information of matching procedure. +// - Debug log: Contains detailed information of matching procedure. Can be +// disabled by undefining NFA_REGEXP_DEBUG_LOG. +// The first one can also be used without debug mode. +// The last three are enabled when compiled as debug mode and individually +// disabled by commenting them out. +// The log files can get quite big! +// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in +// regexp.c #ifdef REGEXP_DEBUG # define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log" # define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log" @@ -2567,20 +2565,20 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) ga_concat(indent, (char_u *)"| "); else ga_concat(indent, (char_u *)" "); - ga_append(indent, '\0'); + ga_append(indent, NUL); nfa_print_state2(debugf, state->out, indent); /* replace last part of indent for state->out1 */ indent->ga_len -= 3; ga_concat(indent, (char_u *)" "); - ga_append(indent, '\0'); + ga_append(indent, NUL); nfa_print_state2(debugf, state->out1, indent); /* shrink indent */ indent->ga_len -= 3; - ga_append(indent, '\0'); + ga_append(indent, NUL); } /* diff --git a/src/nvim/screen.c b/src/nvim/screen.c index a938a3b062..e62e3ca7bc 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5933,6 +5933,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col // Only 1 cell left, but character requires 2 cells: // display a '>' in the last column to avoid wrapping. */ c = '>'; + u8c = '>'; + u8cc[0] = 0; mbyte_cells = 1; } @@ -5963,6 +5965,13 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col clear_next_cell = true; } + // When at the start of the text and overwriting the right half of a + // two-cell character in the same grid, truncate that into a '>'. + if (ptr == text && col > 0 && grid->chars[off][0] == 0) { + grid->chars[off - 1][0] = '>'; + grid->chars[off - 1][1] = 0; + } + schar_copy(grid->chars[off], buf); grid->attrs[off] = attr; if (mbyte_cells == 2) { diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 32be714184..a308df07d1 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -1740,7 +1740,7 @@ char_u *get_sign_name(expand_T *xp, int idx) case EXP_SUBCMD: return (char_u *)cmds[idx]; case EXP_DEFINE: { - char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", "numhl=", + char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL }; return (char_u *)define_arg[idx]; } @@ -1849,6 +1849,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) case SIGNCMD_DEFINE: if (STRNCMP(last, "texthl", 6) == 0 || STRNCMP(last, "linehl", 6) == 0 + || STRNCMP(last, "culhl", 5) == 0 || STRNCMP(last, "numhl", 5) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; } else if (STRNCMP(last, "icon", 4) == 0) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 04068a3cb8..d97c24dcf7 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1466,6 +1466,17 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) int width, height; vterm_get_size(term->vt, &height, &width); + // May still have pending scrollback after increase in terminal height if the + // scrollback wasn't refreshed in time; append these to the top of the buffer. + int row_offset = term->sb_pending; + while (term->sb_pending > 0 && buf->b_ml.ml_line_count < height) { + fetch_row(term, term->sb_pending - row_offset - 1, width); + ml_append(0, (uint8_t *)term->textbuf, 0, false); + appended_lines(0, 1); + term->sb_pending--; + } + + row_offset -= term->sb_pending; while (term->sb_pending > 0) { // This means that either the window height has decreased or the screen // became full and libvterm had to push all rows up. Convert the first @@ -1476,7 +1487,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) ml_delete(1, false); deleted_lines(1, 1); } - fetch_row(term, -term->sb_pending, width); + fetch_row(term, -term->sb_pending - row_offset, width); int buf_index = (int)buf->b_ml.ml_line_count - height; ml_append(buf_index, (uint8_t *)term->textbuf, 0, false); appended_lines(buf_index, 1); diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index ab047fd2a8..b0d872e392 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -64,6 +64,9 @@ if has('reltime') let s:start_time = reltime() endif +" Always use forward slashes. +set shellslash + " Common with all tests on all systems. source setup.vim @@ -104,9 +107,6 @@ lang mess C " Nvim: append runtime from build dir, which contains the generated doc/tags. let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/' -" Always use forward slashes. -set shellslash - let s:t_bold = &t_md let s:t_normal = &t_me if has('win32') diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index b3df8c63e6..fdae0697c3 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -13,7 +13,7 @@ set fillchars=vert:\|,fold:- set laststatus=1 set listchars=eol:$ set joinspaces -set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd +set nohidden nosmarttab noautoindent noautoread complete-=i noruler noshowcmd set nrformats+=octal set shortmess-=F set sidescroll=0 diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 4e1a24af61..45285b69a1 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -2380,95 +2380,7 @@ func Test_autocmd_was_using_freed_memory() pclose endfunc -func Test_FileChangedShell_reload() - if !has('unix') - return - endif - augroup testreload - au FileChangedShell Xchanged let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' - augroup END - new Xchanged - call setline(1, 'reload this') - write - " Need to wait until the timestamp would change by at least a second. - sleep 2 - silent !echo 'extra line' >>Xchanged - checktime - call assert_equal('changed', g:reason) - call assert_equal(2, line('$')) - call assert_equal('extra line', getline(2)) - - " Only triggers once - let g:reason = '' - checktime - call assert_equal('', g:reason) - - " When deleted buffer is not reloaded - silent !rm Xchanged - let g:reason = '' - checktime - call assert_equal('deleted', g:reason) - call assert_equal(2, line('$')) - call assert_equal('extra line', getline(2)) - - " When recreated buffer is reloaded - call setline(1, 'buffer is changed') - silent !echo 'new line' >>Xchanged - let g:reason = '' - checktime - call assert_equal('conflict', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - " Only mode changed - silent !chmod +x Xchanged - let g:reason = '' - checktime - call assert_equal('mode', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - " Only time changed - sleep 2 - silent !touch Xchanged - let g:reason = '' - checktime - call assert_equal('time', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - if has('persistent_undo') - " With an undo file the reload can be undone and a change before the - " reload. - set undofile - call setline(2, 'before write') - write - call setline(2, 'after write') - sleep 2 - silent !echo 'different line' >>Xchanged - let g:reason = '' - checktime - call assert_equal('conflict', g:reason) - call assert_equal(3, line('$')) - call assert_equal('before write', getline(2)) - call assert_equal('different line', getline(3)) - " undo the reload - undo - call assert_equal(2, line('$')) - call assert_equal('after write', getline(2)) - " undo the change before reload - undo - call assert_equal(2, line('$')) - call assert_equal('before write', getline(2)) - - set noundofile - endif - - - au! testreload - bwipe! - call delete('Xchanged') -endfunc +" FileChangedShell tested in test_filechanged.vim func LogACmd() call add(g:logged, line('$')) diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index 20758b0c0a..af42b3857d 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -346,4 +346,12 @@ func Test_blob_sort() endif endfunc +" The following used to cause an out-of-bounds memory access +func Test_blob2string() + let v = '0z' .. repeat('01010101.', 444) + let v ..= '01' + exe 'let b = ' .. v + call assert_equal(v, string(b)) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 49a5386337..1672b0e840 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -467,6 +467,43 @@ func Test_getcompletion() call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc +func Test_fullcommand() + let tests = { + \ '': '', + \ ':': '', + \ ':::': '', + \ ':::5': '', + \ 'not_a_cmd': '', + \ 'Check': '', + \ 'syntax': 'syntax', + \ ':syntax': 'syntax', + \ '::::syntax': 'syntax', + \ 'sy': 'syntax', + \ 'syn': 'syntax', + \ 'synt': 'syntax', + \ ':sy': 'syntax', + \ '::::sy': 'syntax', + \ 'match': 'match', + \ '2match': 'match', + \ '3match': 'match', + \ 'aboveleft': 'aboveleft', + \ 'abo': 'aboveleft', + \ 's': 'substitute', + \ '5s': 'substitute', + \ ':5s': 'substitute', + \ "'<,'>s": 'substitute', + \ ":'<,'>s": 'substitute', + \ 'CheckUni': 'CheckUnix', + \ 'CheckUnix': 'CheckUnix', + \ } + + for [in, want] in items(tests) + call assert_equal(want, fullcommand(in)) + endfor + + call assert_equal('syntax', 'syn'->fullcommand()) +endfunc + func Test_shellcmd_completion() let save_path = $PATH diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 12327f34d6..c2a9683f7c 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -263,6 +263,31 @@ func Test_display_scroll_at_topline() call StopVimInTerminal(buf) endfunc +" Test for 'eob' (EndOfBuffer) item in 'fillchars' +func Test_eob_fillchars() + " default value (skipped) + " call assert_match('eob:\~', &fillchars) + " invalid values + call assert_fails(':set fillchars=eob:', 'E474:') + call assert_fails(':set fillchars=eob:xy', 'E474:') + call assert_fails(':set fillchars=eob:\255', 'E474:') + call assert_fails(':set fillchars=eob:<ff>', 'E474:') + call assert_fails(":set fillchars=eob:\x01", 'E474:') + call assert_fails(':set fillchars=eob:\\x01', 'E474:') + " default is ~ + new + redraw + call assert_equal('~', Screenline(2)) + set fillchars=eob:+ + redraw + call assert_equal('+', Screenline(2)) + set fillchars=eob:\ + redraw + call assert_equal(' ', nr2char(screenchar(2, 1))) + set fillchars& + close +endfunc + func Test_display_linebreak_breakat() new vert resize 25 diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 37786f3ca0..fc4e80f0d6 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1294,6 +1294,7 @@ func Test_edit_forbidden() call assert_fails(':Sandbox', 'E48:') delcom Sandbox call assert_equal(['a'], getline(1,'$')) + " 2) edit with textlock set fu! DoIt() call feedkeys("i\<del>\<esc>", 'tnix') @@ -1313,6 +1314,7 @@ func Test_edit_forbidden() catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function endtry au! InsertCharPre + " 3) edit when completion is shown fun! Complete(findstart, base) if a:findstart @@ -1330,6 +1332,7 @@ func Test_edit_forbidden() endtry delfu Complete set completefunc= + if has("rightleft") && exists("+fkmap") " 4) 'R' when 'fkmap' and 'revins' is set. set revins fkmap diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 1c645ad0f8..92e0559618 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -85,7 +85,7 @@ endfunc func Test_ex_mode_count_overflow() " this used to cause a crash let lines =<< trim END - call feedkeys("\<Esc>Q\<CR>") + call feedkeys("\<Esc>gQ\<CR>") v9|9silent! vi|333333233333y32333333%O call writefile(['done'], 'Xdidexmode') qall! diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim new file mode 100644 index 0000000000..b95cd5faf8 --- /dev/null +++ b/src/nvim/testdir/test_filechanged.vim @@ -0,0 +1,149 @@ +" Tests for when a file was changed outside of Vim. + +func Test_FileChangedShell_reload() + if !has('unix') + return + endif + augroup testreload + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' + augroup END + new Xchanged_r + call setline(1, 'reload this') + write + " Need to wait until the timestamp would change by at least a second. + sleep 2 + silent !echo 'extra line' >>Xchanged_r + checktime + call assert_equal('changed', g:reason) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + + " Only triggers once + let g:reason = '' + checktime + call assert_equal('', g:reason) + + " When deleted buffer is not reloaded + silent !rm Xchanged_r + let g:reason = '' + checktime + call assert_equal('deleted', g:reason) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + + " When recreated buffer is reloaded + call setline(1, 'buffer is changed') + silent !echo 'new line' >>Xchanged_r + let g:reason = '' + checktime + call assert_equal('conflict', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only mode changed + silent !chmod +x Xchanged_r + let g:reason = '' + checktime + call assert_equal('mode', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only time changed + sleep 2 + silent !touch Xchanged_r + let g:reason = '' + checktime + call assert_equal('time', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + if has('persistent_undo') + " With an undo file the reload can be undone and a change before the + " reload. + set undofile + call setline(2, 'before write') + write + call setline(2, 'after write') + sleep 2 + silent !echo 'different line' >>Xchanged_r + let g:reason = '' + checktime + call assert_equal('conflict', g:reason) + call assert_equal(3, line('$')) + call assert_equal('before write', getline(2)) + call assert_equal('different line', getline(3)) + " undo the reload + undo + call assert_equal(2, line('$')) + call assert_equal('after write', getline(2)) + " undo the change before reload + undo + call assert_equal(2, line('$')) + call assert_equal('before write', getline(2)) + + set noundofile + endif + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') +endfunc + +func Test_file_changed_dialog() + throw 'skipped: TODO: ' + if !has('unix') || has('gui_running') + return + endif + au! FileChangedShell + + new Xchanged_d + call setline(1, 'reload this') + write + " Need to wait until the timestamp would change by at least a second. + sleep 2 + silent !echo 'extra line' >>Xchanged_d + call feedkeys('L', 'L') + checktime + call assert_match('W11:', v:warningmsg) + call assert_equal(2, line('$')) + call assert_equal('reload this', getline(1)) + call assert_equal('extra line', getline(2)) + + " delete buffer, only shows an error, no prompt + silent !rm Xchanged_d + checktime + call assert_match('E211:', v:warningmsg) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + let v:warningmsg = 'empty' + + " change buffer, recreate the file and reload + call setline(1, 'buffer is changed') + silent !echo 'new line' >Xchanged_d + call feedkeys('L', 'L') + checktime + call assert_match('W12:', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only mode changed, reload + silent !chmod +x Xchanged_d + call feedkeys('L', 'L') + checktime + call assert_match('W16:', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only time changed, no prompt + sleep 2 + silent !touch Xchanged_d + let v:warningmsg = '' + checktime + call assert_equal('', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + bwipe! + call delete('Xchanged_d') +endfunc diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim index 465613f1cf..81127ea59a 100644 --- a/src/nvim/testdir/test_fileformat.vim +++ b/src/nvim/testdir/test_fileformat.vim @@ -1,5 +1,4 @@ " Test behavior of fileformat after bwipeout of last buffer - func Test_fileformat_after_bw() bwipeout set fileformat& @@ -32,6 +31,251 @@ func Test_fileformat_autocommand() bw! endfunc +" Convert the contents of a file into a literal string +func s:file2str(fname) + let b = readfile(a:fname, 'B') + let s = '' + for c in b + let s .= nr2char(c) + endfor + return s +endfunc + +" Concatenate the contents of files 'f1' and 'f2' and create 'destfile' +func s:concat_files(f1, f2, destfile) + let b1 = readfile(a:f1, 'B') + let b2 = readfile(a:f2, 'B') + let b3 = b1 + b2 + call writefile(b3, a:destfile) +endfun + +" Test for a lot of variations of the 'fileformats' option +func Test_fileformats() + " create three test files, one in each format + call writefile(['unix', 'unix'], 'XXUnix') + call writefile(["dos\r", "dos\r"], 'XXDos') + call writefile(["mac\rmac\r"], 'XXMac', 'b') + " create a file with no End Of Line + call writefile(["noeol"], 'XXEol', 'b') + " create mixed format files + call s:concat_files('XXUnix', 'XXDos', 'XXUxDs') + call s:concat_files('XXUnix', 'XXMac', 'XXUxMac') + call s:concat_files('XXDos', 'XXMac', 'XXDosMac') + call s:concat_files('XXMac', 'XXEol', 'XXMacEol') + call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc') + + new + + " Test 1: try reading and writing with 'fileformats' empty + set fileformats= + + " try with 'fileformat' set to 'unix' + set fileformat=unix + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r\n", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " try with 'fileformat' set to 'dos' + set fileformat=dos + e! XXUnix + w! Xtest + call assert_equal("unix\r\nunix\r\n", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r\r\n", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " try with 'fileformat' set to 'mac' + set fileformat=mac + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n\r", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n\r", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " Test 2: try reading and writing with 'fileformats' set to one format + + " try with 'fileformats' set to 'unix' + set fileformats=unix + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " try with 'fileformats' set to 'dos' + set fileformats=dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " try with 'fileformats' set to 'mac' + set fileformats=mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " Test 3: try reading and writing with 'fileformats' set to two formats + + " try with 'fileformats' set to 'unix,dos' + set fileformats=unix,dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXUxMac + w! Xtest + call assert_equal("unix\nunix\nmac\rmac\r\n", s:file2str('Xtest')) + bwipe XXUxMac + + e! XXDosMac + w! Xtest + call assert_equal("dos\r\ndos\r\nmac\rmac\r\r\n", s:file2str('Xtest')) + bwipe XXDosMac + + " try with 'fileformats' set to 'unix,mac' + set fileformats=unix,mac + e! XXUxDs + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\n", s:file2str('Xtest')) + bwipe XXUxDs + + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXDosMac + w! Xtest + call assert_equal("dos\r\ndos\r\nmac\rmac\r", s:file2str('Xtest')) + bwipe XXDosMac + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("unix,mac:unix\nnoeol\n", s:file2str('Xtest')) + bwipe! XXEol + + " try with 'fileformats' set to 'dos,mac' + set fileformats=dos,mac + e! XXUxDs + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\n", s:file2str('Xtest')) + bwipe XXUxDs + + e! XXUxMac + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("dos,mac:dos\r\nunix\r\nunix\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe! XXUxMac + + e! XXUxDsMc + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXMacEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("dos,mac:mac\rmac\rmac\rnoeol\r", s:file2str('Xtest')) + bwipe! XXMacEol + + " Test 4: try reading and writing with 'fileformats' set to three formats + set fileformats=unix,dos,mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("unix,dos,mac:unix\nnoeol\n", s:file2str('Xtest')) + bwipe! XXEol + + set fileformats=mac,dos,unix + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("mac,dos,unix:mac\rnoeol\r", s:file2str('Xtest')) + bwipe! XXEol + + " Test 5: try with 'binary' set + set fileformats=mac,unix,dos + set binary + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + set fileformats=mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + set fileformats=dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n", s:file2str('Xtest')) + bwipe! XXUnix + + set nobinary ff& ffs& + + " cleanup + only + %bwipe! + call delete('XXUnix') + call delete('XXDos') + call delete('XXMac') + call delete('XXEol') + call delete('XXUxDs') + call delete('XXUxMac') + call delete('XXDosMac') + call delete('XXMacEol') + call delete('XXUxDsMc') + call delete('Xtest') +endfunc + " Test for changing the fileformat using ++read func Test_fileformat_plusplus_read() new diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 31052ce47d..1ee23bb646 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -117,7 +117,7 @@ let s:filename_checks = { \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], - \ 'cs': ['file.cs'], + \ 'cs': ['file.cs', 'file.csx'], \ 'csc': ['file.csc'], \ 'csdl': ['file.csdl'], \ 'csp': ['file.csp', 'file.fdr'], @@ -204,6 +204,7 @@ let s:filename_checks = { \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], \ 'gnuplot': ['file.gpi'], \ 'go': ['file.go'], + \ 'gomod': ['go.mod'], \ 'gp': ['file.gp', '.gprc'], \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'], \ 'grads': ['file.gs'], @@ -226,6 +227,7 @@ let s:filename_checks = { \ 'hollywood': ['file.hws'], \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'], + \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig'], \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'], @@ -261,7 +263,7 @@ let s:filename_checks = { \ 'jgraph': ['file.jgr'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'], \ 'jsonc': ['file.jsonc'], \ 'jsp': ['file.jsp'], \ 'julia': ['file.jl'], @@ -455,6 +457,7 @@ let s:filename_checks = { \ 'skill': ['file.il', 'file.ils', 'file.cdf'], \ 'slang': ['file.sl'], \ 'slice': ['file.ice'], + \ 'solution': ['file.sln'], \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'], \ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'], \ 'slpspi': ['/etc/slp.spi', 'any/etc/slp.spi'], @@ -1002,4 +1005,44 @@ func Test_fs_file() filetype off endfunc +func Test_dep3patch_file() + filetype on + + call assert_true(mkdir('debian/patches', 'p')) + + " series files are not patches + call writefile(['Description: some awesome patch'], 'debian/patches/series') + split debian/patches/series + call assert_notequal('dep3patch', &filetype) + bwipe! + + " diff/patch files without the right headers should still show up as ft=diff + call writefile([], 'debian/patches/foo.diff') + split debian/patches/foo.diff + call assert_equal('diff', &filetype) + bwipe! + + " Files with the right headers are detected as dep3patch, even if they don't + " have a diff/patch extension + call writefile(['Subject: dep3patches'], 'debian/patches/bar') + split debian/patches/bar + call assert_equal('dep3patch', &filetype) + bwipe! + + " Files in sub-directories are detected + call assert_true(mkdir('debian/patches/s390x', 'p')) + call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar') + split debian/patches/s390x/bar + call assert_equal('dep3patch', &filetype) + bwipe! + + " The detection stops when seeing the "header end" marker + call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz') + split debian/patches/baz + call assert_notequal('dep3patch', &filetype) + bwipe! + + call delete('debian/patches', 'rf') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index f4ee539803..0bcbd9c4a5 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -331,7 +331,7 @@ func Test_listchars_invalid() call assert_fails('set listchars=space:xx', 'E474:') call assert_fails('set listchars=tab:xxxx', 'E474:') - " Has non-single width character + " Has double-width character call assert_fails('set listchars=space:·', 'E474:') call assert_fails('set listchars=tab:·x', 'E474:') call assert_fails('set listchars=tab:x·', 'E474:') @@ -339,6 +339,20 @@ func Test_listchars_invalid() call assert_fails('set listchars=multispace:·', 'E474:') call assert_fails('set listchars=multispace:xxx·', 'E474:') + " Has control character + call assert_fails("set listchars=space:\x01", 'E474:') + call assert_fails("set listchars=tab:\x01x", 'E474:') + call assert_fails("set listchars=tab:x\x01", 'E474:') + call assert_fails("set listchars=tab:xx\x01", 'E474:') + call assert_fails("set listchars=multispace:\x01", 'E474:') + call assert_fails("set listchars=multispace:xxx\x01", 'E474:') + call assert_fails('set listchars=space:\\x01', 'E474:') + call assert_fails('set listchars=tab:\\x01x', 'E474:') + call assert_fails('set listchars=tab:x\\x01', 'E474:') + call assert_fails('set listchars=tab:xx\\x01', 'E474:') + call assert_fails('set listchars=multispace:\\x01', 'E474:') + call assert_fails('set listchars=multispace:xxx\\x01', 'E474:') + enew! set ambiwidth& listchars& ff& endfunction diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 2140fe21ea..e0286548d9 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -108,3 +108,11 @@ func Test_echospace() set ruler& showcmd& endfunc + +" this was missing a terminating NUL +func Test_echo_string_partial() + function CountSpaces() + endfunction + call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}]))) +endfunc + diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 7d9cada074..5946732937 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -732,4 +732,25 @@ func Test_opt_reset_scroll() call delete('Xscroll') endfunc +" Test for the 'cdhome' option +func Test_opt_cdhome() + if has('unix') || has('vms') + throw 'Skipped: only works on non-Unix' + endif + + set cdhome& + call assert_equal(0, &cdhome) + set cdhome + + " This paragraph is copied from Test_cd_no_arg(). + let path = getcwd() + cd + call assert_equal($HOME, getcwd()) + call assert_notequal(path, getcwd()) + exe 'cd ' .. fnameescape(path) + call assert_equal(path, getcwd()) + + set cdhome& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 799e6cb57b..ff9ba3d8ed 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -218,15 +218,13 @@ func Test_sign_completion() call assert_equal('"sign define jump list place undefine unplace', @:) call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign icon= linehl= numhl= text= texthl=', @:) + call assert_equal('"sign define Sign culhl= icon= linehl= numhl= text= texthl=', @:) - call feedkeys(":sign define Sign linehl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' . - \ 'SpellLocal SpellRare', @:) - - call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' . - \ 'SpellLocal SpellRare', @:) + for hl in ['culhl', 'linehl', 'numhl', 'texthl'] + call feedkeys(":sign define Sign "..hl.."=Spell\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define Sign '..hl..'=SpellBad SpellCap ' . + \ 'SpellLocal SpellRare', @:) + endfor call writefile(repeat(["Sun is shining"], 30), "XsignOne") call writefile(repeat(["Sky is blue"], 30), "XsignTwo") @@ -417,20 +415,21 @@ func Test_sign_funcs() " Tests for sign_define() let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error', - \ 'culhl': 'Visual'} + \ 'culhl': 'Visual', 'numhl': 'Number'} call assert_equal(0, "sign1"->sign_define(attr)) - call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', - \ 'linehl' : 'Search', 'culhl': 'Visual', 'text' : '=>'}], + call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl': 'Search', + \ 'culhl': 'Visual', 'numhl': 'Number', 'text' : '=>'}], \ sign_getdefined()) " Define a new sign without attributes and then update it call sign_define("sign2") let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange', - \ 'culhl': 'DiffDelete', 'icon' : 'sign2.ico'} + \ 'culhl': 'DiffDelete', 'numhl': 'Number', 'icon' : 'sign2.ico'} call Sign_define_ignore_error("sign2", attr) call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange', \ 'linehl' : 'DiffAdd', 'culhl' : 'DiffDelete', 'text' : '!!', - \ 'icon' : 'sign2.ico'}], "sign2"->sign_getdefined()) + \ 'numhl': 'Number', 'icon' : 'sign2.ico'}], + \ "sign2"->sign_getdefined()) " Test for a sign name with digits call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'})) diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 113c85acef..20b760ac15 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -137,7 +137,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("Qsc\<CR>y", 'tx') + call feedkeys("gQsc\<CR>y", 'tx') bwipe! endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 5cc0da2586..aae315b2c5 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -279,7 +279,7 @@ func Test_ex_mode() endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal Qsleep 100m\rvi\r" + exe "normal gQsleep 100m\rvi\r" call timer_stop(timer) endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index dbabdcf427..8344598486 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -144,7 +144,6 @@ endfun " Test Virtual replace mode. func Test_virtual_replace() - throw 'skipped: TODO: ' if exists('&t_kD') let save_t_kD = &t_kD endif @@ -166,7 +165,6 @@ func Test_virtual_replace() \ ], getline(1, 6)) normal G mark a - inoremap <C-D> <Del> exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n" exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29) call assert_equal([' 1', diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5bb6059fa7..5fec41f9a5 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -114,6 +114,12 @@ static void tinput_done_event(void **argv) static void tinput_wait_enqueue(void **argv) { TermInput *input = argv[0]; + if (rbuffer_size(input->key_buffer) == 0 && input->paste == 3) { + const String keys = { .data = "", .size = 0 }; + String copy = copy_string(keys); + multiqueue_put(main_loop.events, tinput_paste_event, 3, + copy.data, copy.size, (intptr_t)input->paste); + } RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { const String keys = { .data = buf, .size = len }; if (input->paste) { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bb75286369..e7a60aca49 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -308,23 +308,39 @@ static void terminfo_start(UI *ui) // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + int ret; uv_loop_init(&data->write_loop); if (data->out_isatty) { - uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + ret = uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + if (ret) { + ELOG("uv_tty_init failed: %s", uv_strerror(ret)); + } #ifdef WIN32 - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } #else int retry_count = 10; // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a // few times. #12322 - while (uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO) == UV_EINTR + while ((ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR && retry_count > 0) { retry_count--; } + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } #endif } else { - uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); - uv_pipe_open(&data->output_handle.pipe, data->out_fd); + ret = uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); + if (ret) { + ELOG("uv_pipe_init failed: %s", uv_strerror(ret)); + } + ret = uv_pipe_open(&data->output_handle.pipe, data->out_fd); + if (ret) { + ELOG("uv_pipe_open failed: %s", uv_strerror(ret)); + } } flush_buf(ui); } @@ -1086,8 +1102,14 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) // after calling uv_tty_set_mode. So, set the mode of the TTY again here. // #13073 if (data->is_starting && data->input.in_fd == STDERR_FILENO) { - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + int ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } + ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } } #endif tui_set_mode(ui, (ModeShape)mode_idx); @@ -2081,8 +2103,11 @@ static void flush_buf(UI *ui) fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot); } } else { - uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), - bufs, (unsigned)(bufp - bufs), NULL); + int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), + bufs, (unsigned)(bufp - bufs), NULL); + if (ret) { + ELOG("uv_write failed: %s", uv_strerror(ret)); + } uv_run(&data->write_loop, UV_RUN_DEFAULT); } data->bufpos = 0; |