mirror of
https://github.com/neovim/neovim.git
synced 2026-05-25 14:28:28 +00:00
Merge #39176 from justinmk/luavimfn
This commit is contained in:
@@ -192,6 +192,30 @@ be "fast". For example, commit 3940c435e405 fixed such a bug with
|
||||
Common examples of non-fast code: regexp matching, wildcard expansion,
|
||||
expression evaluation.
|
||||
|
||||
==============================================================================
|
||||
Internal practices and patterns *dev-internals-howto*
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
How to add a new Ex cmd or "vimfn" function ("f_xx"/"builtin"/"eval")
|
||||
|
||||
To implement an Ex cmd or f_xx function, choose from one of three ways (in
|
||||
order of preference):
|
||||
|
||||
1. Full Lua: the implementation lives in entirely in Lua. This has
|
||||
a performance benefit: the Vimscript <=> Lua bridge is skipped when the
|
||||
`vim.fn.xx()` function is called from Lua. Examples:
|
||||
- `f_hostname`
|
||||
2. Partial Lua: the C implementation calls `nlua_call_vimfn` or
|
||||
`nlua_call_excmd`. (Or in rare cases: `nlua_exec` / `NLUA_EXEC_STATIC`).
|
||||
Examples:
|
||||
- `ex_log`
|
||||
- `ex_lsp`
|
||||
- `f_serverlist`
|
||||
3. Legacy way: implement the function entirely in C.
|
||||
Examples:
|
||||
- `f_chansend`
|
||||
- `f_flatten`
|
||||
- `f_searchpos`
|
||||
|
||||
==============================================================================
|
||||
The event-loop *event-loop*
|
||||
|
||||
@@ -2,6 +2,10 @@ local api = vim.api
|
||||
local fs = vim.fs
|
||||
local util = require('vim._core.util')
|
||||
|
||||
--- Parsed ex command arguments for builtin commands, passed from C via `nlua_call_excmd`.
|
||||
--- Inherits fields from user command args: args, bang, line1, line2, range, count, reg, smods.
|
||||
--- @class vim._core.ExCmdArgs : vim.api.keyset.create_user_command.command_args
|
||||
|
||||
local M = {}
|
||||
|
||||
--- @param msg string
|
||||
@@ -151,9 +155,9 @@ local actions = {
|
||||
local available_subcmds = vim.tbl_keys(actions)
|
||||
|
||||
--- Implements command: `:lsp {subcmd} {name}?`.
|
||||
--- @param args string
|
||||
M.ex_lsp = function(args)
|
||||
local fargs = api.nvim_parse_cmd('lsp ' .. args, {}).args
|
||||
--- @param eap vim._core.ExCmdArgs
|
||||
M.ex_lsp = function(eap)
|
||||
local fargs = api.nvim_parse_cmd('lsp ' .. eap.args, {}).args
|
||||
if not fargs then
|
||||
return
|
||||
end
|
||||
@@ -192,11 +196,11 @@ end
|
||||
local log_dir = vim.fn.stdpath('log')
|
||||
|
||||
--- Implements command: `:log {file}`.
|
||||
--- @param filename string
|
||||
--- @param mods string
|
||||
M.ex_log = function(filename, mods)
|
||||
--- @param eap vim._core.ExCmdArgs
|
||||
M.ex_log = function(eap)
|
||||
local filename = eap.args
|
||||
if filename == '' then
|
||||
util.wrapped_edit(log_dir, mods)
|
||||
util.wrapped_edit(log_dir, eap.smods)
|
||||
else
|
||||
local path --- @type string
|
||||
-- Special case for NVIM_LOG_FILE
|
||||
@@ -210,7 +214,7 @@ M.ex_log = function(filename, mods)
|
||||
echo_err(("No such log file: '%s'"):format(path))
|
||||
return
|
||||
end
|
||||
util.wrapped_edit(path, mods)
|
||||
util.wrapped_edit(path, eap.smods)
|
||||
vim.cmd.normal { 'G', bang = true }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,36 +2,40 @@
|
||||
|
||||
local M = {}
|
||||
|
||||
--- Called by builtin serverlist(). Returns all running servers in stdpath("run").
|
||||
--- Called by builtin serverlist(). Returns the combined server list (own + peers).
|
||||
---
|
||||
--- - TODO: track TCP servers, somehow.
|
||||
--- - TODO: support Windows named pipes.
|
||||
---
|
||||
--- @param listed string[] Already listed servers
|
||||
--- @return string[] # List of servers found on the current machine in stdpath("run").
|
||||
function M.serverlist(listed)
|
||||
--- @param opts? table Options:
|
||||
--- - opts.peer is true, also discover peer servers.
|
||||
--- @param addrs string[] Internal ("own") addresses, from `server_address_list`.
|
||||
--- @return string[] # Combined list of servers (own + peers).
|
||||
function M.serverlist(opts, addrs)
|
||||
if type(opts) ~= 'table' or not opts.peer then
|
||||
return addrs
|
||||
end
|
||||
|
||||
-- Discover peer servers in stdpath("run").
|
||||
-- TODO: track TCP servers, somehow.
|
||||
-- TODO: support Windows named pipes.
|
||||
local root = vim.fs.normalize(vim.fn.stdpath('run') .. '/..')
|
||||
local socket_paths = vim.fs.find(function(name, _)
|
||||
return name:match('nvim.*')
|
||||
end, { path = root, type = 'socket', limit = math.huge })
|
||||
|
||||
local found = {} ---@type string[]
|
||||
for _, socket in ipairs(socket_paths) do
|
||||
-- Don't list servers twice
|
||||
if not vim.list_contains(listed, socket) then
|
||||
if not vim.list_contains(addrs, socket) then
|
||||
local ok, chan = pcall(vim.fn.sockconnect, 'pipe', socket, { rpc = true })
|
||||
if ok and chan then
|
||||
-- Check that the server is responding
|
||||
-- TODO: do we need a timeout or error handling here?
|
||||
if vim.fn.rpcrequest(chan, 'nvim_get_chan_info', 0).id then
|
||||
table.insert(found, socket)
|
||||
table.insert(addrs, socket)
|
||||
end
|
||||
vim.fn.chanclose(chan)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return found
|
||||
return addrs
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -62,12 +62,14 @@ end
|
||||
|
||||
--- :edit, but it respects commands like :hor, :vert, :tab, etc.
|
||||
--- @param file string
|
||||
--- @param mods_str string
|
||||
function M.wrapped_edit(file, mods_str)
|
||||
local cmdline = table.concat({ mods_str, 'edit' }, ' ')
|
||||
local mods = vim.api.nvim_parse_cmd(cmdline, {}).mods
|
||||
--- @diagnostic disable-next-line: need-check-nil
|
||||
if mods.tab > 0 or mods.split ~= '' or mods.horizontal or mods.vertical then
|
||||
--- @param mods string|vim.api.keyset.cmd_mods Modifier string ("vertical") or structured mods table.
|
||||
function M.wrapped_edit(file, mods)
|
||||
assert(mods)
|
||||
if type(mods) == 'string' then
|
||||
mods = vim.api.nvim_parse_cmd(mods .. ' edit', {}).mods --[[@as vim.api.keyset.cmd_mods]]
|
||||
end
|
||||
--- @cast mods vim.api.keyset.cmd_mods
|
||||
if (mods.tab or 0) > 0 or (mods.split or '') ~= '' or mods.horizontal or mods.vertical then
|
||||
local buf = M.get_buf_by_name(file)
|
||||
if buf == nil then
|
||||
buf = vim.api.nvim_create_buf(true, false)
|
||||
|
||||
@@ -11,4 +11,10 @@ function M.f_hostname()
|
||||
return vim.uv.os_gethostname()
|
||||
end
|
||||
|
||||
--- Returns all environment variables as a dictionary.
|
||||
--- @return table<string, string>
|
||||
function M.f_environ()
|
||||
return vim.uv.os_environ()
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -111,7 +111,7 @@ error('Cannot require a meta file')
|
||||
---
|
||||
--- Command modifiers in a structured format. Has the same structure as the
|
||||
--- "mods" key of |nvim_parse_cmd()|.
|
||||
--- @field smods table
|
||||
--- @field smods vim.api.keyset.cmd_mods
|
||||
|
||||
--- @class vim.api.keyset.command_info
|
||||
--- @field name string
|
||||
|
||||
@@ -48,7 +48,7 @@ local dup_allowed = {
|
||||
E509 = 2,
|
||||
E5101 = 2,
|
||||
E5102 = 2,
|
||||
E5108 = 4,
|
||||
E5108 = 6,
|
||||
E5111 = 2,
|
||||
E513 = 2,
|
||||
E521 = 2,
|
||||
|
||||
@@ -2090,6 +2090,7 @@ M.funcs = {
|
||||
<
|
||||
]=],
|
||||
fast = true,
|
||||
func_lua = 'f_environ',
|
||||
name = 'environ',
|
||||
params = {},
|
||||
signature = 'environ()',
|
||||
|
||||
@@ -403,7 +403,6 @@ static void lua_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
ADD_C(args, vim_to_object(tv, &arena, false));
|
||||
}
|
||||
|
||||
// `func_lua` is the function name (e.g. "f_hostname") in `_core/vimfn.lua`.
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "return require('vim._core.vimfn').%s(...)", fptr.func_lua);
|
||||
|
||||
@@ -1206,51 +1205,6 @@ static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
rettv->vval.v_number = n;
|
||||
}
|
||||
|
||||
/// "environ()" function
|
||||
static void f_environ(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
tv_dict_alloc_ret(rettv);
|
||||
|
||||
size_t env_size = os_get_fullenv_size();
|
||||
char **env = xmalloc(sizeof(*env) * (env_size + 1));
|
||||
env[env_size] = NULL;
|
||||
|
||||
os_copy_fullenv(env, env_size);
|
||||
|
||||
for (ssize_t i = (ssize_t)env_size - 1; i >= 0; i--) {
|
||||
const char *str = env[i];
|
||||
const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
|
||||
'=');
|
||||
assert(end != NULL);
|
||||
ptrdiff_t len = end - str;
|
||||
assert(len > 0);
|
||||
const char *value = str + len + 1;
|
||||
|
||||
char c = env[i][len];
|
||||
env[i][len] = NUL;
|
||||
|
||||
#ifdef MSWIN
|
||||
// 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, key, (size_t)len, value);
|
||||
xfree(key);
|
||||
}
|
||||
os_free_fullenv(env);
|
||||
}
|
||||
|
||||
/// "escape({string}, {chars})" function
|
||||
static void f_escape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
@@ -3429,9 +3383,12 @@ dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, cons
|
||||
|
||||
if (!clear_env) {
|
||||
typval_T temp_env = TV_INITIAL_VALUE;
|
||||
f_environ(NULL, &temp_env, (EvalFuncData){ .null = NULL });
|
||||
tv_dict_extend(env, temp_env.vval.v_dict, "force");
|
||||
tv_dict_free(temp_env.vval.v_dict);
|
||||
typval_T no_args[] = { { .v_type = VAR_UNKNOWN } };
|
||||
nlua_call_vimfn("vim._core.vimfn", "f_environ", no_args, &temp_env);
|
||||
if (temp_env.v_type == VAR_DICT) {
|
||||
tv_dict_extend(env, temp_env.vval.v_dict, "force");
|
||||
}
|
||||
tv_clear(&temp_env);
|
||||
|
||||
if (pty) {
|
||||
// These env vars shouldn't propagate to the child process. #6764
|
||||
@@ -6376,42 +6333,21 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
size_t n;
|
||||
char **addrs = server_address_list(&n);
|
||||
|
||||
Arena arena = ARENA_EMPTY;
|
||||
// Passed to vim._core.server.serverlist() to avoid duplicates
|
||||
Array addrs_arr = arena_array(&arena, n);
|
||||
|
||||
// Copy addrs into a linked list.
|
||||
list_T *const l = tv_list_alloc_ret(rettv, (ptrdiff_t)n);
|
||||
// Get the (internal) address list as a typval to pass to Lua.
|
||||
typval_T addrs_tv;
|
||||
tv_list_alloc_ret(&addrs_tv, (ptrdiff_t)n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
tv_list_append_allocated_string(l, addrs[i]);
|
||||
ADD_C(addrs_arr, CSTR_AS_OBJ(addrs[i]));
|
||||
tv_list_append_allocated_string(addrs_tv.vval.v_list, addrs[i]);
|
||||
}
|
||||
|
||||
if (!(argvars[0].v_type == VAR_DICT && tv_dict_get_bool(argvars[0].vval.v_dict, "peer", false))) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
MAXSIZE_TEMP_ARRAY(args, 1);
|
||||
ADD_C(args, ARRAY_OBJ(addrs_arr));
|
||||
|
||||
Error err = ERROR_INIT;
|
||||
Object rv = NLUA_EXEC_STATIC("return require('vim._core.server').serverlist(...)",
|
||||
args, kRetObject,
|
||||
&arena, &err);
|
||||
|
||||
if (ERROR_SET(&err)) {
|
||||
ELOG("vim._core.serverlist failed: %s", err.msg);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < rv.data.array.size; i++) {
|
||||
char *curr_server = rv.data.array.items[i].data.string.data;
|
||||
tv_list_append_string(l, curr_server, -1);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
xfree(addrs);
|
||||
arena_mem_free(arena_finish(&arena));
|
||||
|
||||
// Lua handles options (e.g. {peer=true}), peer discovery, and returns combined list.
|
||||
typval_T opts = argvars[0].v_type != VAR_UNKNOWN ? argvars[0] : (typval_T){ .v_type = VAR_SPECIAL,
|
||||
.vval.v_special =
|
||||
kSpecialVarNull };
|
||||
typval_T lua_args[] = { opts, addrs_tv, { .v_type = VAR_UNKNOWN } };
|
||||
nlua_call_vimfn("vim._core.server", "serverlist", lua_args, rettv);
|
||||
tv_clear(&addrs_tv);
|
||||
}
|
||||
|
||||
/// "serverstart()" function
|
||||
|
||||
@@ -1741,7 +1741,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
|
||||
if (len > 0) {
|
||||
error = FCERR_NONE;
|
||||
argv_add_base(funcexe->fe_basetv, &argvars, &argcount, argv, &argv_base);
|
||||
nlua_typval_call(funcname, (size_t)len, argvars, argcount, rettv);
|
||||
nlua_call_typval(funcname, (size_t)len, argvars, argcount, rettv);
|
||||
} else {
|
||||
// v:lua was called directly; show its name in the emsg
|
||||
XFREE_CLEAR(name);
|
||||
@@ -1827,7 +1827,7 @@ int call_simple_luafunc(const char *funcname, size_t len, typval_T *rettv)
|
||||
|
||||
typval_T argvars[1];
|
||||
argvars[0].v_type = VAR_UNKNOWN;
|
||||
nlua_typval_call(funcname, len, argvars, 0, rettv);
|
||||
nlua_call_typval(funcname, len, argvars, 0, rettv);
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -8294,41 +8294,13 @@ static void ex_terminal(exarg_T *eap)
|
||||
/// ":log {name}"
|
||||
static void ex_log(exarg_T *eap)
|
||||
{
|
||||
Error err = ERROR_INIT;
|
||||
MAXSIZE_TEMP_ARRAY(args, 2);
|
||||
|
||||
char mods[1024];
|
||||
size_t mods_len = 0;
|
||||
mods[0] = NUL;
|
||||
|
||||
if (cmdmod.cmod_tab > 0 || cmdmod.cmod_split != 0) {
|
||||
bool multi_mods = false;
|
||||
mods_len = add_win_cmd_modifiers(mods, &cmdmod, &multi_mods);
|
||||
assert(mods_len < sizeof(mods));
|
||||
}
|
||||
ADD_C(args, CSTR_AS_OBJ(eap->arg));
|
||||
ADD_C(args, STRING_OBJ(((String){ .data = mods, .size = mods_len })));
|
||||
|
||||
NLUA_EXEC_STATIC("require'vim._core.ex_cmd'.ex_log(...)", args, kRetNilBool, NULL, &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg_multiline(err.msg, "lua_error", HLF_E, true);
|
||||
}
|
||||
api_clear_error(&err);
|
||||
nlua_call_excmd("vim._core.ex_cmd", "ex_log", eap, &cmdmod);
|
||||
}
|
||||
|
||||
/// ":lsp {subcmd} {clients}"
|
||||
static void ex_lsp(exarg_T *eap)
|
||||
{
|
||||
Error err = ERROR_INIT;
|
||||
MAXSIZE_TEMP_ARRAY(args, 1);
|
||||
|
||||
ADD_C(args, CSTR_AS_OBJ(eap->arg));
|
||||
|
||||
NLUA_EXEC_STATIC("require'vim._core.ex_cmd'.ex_lsp(...)", args, kRetNilBool, NULL, &err);
|
||||
if (ERROR_SET(&err)) {
|
||||
emsg_multiline(err.msg, "lua_error", HLF_E, true);
|
||||
}
|
||||
api_clear_error(&err);
|
||||
nlua_call_excmd("vim._core.ex_cmd", "ex_lsp", eap, &cmdmod);
|
||||
}
|
||||
|
||||
/// ":fclose"
|
||||
|
||||
@@ -111,6 +111,123 @@ typedef struct {
|
||||
} \
|
||||
}
|
||||
|
||||
/// Pushes cmdmod_T as a table (Lua type: `vim.api.keyset.cmd_mods`) onto the stack.
|
||||
static void nlua_push_cmdmod(lua_State *lstate, const cmdmod_T *cmod)
|
||||
{
|
||||
lua_newtable(lstate);
|
||||
|
||||
lua_pushinteger(lstate, cmod->cmod_tab - 1);
|
||||
lua_setfield(lstate, -2, "tab");
|
||||
|
||||
lua_pushinteger(lstate, cmod->cmod_verbose - 1);
|
||||
lua_setfield(lstate, -2, "verbose");
|
||||
|
||||
if (cmod->cmod_split & WSP_ABOVE) {
|
||||
lua_pushstring(lstate, "aboveleft");
|
||||
} else if (cmod->cmod_split & WSP_BELOW) {
|
||||
lua_pushstring(lstate, "belowright");
|
||||
} else if (cmod->cmod_split & WSP_TOP) {
|
||||
lua_pushstring(lstate, "topleft");
|
||||
} else if (cmod->cmod_split & WSP_BOT) {
|
||||
lua_pushstring(lstate, "botright");
|
||||
} else {
|
||||
lua_pushstring(lstate, "");
|
||||
}
|
||||
lua_setfield(lstate, -2, "split");
|
||||
|
||||
lua_pushboolean(lstate, cmod->cmod_split & WSP_VERT);
|
||||
lua_setfield(lstate, -2, "vertical");
|
||||
lua_pushboolean(lstate, cmod->cmod_split & WSP_HOR);
|
||||
lua_setfield(lstate, -2, "horizontal");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_SILENT);
|
||||
lua_setfield(lstate, -2, "silent");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_ERRSILENT);
|
||||
lua_setfield(lstate, -2, "emsg_silent");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_UNSILENT);
|
||||
lua_setfield(lstate, -2, "unsilent");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_SANDBOX);
|
||||
lua_setfield(lstate, -2, "sandbox");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_NOAUTOCMD);
|
||||
lua_setfield(lstate, -2, "noautocmd");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_BROWSE);
|
||||
lua_setfield(lstate, -2, "browse");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_CONFIRM);
|
||||
lua_setfield(lstate, -2, "confirm");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_HIDE);
|
||||
lua_setfield(lstate, -2, "hide");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPALT);
|
||||
lua_setfield(lstate, -2, "keepalt");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPJUMPS);
|
||||
lua_setfield(lstate, -2, "keepjumps");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPMARKS);
|
||||
lua_setfield(lstate, -2, "keepmarks");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_KEEPPATTERNS);
|
||||
lua_setfield(lstate, -2, "keeppatterns");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_LOCKMARKS);
|
||||
lua_setfield(lstate, -2, "lockmarks");
|
||||
lua_pushboolean(lstate, cmod->cmod_flags & CMOD_NOSWAPFILE);
|
||||
lua_setfield(lstate, -2, "noswapfile");
|
||||
}
|
||||
|
||||
/// Pushes common exarg_T fields (bang, line1, line2, …) onto a table at the top of the stack.
|
||||
static void nlua_push_eap(lua_State *lstate, exarg_T *eap, const cmdmod_T *cmod)
|
||||
{
|
||||
lua_pushstring(lstate, eap->arg);
|
||||
lua_setfield(lstate, -2, "args");
|
||||
|
||||
lua_pushboolean(lstate, eap->forceit);
|
||||
lua_setfield(lstate, -2, "bang");
|
||||
|
||||
lua_pushinteger(lstate, eap->line1);
|
||||
lua_setfield(lstate, -2, "line1");
|
||||
|
||||
lua_pushinteger(lstate, eap->line2);
|
||||
lua_setfield(lstate, -2, "line2");
|
||||
|
||||
lua_pushinteger(lstate, eap->addr_count);
|
||||
lua_setfield(lstate, -2, "range");
|
||||
|
||||
lua_pushinteger(lstate, eap->line2);
|
||||
lua_setfield(lstate, -2, "count");
|
||||
|
||||
char reg[2] = { (char)eap->regname, NUL };
|
||||
lua_pushstring(lstate, reg);
|
||||
lua_setfield(lstate, -2, "reg");
|
||||
|
||||
nlua_push_cmdmod(lstate, cmod);
|
||||
lua_setfield(lstate, -2, "smods");
|
||||
}
|
||||
|
||||
/// Calls Lua to implement an excmd. Passes `eap` + `cmdmod` to Lua as a dict arg, which is arranged
|
||||
/// to match the Lua type `vim.api.keyset.create_user_command.command_args`.
|
||||
///
|
||||
/// @param module Lua module name, e.g. "vim._core.ex_cmd".
|
||||
/// @param func Function name in the module, e.g. "ex_log".
|
||||
/// @param eap Excmd args.
|
||||
/// @param cmod Excmd mods.
|
||||
void nlua_call_excmd(const char *module, const char *func, exarg_T *eap, const cmdmod_T *cmod)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
|
||||
lua_getglobal(lstate, "require");
|
||||
lua_pushstring(lstate, module);
|
||||
if (lua_pcall(lstate, 1, 1, 0) != 0) {
|
||||
semsg("E5108: %s", lua_tostring(lstate, -1));
|
||||
lua_pop(lstate, 1);
|
||||
return;
|
||||
}
|
||||
lua_getfield(lstate, -1, func);
|
||||
lua_remove(lstate, -2);
|
||||
|
||||
lua_newtable(lstate);
|
||||
nlua_push_eap(lstate, eap, cmod);
|
||||
|
||||
if (nlua_pcall(lstate, 1, 0)) {
|
||||
semsg("E5108: %s", lua_tostring(lstate, -1));
|
||||
lua_pop(lstate, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#if __has_feature(address_sanitizer)
|
||||
static bool nlua_track_refs = false;
|
||||
# define NLUA_TRACK_REFS
|
||||
@@ -1191,6 +1308,7 @@ static int nlua_debug(lua_State *lstate)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// "vim.in_fast_event()" function.
|
||||
int nlua_in_fast_event(lua_State *lstate)
|
||||
{
|
||||
lua_pushboolean(lstate, in_fast_callback > 0);
|
||||
@@ -1479,7 +1597,17 @@ void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret
|
||||
}
|
||||
}
|
||||
|
||||
void nlua_typval_call(const char *str, size_t len, typval_T *const args, int argcount,
|
||||
/// Calls a Lua function by name with typval args. Used by v:lua.func().
|
||||
///
|
||||
/// Builds "return func(...)" and executes it via luaL_loadbuffer.
|
||||
/// Converts args via nlua_push_typval, result via nlua_pop_typval.
|
||||
///
|
||||
/// @param str Lua expression (function name), e.g. "my_func" or "mod.func".
|
||||
/// @param len Length of str.
|
||||
/// @param args typval_T arguments.
|
||||
/// @param argcount Number of arguments.
|
||||
/// @param ret_tv Return value (always set).
|
||||
void nlua_call_typval(const char *str, size_t len, typval_T *const args, int argcount,
|
||||
typval_T *ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
@@ -1506,6 +1634,7 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls a Lua completion function (stored as LuaRef) for cmdline expansion.
|
||||
void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
@@ -1524,6 +1653,7 @@ void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv)
|
||||
nlua_pop_typval(lstate, ret_tv);
|
||||
}
|
||||
|
||||
/// Executes a Lua chunk with typval arguments and optional typval return.
|
||||
static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name,
|
||||
typval_T *const args, int argcount, bool special, typval_T *ret_tv)
|
||||
{
|
||||
@@ -1537,13 +1667,19 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name
|
||||
|
||||
lua_State *const lstate = global_lstate;
|
||||
|
||||
// 1. Compile lcmd into a Lua chunk (function), push it on the stack.
|
||||
//
|
||||
// TODO(justinmk): Cache the chunk in LUA_REGISTRYINDEX? But one-off callers (:lua, luaeval) may
|
||||
// bloat the cache. Could add a `cache:boolean` param.
|
||||
if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) {
|
||||
nlua_error(lstate, _("E5107: Lua: %.*s"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Push typval args as Lua values. Stack: [chunk, arg1, ..., argN].
|
||||
PUSH_ALL_TYPVALS(lstate, args, argcount, special);
|
||||
|
||||
// 3. Call: chunk(arg1, ..., argN). Essentially loadstring().
|
||||
if (nlua_pcall(lstate, argcount, ret_tv ? 1 : 0)) {
|
||||
nlua_error(lstate, _("E5108: Lua: %.*s"));
|
||||
return;
|
||||
@@ -1554,6 +1690,7 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute Lua code from a growarray of lines. Used by ":lua << EOF".
|
||||
void nlua_exec_ga(garray_T *ga, char *name)
|
||||
{
|
||||
char *code = ga_concat_strings(ga, "\n");
|
||||
@@ -1629,6 +1766,29 @@ Object nlua_exec(const String str, const char *chunkname, const Array args, LuaR
|
||||
return nlua_call_pop_retval(lstate, mode, arena, top, err);
|
||||
}
|
||||
|
||||
/// Calls Lua to implement a "vimfn" ("f_xx"/"eval"/"builtin") function.
|
||||
///
|
||||
/// Converts argvars directly to Lua values (no Object intermediate), calls the Lua function, and
|
||||
/// converts the result back to typval_T.
|
||||
///
|
||||
/// @param module Lua module name, e.g. "vim._core.server".
|
||||
/// @param func Function name in the module, e.g. "serverlist".
|
||||
/// @param argvars typval args (VAR_UNKNOWN-terminated).
|
||||
/// @param rettv Return value (caller must tv_clear), or NULL to discard.
|
||||
void nlua_call_vimfn(const char *module, const char *func, typval_T *argvars, typval_T *rettv)
|
||||
{
|
||||
int argcount = 0;
|
||||
for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) {
|
||||
argcount++;
|
||||
}
|
||||
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "return require('%s').%s(...)", module, func);
|
||||
|
||||
nlua_typval_exec(buf, strlen(buf), module, argvars, argcount, false, rettv);
|
||||
}
|
||||
|
||||
/// Checks if a LuaRef refers to a function.
|
||||
bool nlua_ref_is_function(LuaRef ref)
|
||||
{
|
||||
lua_State *const lstate = global_lstate;
|
||||
@@ -1662,6 +1822,7 @@ static int mode_ret(LuaRetMode mode)
|
||||
return mode == kRetMulti ? LUA_MULTRET : 1;
|
||||
}
|
||||
|
||||
/// Like nlua_call_ref, but with an option to run in fast (api-fast) context.
|
||||
Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode,
|
||||
Arena *arena, Error *err)
|
||||
{
|
||||
@@ -2054,6 +2215,7 @@ static int nlua_is_thread(lua_State *lstate)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Check if a typval dict/list originated from Lua (has a LuaRef).
|
||||
bool nlua_is_table_from_lua(const typval_T *const arg)
|
||||
{
|
||||
if (arg->v_type == VAR_DICT) {
|
||||
@@ -2244,22 +2406,19 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
|
||||
nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref);
|
||||
|
||||
lua_newtable(lstate);
|
||||
nlua_push_eap(lstate, eap, &cmdmod);
|
||||
|
||||
lua_pushstring(lstate, cmd->uc_name);
|
||||
lua_setfield(lstate, -2, "name");
|
||||
|
||||
lua_pushboolean(lstate, eap->forceit == 1);
|
||||
lua_setfield(lstate, -2, "bang");
|
||||
|
||||
lua_pushinteger(lstate, eap->line1);
|
||||
lua_setfield(lstate, -2, "line1");
|
||||
|
||||
lua_pushinteger(lstate, eap->line2);
|
||||
lua_setfield(lstate, -2, "line2");
|
||||
// Override count with uc_def fallback for user commands.
|
||||
if (eap->addr_count == 0) {
|
||||
lua_pushinteger(lstate, cmd->uc_def);
|
||||
lua_setfield(lstate, -2, "count");
|
||||
}
|
||||
|
||||
lua_newtable(lstate); // f-args table
|
||||
lua_pushstring(lstate, eap->arg);
|
||||
lua_pushvalue(lstate, -1); // Reference for potential use on f-args
|
||||
lua_setfield(lstate, -4, "args");
|
||||
lua_pushstring(lstate, eap->arg); // for f-args splitting below
|
||||
|
||||
// Split args by unescaped whitespace |<f-args>| (nargs dependent)
|
||||
if (cmd->uc_argt & EX_NOSPC) {
|
||||
@@ -2298,20 +2457,6 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
|
||||
}
|
||||
lua_setfield(lstate, -2, "fargs");
|
||||
|
||||
char reg[2] = { (char)eap->regname, NUL };
|
||||
lua_pushstring(lstate, reg);
|
||||
lua_setfield(lstate, -2, "reg");
|
||||
|
||||
lua_pushinteger(lstate, eap->addr_count);
|
||||
lua_setfield(lstate, -2, "range");
|
||||
|
||||
if (eap->addr_count > 0) {
|
||||
lua_pushinteger(lstate, eap->line2);
|
||||
} else {
|
||||
lua_pushinteger(lstate, cmd->uc_def);
|
||||
}
|
||||
lua_setfield(lstate, -2, "count");
|
||||
|
||||
char nargs[2];
|
||||
if (cmd->uc_argt & EX_EXTRA) {
|
||||
if (cmd->uc_argt & EX_NOSPC) {
|
||||
@@ -2332,6 +2477,8 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
|
||||
lua_pushstring(lstate, nargs);
|
||||
lua_setfield(lstate, -2, "nargs");
|
||||
|
||||
// User commands also get a string "mods" field (in addition to "smods" from nlua_push_eap).
|
||||
//
|
||||
// The size of this buffer is chosen empirically to be large enough to hold
|
||||
// every possible modifier (with room to spare). If the list of possible
|
||||
// modifiers grows this may need to be updated.
|
||||
@@ -2340,66 +2487,6 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
|
||||
lua_pushstring(lstate, buf);
|
||||
lua_setfield(lstate, -2, "mods");
|
||||
|
||||
lua_newtable(lstate); // smods table
|
||||
|
||||
lua_pushinteger(lstate, cmdmod.cmod_tab - 1);
|
||||
lua_setfield(lstate, -2, "tab");
|
||||
|
||||
lua_pushinteger(lstate, cmdmod.cmod_verbose - 1);
|
||||
lua_setfield(lstate, -2, "verbose");
|
||||
|
||||
if (cmdmod.cmod_split & WSP_ABOVE) {
|
||||
lua_pushstring(lstate, "aboveleft");
|
||||
} else if (cmdmod.cmod_split & WSP_BELOW) {
|
||||
lua_pushstring(lstate, "belowright");
|
||||
} else if (cmdmod.cmod_split & WSP_TOP) {
|
||||
lua_pushstring(lstate, "topleft");
|
||||
} else if (cmdmod.cmod_split & WSP_BOT) {
|
||||
lua_pushstring(lstate, "botright");
|
||||
} else {
|
||||
lua_pushstring(lstate, "");
|
||||
}
|
||||
lua_setfield(lstate, -2, "split");
|
||||
|
||||
lua_pushboolean(lstate, cmdmod.cmod_split & WSP_VERT);
|
||||
lua_setfield(lstate, -2, "vertical");
|
||||
lua_pushboolean(lstate, cmdmod.cmod_split & WSP_HOR);
|
||||
lua_setfield(lstate, -2, "horizontal");
|
||||
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SILENT);
|
||||
lua_setfield(lstate, -2, "silent");
|
||||
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_ERRSILENT);
|
||||
lua_setfield(lstate, -2, "emsg_silent");
|
||||
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_UNSILENT);
|
||||
lua_setfield(lstate, -2, "unsilent");
|
||||
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SANDBOX);
|
||||
lua_setfield(lstate, -2, "sandbox");
|
||||
lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_NOAUTOCMD);
|
||||
lua_setfield(lstate, -2, "noautocmd");
|
||||
|
||||
typedef struct {
|
||||
int flag;
|
||||
char *name;
|
||||
} mod_entry_T;
|
||||
static mod_entry_T mod_entries[] = {
|
||||
{ CMOD_BROWSE, "browse" },
|
||||
{ CMOD_CONFIRM, "confirm" },
|
||||
{ CMOD_HIDE, "hide" },
|
||||
{ CMOD_KEEPALT, "keepalt" },
|
||||
{ CMOD_KEEPJUMPS, "keepjumps" },
|
||||
{ CMOD_KEEPMARKS, "keepmarks" },
|
||||
{ CMOD_KEEPPATTERNS, "keeppatterns" },
|
||||
{ CMOD_LOCKMARKS, "lockmarks" },
|
||||
{ CMOD_NOSWAPFILE, "noswapfile" }
|
||||
};
|
||||
|
||||
// The modifiers that are simple flags
|
||||
for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
|
||||
lua_pushboolean(lstate, cmdmod.cmod_flags & mod_entries[i].flag);
|
||||
lua_setfield(lstate, -2, mod_entries[i].name);
|
||||
}
|
||||
|
||||
lua_setfield(lstate, -2, "smods");
|
||||
|
||||
if (preview) {
|
||||
lua_pushinteger(lstate, cmdpreview_get_ns());
|
||||
|
||||
|
||||
@@ -223,40 +223,6 @@ int os_unsetenv(const char *name)
|
||||
return r == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
/// Returns number of variables in the current environment variables block
|
||||
size_t os_get_fullenv_size(void)
|
||||
{
|
||||
size_t len = 0;
|
||||
#ifdef MSWIN
|
||||
wchar_t *envstrings = GetEnvironmentStringsW();
|
||||
wchar_t *p = envstrings;
|
||||
size_t l;
|
||||
if (!envstrings) {
|
||||
return len;
|
||||
}
|
||||
// GetEnvironmentStringsW() result has this format:
|
||||
// var1=value1\0var2=value2\0...varN=valueN\0\0
|
||||
while ((l = wcslen(p)) != 0) {
|
||||
p += l + 1;
|
||||
len++;
|
||||
}
|
||||
|
||||
FreeEnvironmentStringsW(envstrings);
|
||||
#else
|
||||
# ifdef HAVE__NSGETENVIRON
|
||||
char **environ = *_NSGetEnviron();
|
||||
# else
|
||||
extern char **environ;
|
||||
# endif
|
||||
|
||||
while (environ[len] != NULL) {
|
||||
len++;
|
||||
}
|
||||
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
void os_free_fullenv(char **env)
|
||||
{
|
||||
if (!env) {
|
||||
@@ -268,51 +234,6 @@ void os_free_fullenv(char **env)
|
||||
xfree(env);
|
||||
}
|
||||
|
||||
/// Copies the current environment variables into the given array, `env`. Each
|
||||
/// array element is of the form "NAME=VALUE".
|
||||
/// Result must be freed by the caller.
|
||||
///
|
||||
/// @param[out] env array to populate with environment variables
|
||||
/// @param env_size size of `env`, @see os_fullenv_size
|
||||
void os_copy_fullenv(char **env, size_t env_size)
|
||||
{
|
||||
#ifdef MSWIN
|
||||
wchar_t *envstrings = GetEnvironmentStringsW();
|
||||
if (!envstrings) {
|
||||
return;
|
||||
}
|
||||
wchar_t *p = envstrings;
|
||||
size_t i = 0;
|
||||
size_t l;
|
||||
// GetEnvironmentStringsW() result has this format:
|
||||
// var1=value1\0var2=value2\0...varN=valueN\0\0
|
||||
while ((l = wcslen(p)) != 0 && i < env_size) {
|
||||
char *utf8_str;
|
||||
int conversion_result = utf16_to_utf8(p, -1, &utf8_str);
|
||||
if (conversion_result != 0) {
|
||||
semsg("utf16_to_utf8 failed: %d", conversion_result);
|
||||
break;
|
||||
}
|
||||
p += l + 1;
|
||||
|
||||
env[i] = utf8_str;
|
||||
i++;
|
||||
}
|
||||
|
||||
FreeEnvironmentStringsW(envstrings);
|
||||
#else
|
||||
# ifdef HAVE__NSGETENVIRON
|
||||
char **environ = *_NSGetEnviron();
|
||||
# else
|
||||
extern char **environ;
|
||||
# endif
|
||||
|
||||
for (size_t i = 0; i < env_size && environ[i] != NULL; i++) {
|
||||
env[i] = xstrdup(environ[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Copy value of the environment variable at `index` in the current
|
||||
/// environment variables block.
|
||||
/// Result must be freed by the caller.
|
||||
|
||||
@@ -31,9 +31,10 @@ typedef double float_T;
|
||||
|
||||
typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler;
|
||||
|
||||
/// vimfn metatdata defined in `src/nvim/eval.lua`.
|
||||
typedef union {
|
||||
float_T (*func_float)(float_T);
|
||||
const MsgpackRpcRequestHandler *func_api; // Vimscript bridge to API fn.
|
||||
const MsgpackRpcRequestHandler *func_api; // Vimscript bridge to API fn (eval=true in eval.lua).
|
||||
const char *func_lua; ///< Lua-implemented vimfn.
|
||||
void *null;
|
||||
} EvalFuncData;
|
||||
|
||||
@@ -29,7 +29,7 @@ describe(':lsp', function()
|
||||
env = { VIMRUNTIME = 'non-existent' },
|
||||
}
|
||||
t.matches(
|
||||
[[Vim%(lsp%):Lua: .*module 'vim%.lsp' not found:]],
|
||||
[[Vim%(lsp%):E%d+: .*module 'vim%.lsp' not found:]],
|
||||
vim.split(t.pcall_err(n.command, 'lsp enable dummy'), '\n')[1]
|
||||
)
|
||||
end)
|
||||
|
||||
@@ -11,18 +11,38 @@ local command = n.command
|
||||
local eval = n.eval
|
||||
local setenv = n.fn.setenv
|
||||
|
||||
describe('environment variables', function()
|
||||
it('environ() handles empty env variable', function()
|
||||
clear({ env = { EMPTY_VAR = '' } })
|
||||
eq('', environ()['EMPTY_VAR'])
|
||||
eq(nil, environ()['DOES_NOT_EXIST'])
|
||||
end)
|
||||
|
||||
describe('vim.fn.environ()', function()
|
||||
it('exists() handles empty env variable', function()
|
||||
clear({ env = { EMPTY_VAR = '' } })
|
||||
eq(1, exists('$EMPTY_VAR'))
|
||||
eq(0, exists('$DOES_NOT_EXIST'))
|
||||
end)
|
||||
|
||||
it('handles empty env variable', function()
|
||||
clear({ env = { EMPTY_VAR = '' } })
|
||||
eq('', environ()['EMPTY_VAR'])
|
||||
-- vim.env returns nil if the value is empty string. 🤷
|
||||
eq(vim.NIL, n.exec_lua('return vim.env.EMPTY_VAR'))
|
||||
eq(nil, environ()['DOES_NOT_EXIST'])
|
||||
eq(vim.NIL, n.exec_lua('return vim.env.DOES_NOT_EXIST'))
|
||||
end)
|
||||
|
||||
it('results match getenv()', function()
|
||||
clear()
|
||||
eq(
|
||||
true,
|
||||
n.exec_lua([[
|
||||
local env = vim.fn.environ()
|
||||
assert(vim.tbl_count(env) > 10, 'environ() should have some env vars!')
|
||||
for k, v in pairs(env) do
|
||||
if v ~= '' and vim.fn.getenv(k) ~= v then
|
||||
error(('environ()[%q] = %q, but vim.fn.getenv(%q) = %q'):format(k, v, k, vim.fn.getenv(k)))
|
||||
end
|
||||
end
|
||||
return true
|
||||
]])
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('empty $HOME', function()
|
||||
|
||||
Reference in New Issue
Block a user