mirror of
https://github.com/neovim/neovim.git
synced 2025-10-02 07:58:35 +00:00
Merge remote-tracking branch 'origin/master' into qftf
This commit is contained in:
@@ -177,6 +177,29 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
|
||||
return vim_to_object(&di->di_tv);
|
||||
}
|
||||
|
||||
dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err)
|
||||
{
|
||||
dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
|
||||
|
||||
if (di != NULL) {
|
||||
if (di->di_flags & DI_FLAGS_RO) {
|
||||
api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data);
|
||||
} else if (di->di_flags & DI_FLAGS_LOCK) {
|
||||
api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data);
|
||||
} else if (del && (di->di_flags & DI_FLAGS_FIX)) {
|
||||
api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
|
||||
}
|
||||
} else if (dict->dv_lock) {
|
||||
api_set_error(err, kErrorTypeException, "Dictionary is locked");
|
||||
} else if (key.size == 0) {
|
||||
api_set_error(err, kErrorTypeValidation, "Key name is empty");
|
||||
} else if (key.size > INT_MAX) {
|
||||
api_set_error(err, kErrorTypeValidation, "Key name is too long");
|
||||
}
|
||||
|
||||
return di;
|
||||
}
|
||||
|
||||
/// Set a value in a scope dict. Objects are recursively expanded into their
|
||||
/// vimscript equivalents.
|
||||
///
|
||||
@@ -192,27 +215,9 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del,
|
||||
bool retval, Error *err)
|
||||
{
|
||||
Object rv = OBJECT_INIT;
|
||||
dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
|
||||
dictitem_T *di = dict_check_writable(dict, key, del, err);
|
||||
|
||||
if (di != NULL) {
|
||||
if (di->di_flags & DI_FLAGS_RO) {
|
||||
api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data);
|
||||
return rv;
|
||||
} else if (di->di_flags & DI_FLAGS_LOCK) {
|
||||
api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data);
|
||||
return rv;
|
||||
} else if (del && (di->di_flags & DI_FLAGS_FIX)) {
|
||||
api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
|
||||
return rv;
|
||||
}
|
||||
} else if (dict->dv_lock) {
|
||||
api_set_error(err, kErrorTypeException, "Dictionary is locked");
|
||||
return rv;
|
||||
} else if (key.size == 0) {
|
||||
api_set_error(err, kErrorTypeValidation, "Key name is empty");
|
||||
return rv;
|
||||
} else if (key.size > INT_MAX) {
|
||||
api_set_error(err, kErrorTypeValidation, "Key name is too long");
|
||||
if (ERROR_SET(err)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@@ -1619,7 +1619,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
|
||||
char buf[IOSIZE];
|
||||
|
||||
// apply :filter /pat/ to variable name
|
||||
xstrlcpy(buf, prefix, IOSIZE - 1);
|
||||
xstrlcpy(buf, prefix, IOSIZE);
|
||||
xstrlcat(buf, (char *)di->di_key, IOSIZE);
|
||||
if (message_filtered((char_u *)buf)) {
|
||||
continue;
|
||||
|
@@ -1850,15 +1850,30 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
ptrdiff_t len = end - str;
|
||||
assert(len > 0);
|
||||
const char * value = str + len + 1;
|
||||
if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) {
|
||||
|
||||
char c = env[i][len];
|
||||
env[i][len] = NUL;
|
||||
|
||||
#ifdef WIN32
|
||||
// Upper-case all the keys for Windows so we can detect duplicates
|
||||
char *const key = strcase_save(str, true);
|
||||
#else
|
||||
char *const key = xstrdup(str);
|
||||
#endif
|
||||
|
||||
env[i][len] = c;
|
||||
|
||||
if (tv_dict_find(rettv->vval.v_dict, key, len) != NULL) {
|
||||
// Since we're traversing from the end of the env block to the front, any
|
||||
// duplicate names encountered should be ignored. This preserves the
|
||||
// semantics of env vars defined later in the env block taking precedence.
|
||||
xfree(key);
|
||||
continue;
|
||||
}
|
||||
tv_dict_add_str(rettv->vval.v_dict,
|
||||
str, len,
|
||||
key, len,
|
||||
value);
|
||||
xfree(key);
|
||||
}
|
||||
os_free_fullenv(env);
|
||||
}
|
||||
@@ -5096,7 +5111,21 @@ static dict_T *create_environment(const dictitem_T *job_env,
|
||||
}
|
||||
|
||||
if (job_env) {
|
||||
#ifdef WIN32
|
||||
TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
|
||||
// Always use upper-case keys for Windows so we detect duplicate keys
|
||||
char *const key = strcase_save((const char *)var->di_key, true);
|
||||
size_t len = strlen(key);
|
||||
dictitem_T *dv = tv_dict_find(env, key, len);
|
||||
if (dv) {
|
||||
tv_dict_item_remove(env, dv);
|
||||
}
|
||||
tv_dict_add_str(env, key, len, tv_get_string(&var->di_tv));
|
||||
xfree(key);
|
||||
});
|
||||
#else
|
||||
tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (pty) {
|
||||
|
@@ -1772,7 +1772,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
|
||||
// count, it's a buffer name.
|
||||
///
|
||||
if ((ea.argt & EX_COUNT) && ascii_isdigit(*ea.arg)
|
||||
&& (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg)) == NUL
|
||||
&& (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg + 1)) == NUL
|
||||
|| ascii_iswhite(*p))) {
|
||||
n = getdigits_long(&ea.arg, false, -1);
|
||||
ea.arg = skipwhite(ea.arg);
|
||||
@@ -2790,15 +2790,18 @@ static struct cmdmod {
|
||||
*/
|
||||
int modifier_len(char_u *cmd)
|
||||
{
|
||||
int i, j;
|
||||
char_u *p = cmd;
|
||||
|
||||
if (ascii_isdigit(*cmd))
|
||||
p = skipwhite(skipdigits(cmd));
|
||||
for (i = 0; i < (int)ARRAY_SIZE(cmdmods); ++i) {
|
||||
for (j = 0; p[j] != NUL; ++j)
|
||||
if (p[j] != cmdmods[i].name[j])
|
||||
if (ascii_isdigit(*cmd)) {
|
||||
p = skipwhite(skipdigits(cmd + 1));
|
||||
}
|
||||
for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) {
|
||||
int j;
|
||||
for (j = 0; p[j] != NUL; j++) {
|
||||
if (p[j] != cmdmods[i].name[j]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j >= cmdmods[i].minlen
|
||||
&& !ASCII_ISALPHA(p[j])
|
||||
&& (p == cmd || cmdmods[i].has_count)) {
|
||||
|
@@ -471,6 +471,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
|
||||
lua_pushcfunction(lstate, &nlua_wait);
|
||||
lua_setfield(lstate, -2, "wait");
|
||||
|
||||
// _getvar
|
||||
lua_pushcfunction(lstate, &nlua_getvar);
|
||||
lua_setfield(lstate, -2, "_getvar");
|
||||
|
||||
// _setvar
|
||||
lua_pushcfunction(lstate, &nlua_setvar);
|
||||
lua_setfield(lstate, -2, "_setvar");
|
||||
|
||||
|
||||
// vim.loop
|
||||
luv_set_loop(lstate, &main_loop.uv);
|
||||
luv_set_callback(lstate, nlua_luv_cfpcall);
|
||||
@@ -870,6 +879,109 @@ check_err:
|
||||
return request ? 1 : 0;
|
||||
}
|
||||
|
||||
static dict_T *nlua_get_var_scope(lua_State *lstate) {
|
||||
const char *scope = luaL_checkstring(lstate, 1);
|
||||
handle_T handle = (handle_T)luaL_checkinteger(lstate, 2);
|
||||
dict_T *dict = NULL;
|
||||
Error err = ERROR_INIT;
|
||||
if (strequal(scope, "g")) {
|
||||
dict = &globvardict;
|
||||
} else if (strequal(scope, "v")) {
|
||||
dict = &vimvardict;
|
||||
} else if (strequal(scope, "b")) {
|
||||
buf_T *buf = find_buffer_by_handle(handle, &err);
|
||||
if (buf) {
|
||||
dict = buf->b_vars;
|
||||
}
|
||||
} else if (strequal(scope, "w")) {
|
||||
win_T *win = find_window_by_handle(handle, &err);
|
||||
if (win) {
|
||||
dict = win->w_vars;
|
||||
}
|
||||
} else if (strequal(scope, "t")) {
|
||||
tabpage_T *tabpage = find_tab_by_handle(handle, &err);
|
||||
if (tabpage) {
|
||||
dict = tabpage->tp_vars;
|
||||
}
|
||||
} else {
|
||||
luaL_error(lstate, "invalid scope", err.msg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ERROR_SET(&err)) {
|
||||
luaL_error(lstate, "FAIL: %s", err.msg);
|
||||
return NULL;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
|
||||
static int nlua_getvar(lua_State *lstate)
|
||||
{
|
||||
// non-local return if not found
|
||||
dict_T *dict = nlua_get_var_scope(lstate);
|
||||
size_t len;
|
||||
const char *name = luaL_checklstring(lstate, 3, &len);
|
||||
|
||||
dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
|
||||
if (di == NULL) {
|
||||
return 0; // nil
|
||||
}
|
||||
nlua_push_typval(lstate, &di->di_tv, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int nlua_setvar(lua_State *lstate)
|
||||
{
|
||||
// non-local return if not found
|
||||
dict_T *dict = nlua_get_var_scope(lstate);
|
||||
String key;
|
||||
key.data = (char *)luaL_checklstring(lstate, 3, &key.size);
|
||||
|
||||
bool del = (lua_gettop(lstate) < 4) || lua_isnil(lstate, 4);
|
||||
|
||||
Error err = ERROR_INIT;
|
||||
dictitem_T *di = dict_check_writable(dict, key, del, &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (del) {
|
||||
// Delete the key
|
||||
if (di == NULL) {
|
||||
// Doesn't exist, nothing to do
|
||||
return 0;
|
||||
} else {
|
||||
// Delete the entry
|
||||
tv_dict_item_remove(dict, di);
|
||||
}
|
||||
} else {
|
||||
// Update the key
|
||||
typval_T tv;
|
||||
|
||||
// Convert the lua value to a vimscript type in the temporary variable
|
||||
lua_pushvalue(lstate, 4);
|
||||
if (!nlua_pop_typval(lstate, &tv)) {
|
||||
return luaL_error(lstate, "Couldn't convert lua value");
|
||||
}
|
||||
|
||||
if (di == NULL) {
|
||||
// Need to create an entry
|
||||
di = tv_dict_item_alloc_len(key.data, key.size);
|
||||
tv_dict_add(dict, di);
|
||||
} else {
|
||||
// Clear the old value
|
||||
tv_clear(&di->di_tv);
|
||||
}
|
||||
|
||||
// Update the value
|
||||
tv_copy(&tv, &di->di_tv);
|
||||
// Clear the temporary variable
|
||||
tv_clear(&tv);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nlua_nil_tostring(lua_State *lstate)
|
||||
{
|
||||
lua_pushstring(lstate, "vim.NIL");
|
||||
|
@@ -341,32 +341,24 @@ do
|
||||
end
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
local function pcall_ret(status, ...)
|
||||
if status then return ... end
|
||||
end
|
||||
local function nil_wrap(fn)
|
||||
return function(...)
|
||||
return pcall_ret(pcall(fn, ...))
|
||||
local function make_dict_accessor(scope)
|
||||
validate {
|
||||
scope = {scope, 's'};
|
||||
}
|
||||
local mt = {}
|
||||
function mt:__newindex(k, v)
|
||||
return vim._setvar(scope, 0, k, v)
|
||||
end
|
||||
function mt:__index(k)
|
||||
return vim._getvar(scope, 0, k)
|
||||
end
|
||||
return setmetatable({}, mt)
|
||||
end
|
||||
|
||||
vim.b = make_meta_accessor(
|
||||
nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end),
|
||||
function(v, k) return a.nvim_buf_set_var(0, v, k) end,
|
||||
function(v) return a.nvim_buf_del_var(0, v) end
|
||||
)
|
||||
vim.w = make_meta_accessor(
|
||||
nil_wrap(function(v) return a.nvim_win_get_var(0, v) end),
|
||||
function(v, k) return a.nvim_win_set_var(0, v, k) end,
|
||||
function(v) return a.nvim_win_del_var(0, v) end
|
||||
)
|
||||
vim.t = make_meta_accessor(
|
||||
nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end),
|
||||
function(v, k) return a.nvim_tabpage_set_var(0, v, k) end,
|
||||
function(v) return a.nvim_tabpage_del_var(0, v) end
|
||||
)
|
||||
vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
|
||||
vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
|
||||
vim.g = make_dict_accessor('g')
|
||||
vim.v = make_dict_accessor('v')
|
||||
vim.b = make_dict_accessor('b')
|
||||
vim.w = make_dict_accessor('w')
|
||||
vim.t = make_dict_accessor('t')
|
||||
vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
|
||||
|
||||
local function getenv(k)
|
||||
|
@@ -1069,7 +1069,7 @@ char_u *get_menu_names(expand_T *xp, int idx)
|
||||
#define TBUFFER_LEN 256
|
||||
static char_u tbuffer[TBUFFER_LEN]; /*hack*/
|
||||
char_u *str;
|
||||
static int should_advance = FALSE;
|
||||
static bool should_advance = false;
|
||||
|
||||
if (idx == 0) { /* first call: start at first item */
|
||||
menu = expand_menu;
|
||||
@@ -1089,12 +1089,13 @@ char_u *get_menu_names(expand_T *xp, int idx)
|
||||
|
||||
if (menu->modes & expand_modes) {
|
||||
if (menu->children != NULL) {
|
||||
if (should_advance)
|
||||
STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN - 1);
|
||||
else {
|
||||
STRLCPY(tbuffer, menu->dname, TBUFFER_LEN - 1);
|
||||
if (menu->en_dname == NULL)
|
||||
should_advance = TRUE;
|
||||
if (should_advance) {
|
||||
STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN);
|
||||
} else {
|
||||
STRLCPY(tbuffer, menu->dname, TBUFFER_LEN);
|
||||
if (menu->en_dname == NULL) {
|
||||
should_advance = true;
|
||||
}
|
||||
}
|
||||
/* hack on menu separators: use a 'magic' char for the separator
|
||||
* so that '.' in names gets escaped properly */
|
||||
@@ -1105,8 +1106,9 @@ char_u *get_menu_names(expand_T *xp, int idx)
|
||||
str = menu->en_dname;
|
||||
else {
|
||||
str = menu->dname;
|
||||
if (menu->en_dname == NULL)
|
||||
should_advance = TRUE;
|
||||
if (menu->en_dname == NULL) {
|
||||
should_advance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
|
@@ -1942,10 +1942,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
|
||||
case OP_FORMAT:
|
||||
if (*curbuf->b_p_fex != NUL) {
|
||||
op_formatexpr(oap); // use expression
|
||||
} else if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
|
||||
op_colon(oap); // use external command
|
||||
} else {
|
||||
op_format(oap, false); // use internal function
|
||||
if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
|
||||
op_colon(oap); // use external command
|
||||
} else {
|
||||
op_format(oap, false); // use internal function
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@@ -65,7 +65,7 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2,
|
||||
if (expandenv) {
|
||||
expand_env(s1, exp1, MAXPATHL);
|
||||
} else {
|
||||
xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1);
|
||||
xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL);
|
||||
}
|
||||
bool id_ok_1 = os_fileid((char *)exp1, &file_id_1);
|
||||
bool id_ok_2 = os_fileid((char *)s2, &file_id_2);
|
||||
|
@@ -3010,7 +3010,7 @@ static int find_extra(char_u **pp)
|
||||
// Repeat for addresses separated with ';'
|
||||
for (;; ) {
|
||||
if (ascii_isdigit(*str)) {
|
||||
str = skipdigits(str);
|
||||
str = skipdigits(str + 1);
|
||||
} else if (*str == '/' || *str == '?') {
|
||||
str = skip_regexp(str + 1, *str, false, NULL);
|
||||
if (*str != first_char) {
|
||||
|
@@ -781,17 +781,66 @@ func Test_diff_lastline()
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
func WriteDiffFiles(buf, list1, list2)
|
||||
call writefile(a:list1, 'Xfile1')
|
||||
call writefile(a:list2, 'Xfile2')
|
||||
if a:buf
|
||||
call term_sendkeys(a:buf, ":checktime\<CR>")
|
||||
endif
|
||||
endfunc
|
||||
" Verify a screendump with both the internal and external diff.
|
||||
func VerifyBoth(buf, dumpfile, extra)
|
||||
" trailing : for leaving the cursor on the command line
|
||||
for cmd in [":set diffopt=filler" . a:extra . "\<CR>:", ":set diffopt+=internal\<CR>:"]
|
||||
call term_sendkeys(a:buf, cmd)
|
||||
if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external')
|
||||
break " don't let the next iteration overwrite the "failed" file.
|
||||
" don't let the next iteration overwrite the "failed" file.
|
||||
return
|
||||
endif
|
||||
endfor
|
||||
|
||||
" also test unified diff
|
||||
call term_sendkeys(a:buf, ":call SetupUnified()\<CR>:")
|
||||
call VerifyScreenDump(a:buf, a:dumpfile, {}, 'unified')
|
||||
call term_sendkeys(a:buf, ":call StopUnified()\<CR>:")
|
||||
endfunc
|
||||
|
||||
" Verify a screendump with the internal diff only.
|
||||
func VerifyInternal(buf, dumpfile, extra)
|
||||
call term_sendkeys(a:buf, ":diffupdate!\<CR>")
|
||||
" trailing : for leaving the cursor on the command line
|
||||
call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:")
|
||||
call TermWait(a:buf)
|
||||
call VerifyScreenDump(a:buf, a:dumpfile, {})
|
||||
endfunc
|
||||
|
||||
func Test_diff_screen()
|
||||
CheckScreendump
|
||||
CheckFeature menu
|
||||
|
||||
let lines =<< trim END
|
||||
func UnifiedDiffExpr()
|
||||
" Prepend some text to check diff type detection
|
||||
call writefile(['warning', ' message'], v:fname_out)
|
||||
silent exe '!diff -u ' .. v:fname_in .. ' ' .. v:fname_new .. '>>' .. v:fname_out
|
||||
endfunc
|
||||
func SetupUnified()
|
||||
set diffexpr=UnifiedDiffExpr()
|
||||
endfunc
|
||||
func StopUnified()
|
||||
set diffexpr=
|
||||
endfunc
|
||||
END
|
||||
call writefile(lines, 'XdiffSetup')
|
||||
|
||||
" clean up already existing swap files, just in case
|
||||
call delete('.Xfile1.swp')
|
||||
call delete('.Xfile2.swp')
|
||||
|
||||
" Test 1: Add a line in beginning of file 2
|
||||
call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
|
||||
let buf = RunVimInTerminal('-d -S XdiffSetup Xfile1 Xfile2', {})
|
||||
" Set autoread mode, so that Vim won't complain once we re-write the test
|
||||
" files
|
||||
call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
|
||||
@@ -911,6 +960,7 @@ func Test_diff_screen()
|
||||
call StopVimInTerminal(buf)
|
||||
call delete('Xfile1')
|
||||
call delete('Xfile2')
|
||||
call delete('XdiffSetup')
|
||||
endfunc
|
||||
|
||||
func Test_diff_with_cursorline()
|
||||
|
Reference in New Issue
Block a user