mirror of
https://github.com/neovim/neovim.git
synced 2025-09-11 13:58:18 +00:00

This is a refactor extracted from vim-patch 9.0.0067: cannot show virtual text The logic for inline virtual text is going to be different in nvim than text property based text in vim, but this refactor is still useful, as calculation of displayed linesize is going to be stateful in a similar way.
10017 lines
271 KiB
C
10017 lines
271 KiB
C
// This is an open source non-commercial project. Dear PVS-Studio, please check
|
|
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
|
|
#include <float.h>
|
|
#include <math.h>
|
|
|
|
#include "nvim/api/private/converter.h"
|
|
#include "nvim/api/private/helpers.h"
|
|
#include "nvim/api/vim.h"
|
|
#include "nvim/arglist.h"
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/assert.h"
|
|
#include "nvim/buffer.h"
|
|
#include "nvim/change.h"
|
|
#include "nvim/channel.h"
|
|
#include "nvim/charset.h"
|
|
#include "nvim/cmdexpand.h"
|
|
#include "nvim/cmdhist.h"
|
|
#include "nvim/context.h"
|
|
#include "nvim/cursor.h"
|
|
#include "nvim/diff.h"
|
|
#include "nvim/digraph.h"
|
|
#include "nvim/edit.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval/decode.h"
|
|
#include "nvim/eval/encode.h"
|
|
#include "nvim/eval/executor.h"
|
|
#include "nvim/eval/funcs.h"
|
|
#include "nvim/eval/typval.h"
|
|
#include "nvim/eval/userfunc.h"
|
|
#include "nvim/eval/vars.h"
|
|
#include "nvim/ex_cmds.h"
|
|
#include "nvim/ex_docmd.h"
|
|
#include "nvim/ex_eval.h"
|
|
#include "nvim/ex_getln.h"
|
|
#include "nvim/file_search.h"
|
|
#include "nvim/fileio.h"
|
|
#include "nvim/fold.h"
|
|
#include "nvim/getchar.h"
|
|
#include "nvim/globals.h"
|
|
#include "nvim/highlight_group.h"
|
|
#include "nvim/if_cscope.h"
|
|
#include "nvim/indent.h"
|
|
#include "nvim/indent_c.h"
|
|
#include "nvim/input.h"
|
|
#include "nvim/insexpand.h"
|
|
#include "nvim/lua/executor.h"
|
|
#include "nvim/macros.h"
|
|
#include "nvim/mapping.h"
|
|
#include "nvim/mark.h"
|
|
#include "nvim/match.h"
|
|
#include "nvim/math.h"
|
|
#include "nvim/memline.h"
|
|
#include "nvim/menu.h"
|
|
#include "nvim/mouse.h"
|
|
#include "nvim/move.h"
|
|
#include "nvim/msgpack_rpc/channel.h"
|
|
#include "nvim/msgpack_rpc/server.h"
|
|
#include "nvim/ops.h"
|
|
#include "nvim/option.h"
|
|
#include "nvim/optionstr.h"
|
|
#include "nvim/os/dl.h"
|
|
#include "nvim/os/shell.h"
|
|
#include "nvim/path.h"
|
|
#include "nvim/plines.h"
|
|
#include "nvim/popupmenu.h"
|
|
#include "nvim/profile.h"
|
|
#include "nvim/quickfix.h"
|
|
#include "nvim/regexp.h"
|
|
#include "nvim/runtime.h"
|
|
#include "nvim/screen.h"
|
|
#include "nvim/search.h"
|
|
#include "nvim/sha256.h"
|
|
#include "nvim/sign.h"
|
|
#include "nvim/spell.h"
|
|
#include "nvim/spellsuggest.h"
|
|
#include "nvim/state.h"
|
|
#include "nvim/syntax.h"
|
|
#include "nvim/tag.h"
|
|
#include "nvim/testing.h"
|
|
#include "nvim/ui.h"
|
|
#include "nvim/undo.h"
|
|
#include "nvim/version.h"
|
|
#include "nvim/vim.h"
|
|
#include "nvim/window.h"
|
|
|
|
/// Describe data to return from find_some_match()
|
|
typedef enum {
|
|
kSomeMatch, ///< Data for match().
|
|
kSomeMatchEnd, ///< Data for matchend().
|
|
kSomeMatchList, ///< Data for matchlist().
|
|
kSomeMatchStr, ///< Data for matchstr().
|
|
kSomeMatchStrPos, ///< Data for matchstrpos().
|
|
} SomeMatchType;
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "eval/funcs.c.generated.h"
|
|
|
|
# ifdef _MSC_VER
|
|
// This prevents MSVC from replacing the functions with intrinsics,
|
|
// and causing errors when trying to get their addresses in funcs.generated.h
|
|
# pragma function(ceil)
|
|
# pragma function(floor)
|
|
# endif
|
|
|
|
PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
|
|
PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
|
|
# include "funcs.generated.h"
|
|
PRAGMA_DIAG_POP
|
|
PRAGMA_DIAG_POP
|
|
#endif
|
|
|
|
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
|
|
static char *e_invalwindow = N_("E957: Invalid window number");
|
|
static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
|
|
|
|
/// Dummy va_list for passing to vim_snprintf
|
|
///
|
|
/// Used because:
|
|
/// - passing a NULL pointer doesn't work when va_list isn't a pointer
|
|
/// - locally in the function results in a "used before set" warning
|
|
/// - using va_start() to initialize it gives "function with fixed args" error
|
|
static va_list dummy_ap;
|
|
|
|
/// Function given to ExpandGeneric() to obtain the list of internal
|
|
/// or user defined function names.
|
|
char *get_function_name(expand_T *xp, int idx)
|
|
{
|
|
static int intidx = -1;
|
|
|
|
if (idx == 0) {
|
|
intidx = -1;
|
|
}
|
|
if (intidx < 0) {
|
|
char_u *name = (char_u *)get_user_func_name(xp, idx);
|
|
if (name != NULL) {
|
|
if (*name != NUL && *name != '<'
|
|
&& STRNCMP("g:", xp->xp_pattern, 2) == 0) {
|
|
return cat_prefix_varname('g', (char *)name);
|
|
}
|
|
return (char *)name;
|
|
}
|
|
}
|
|
|
|
const char *const key = functions[++intidx].name;
|
|
if (!key) {
|
|
return NULL;
|
|
}
|
|
const size_t key_len = strlen(key);
|
|
memcpy(IObuff, key, key_len);
|
|
IObuff[key_len] = '(';
|
|
if (functions[intidx].max_argc == 0) {
|
|
IObuff[key_len + 1] = ')';
|
|
IObuff[key_len + 2] = NUL;
|
|
} else {
|
|
IObuff[key_len + 1] = NUL;
|
|
}
|
|
return (char *)IObuff;
|
|
}
|
|
|
|
/// Function given to ExpandGeneric() to obtain the list of internal or
|
|
/// user defined variable or function names.
|
|
char *get_expr_name(expand_T *xp, int idx)
|
|
{
|
|
static int intidx = -1;
|
|
|
|
if (idx == 0) {
|
|
intidx = -1;
|
|
}
|
|
if (intidx < 0) {
|
|
char_u *name = (char_u *)get_function_name(xp, idx);
|
|
if (name != NULL) {
|
|
return (char *)name;
|
|
}
|
|
}
|
|
return get_user_var_name(xp, ++intidx);
|
|
}
|
|
|
|
/// Find internal function in hash functions
|
|
///
|
|
/// @param[in] name Name of the function.
|
|
///
|
|
/// @return pointer to the function definition or NULL if not found.
|
|
const EvalFuncDef *find_internal_func(const char *const name)
|
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
size_t len = strlen(name);
|
|
int index = find_internal_func_hash(name, len);
|
|
return index >= 0 ? &functions[index] : NULL;
|
|
}
|
|
|
|
int call_internal_func(const char_u *const fname, const int argcount, typval_T *const argvars,
|
|
typval_T *const rettv)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
const EvalFuncDef *const fdef = find_internal_func((const char *)fname);
|
|
if (fdef == NULL) {
|
|
return ERROR_UNKNOWN;
|
|
} else if (argcount < fdef->min_argc) {
|
|
return ERROR_TOOFEW;
|
|
} else if (argcount > fdef->max_argc) {
|
|
return ERROR_TOOMANY;
|
|
}
|
|
argvars[argcount].v_type = VAR_UNKNOWN;
|
|
fdef->func(argvars, rettv, fdef->data);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
/// Invoke a method for base->method().
|
|
int call_internal_method(const char_u *const fname, const int argcount, typval_T *const argvars,
|
|
typval_T *const rettv, typval_T *const basetv)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
const EvalFuncDef *const fdef = find_internal_func((const char *)fname);
|
|
if (fdef == NULL) {
|
|
return ERROR_UNKNOWN;
|
|
} else if (fdef->base_arg == BASE_NONE) {
|
|
return ERROR_NOTMETHOD;
|
|
} else if (argcount + 1 < fdef->min_argc) {
|
|
return ERROR_TOOFEW;
|
|
} else if (argcount + 1 > fdef->max_argc) {
|
|
return ERROR_TOOMANY;
|
|
}
|
|
|
|
typval_T argv[MAX_FUNC_ARGS + 1];
|
|
const ptrdiff_t base_index = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1;
|
|
memcpy(argv, argvars, (size_t)base_index * sizeof(typval_T));
|
|
argv[base_index] = *basetv;
|
|
memcpy(argv + base_index + 1, argvars + base_index,
|
|
(size_t)(argcount - base_index) * sizeof(typval_T));
|
|
argv[argcount + 1].v_type = VAR_UNKNOWN;
|
|
|
|
fdef->func(argv, rettv, fdef->data);
|
|
return ERROR_NONE;
|
|
}
|
|
|
|
/// @return true for a non-zero Number and a non-empty String.
|
|
static int non_zero_arg(typval_T *argvars)
|
|
{
|
|
return ((argvars[0].v_type == VAR_NUMBER
|
|
&& argvars[0].vval.v_number != 0)
|
|
|| (argvars[0].v_type == VAR_BOOL
|
|
&& argvars[0].vval.v_bool == kBoolVarTrue)
|
|
|| (argvars[0].v_type == VAR_STRING
|
|
&& argvars[0].vval.v_string != NULL
|
|
&& *argvars[0].vval.v_string != NUL));
|
|
}
|
|
|
|
/// Apply a floating point C function on a typval with one float_T.
|
|
///
|
|
/// Some versions of glibc on i386 have an optimization that makes it harder to
|
|
/// call math functions indirectly from inside an inlined function, causing
|
|
/// compile-time errors. Avoid `inline` in that case. #3072
|
|
static void float_op_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
float_T f;
|
|
|
|
rettv->v_type = VAR_FLOAT;
|
|
if (tv_get_float_chk(argvars, &f)) {
|
|
rettv->vval.v_float = fptr.float_func(f);
|
|
} else {
|
|
rettv->vval.v_float = 0.0;
|
|
}
|
|
}
|
|
|
|
static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
MsgpackRpcRequestHandler handler = *fptr.api_handler;
|
|
|
|
Array args = ARRAY_DICT_INIT;
|
|
|
|
for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) {
|
|
ADD(args, vim_to_object(tv));
|
|
}
|
|
|
|
Error err = ERROR_INIT;
|
|
Arena res_arena = ARENA_EMPTY;
|
|
Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err);
|
|
|
|
if (ERROR_SET(&err)) {
|
|
semsg_multiline((const char *)e_api_error, err.msg);
|
|
goto end;
|
|
}
|
|
|
|
if (!object_to_vim(result, rettv, &err)) {
|
|
semsg(_("Error converting the call result: %s"), err.msg);
|
|
}
|
|
|
|
end:
|
|
api_free_array(args);
|
|
if (handler.arena_return) {
|
|
arena_mem_free(arena_finish(&res_arena));
|
|
} else {
|
|
api_free_object(result);
|
|
}
|
|
api_clear_error(&err);
|
|
}
|
|
|
|
/// "abs(expr)" function
|
|
static void f_abs(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type == VAR_FLOAT) {
|
|
float_op_wrapper(argvars, rettv, (EvalFuncData){ .float_func = &fabs });
|
|
} else {
|
|
bool error = false;
|
|
|
|
varnumber_T n = tv_get_number_chk(&argvars[0], &error);
|
|
if (error) {
|
|
rettv->vval.v_number = -1;
|
|
} else if (n > 0) {
|
|
rettv->vval.v_number = n;
|
|
} else {
|
|
rettv->vval.v_number = -n;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "add(list, item)" function
|
|
static void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = 1; // Default: failed.
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
list_T *const l = argvars[0].vval.v_list;
|
|
if (!var_check_lock(tv_list_locked(l), N_("add() argument"),
|
|
TV_TRANSLATE)) {
|
|
tv_list_append_tv(l, &argvars[1]);
|
|
tv_copy(&argvars[0], rettv);
|
|
}
|
|
} else if (argvars[0].v_type == VAR_BLOB) {
|
|
blob_T *const b = argvars[0].vval.v_blob;
|
|
if (b != NULL
|
|
&& !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) {
|
|
bool error = false;
|
|
const varnumber_T n = tv_get_number_chk(&argvars[1], &error);
|
|
|
|
if (!error) {
|
|
ga_append(&b->bv_ga, (char)n);
|
|
tv_copy(&argvars[0], rettv);
|
|
}
|
|
}
|
|
} else {
|
|
emsg(_(e_listblobreq));
|
|
}
|
|
}
|
|
|
|
/// "and(expr, expr)" function
|
|
static void f_and(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
|
|
& tv_get_number_chk(&argvars[1], NULL);
|
|
}
|
|
|
|
/// "api_info()" function
|
|
static void f_api_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
Dictionary metadata = api_metadata();
|
|
(void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL);
|
|
}
|
|
|
|
/// "append(lnum, string/list)" function
|
|
static void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const linenr_T lnum = tv_get_lnum(&argvars[0]);
|
|
|
|
set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv);
|
|
}
|
|
|
|
/// "appendbufline(buf, lnum, string/list)" function
|
|
static void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_T *const buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
rettv->vval.v_number = 1; // FAIL
|
|
} else {
|
|
const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
|
|
set_buffer_lines(buf, lnum, true, &argvars[2], rettv);
|
|
}
|
|
}
|
|
|
|
/// "atan2()" function
|
|
static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
float_T fx;
|
|
float_T fy;
|
|
|
|
rettv->v_type = VAR_FLOAT;
|
|
if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
|
|
rettv->vval.v_float = atan2(fx, fy);
|
|
} else {
|
|
rettv->vval.v_float = 0.0;
|
|
}
|
|
}
|
|
|
|
/// "browse(save, title, initdir, default)" function
|
|
static void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_string = NULL;
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "browsedir(title, initdir)" function
|
|
static void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
f_browse(argvars, rettv, fptr);
|
|
}
|
|
|
|
/// Find a buffer by number or exact name.
|
|
static buf_T *find_buffer(typval_T *avar)
|
|
{
|
|
buf_T *buf = NULL;
|
|
|
|
if (avar->v_type == VAR_NUMBER) {
|
|
buf = buflist_findnr((int)avar->vval.v_number);
|
|
} else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) {
|
|
buf = buflist_findname_exp(avar->vval.v_string);
|
|
if (buf == NULL) {
|
|
// No full path name match, try a match with a URL or a "nofile"
|
|
// buffer, these don't use the full path.
|
|
FOR_ALL_BUFFERS(bp) {
|
|
if (bp->b_fname != NULL
|
|
&& (path_with_url(bp->b_fname) || bt_nofilename(bp))
|
|
&& STRCMP(bp->b_fname, avar->vval.v_string) == 0) {
|
|
buf = bp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/// "bufadd(expr)" function
|
|
static void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char_u *name = (char_u *)tv_get_string(&argvars[0]);
|
|
|
|
rettv->vval.v_number = buflist_add(*name == NUL ? NULL : (char *)name, 0);
|
|
}
|
|
|
|
/// "bufexists(expr)" function
|
|
static void f_bufexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
|
|
}
|
|
|
|
/// "buflisted(expr)" function
|
|
static void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_T *buf;
|
|
|
|
buf = find_buffer(&argvars[0]);
|
|
rettv->vval.v_number = (buf != NULL && buf->b_p_bl);
|
|
}
|
|
|
|
/// "bufload(expr)" function
|
|
static void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr)
|
|
{
|
|
buf_T *buf = get_buf_arg(&argvars[0]);
|
|
|
|
if (buf != NULL && buf->b_ml.ml_mfp == NULL) {
|
|
aco_save_T aco;
|
|
|
|
aucmd_prepbuf(&aco, buf);
|
|
swap_exists_action = SEA_NONE;
|
|
open_buffer(false, NULL, 0);
|
|
aucmd_restbuf(&aco);
|
|
}
|
|
}
|
|
|
|
/// "bufloaded(expr)" function
|
|
static void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_T *buf;
|
|
|
|
buf = find_buffer(&argvars[0]);
|
|
rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
|
|
}
|
|
|
|
/// "bufname(expr)" function
|
|
static void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const buf_T *buf;
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
buf = curbuf;
|
|
} else {
|
|
buf = tv_get_buf_from_arg(&argvars[0]);
|
|
}
|
|
if (buf != NULL && buf->b_fname != NULL) {
|
|
rettv->vval.v_string = xstrdup(buf->b_fname);
|
|
}
|
|
}
|
|
|
|
/// "bufnr(expr)" function
|
|
static void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const buf_T *buf;
|
|
bool error = false;
|
|
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
buf = curbuf;
|
|
} else {
|
|
// Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found
|
|
// and the second argument isn't zero, but we want to return early if the
|
|
// first argument isn't a string or number so only one error is shown.
|
|
if (!tv_check_str_or_nr(&argvars[0])) {
|
|
return;
|
|
}
|
|
emsg_off++;
|
|
buf = tv_get_buf(&argvars[0], false);
|
|
emsg_off--;
|
|
}
|
|
|
|
// If the buffer isn't found and the second argument is not zero create a
|
|
// new buffer.
|
|
const char *name;
|
|
if (buf == NULL
|
|
&& argvars[1].v_type != VAR_UNKNOWN
|
|
&& tv_get_number_chk(&argvars[1], &error) != 0
|
|
&& !error
|
|
&& (name = tv_get_string_chk(&argvars[0])) != NULL) {
|
|
buf = buflist_new((char *)name, NULL, 1, 0);
|
|
}
|
|
|
|
if (buf != NULL) {
|
|
rettv->vval.v_number = buf->b_fnum;
|
|
}
|
|
}
|
|
|
|
static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
|
|
{
|
|
const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
|
|
if (buf == NULL) { // no need to search if invalid arg or buffer not found
|
|
rettv->vval.v_number = -1;
|
|
return;
|
|
}
|
|
|
|
int winnr = 0;
|
|
int winid;
|
|
bool found_buf = false;
|
|
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
|
winnr++;
|
|
if (wp->w_buffer == buf) {
|
|
found_buf = true;
|
|
winid = wp->handle;
|
|
break;
|
|
}
|
|
}
|
|
rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1);
|
|
}
|
|
|
|
/// "bufwinid(nr)" function
|
|
static void f_bufwinid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_win_common(argvars, rettv, false);
|
|
}
|
|
|
|
/// "bufwinnr(nr)" function
|
|
static void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_win_common(argvars, rettv, true);
|
|
}
|
|
|
|
/// Get buffer by number or pattern.
|
|
buf_T *tv_get_buf(typval_T *tv, int curtab_only)
|
|
{
|
|
if (tv->v_type == VAR_NUMBER) {
|
|
return buflist_findnr((int)tv->vval.v_number);
|
|
}
|
|
if (tv->v_type != VAR_STRING) {
|
|
return NULL;
|
|
}
|
|
|
|
char_u *name = (char_u *)tv->vval.v_string;
|
|
|
|
if (name == NULL || *name == NUL) {
|
|
return curbuf;
|
|
}
|
|
if (name[0] == '$' && name[1] == NUL) {
|
|
return lastbuf;
|
|
}
|
|
|
|
// Ignore 'magic' and 'cpoptions' here to make scripts portable
|
|
int save_magic = p_magic;
|
|
p_magic = true;
|
|
char *save_cpo = p_cpo;
|
|
p_cpo = empty_option;
|
|
|
|
buf_T *buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name),
|
|
true, false, curtab_only));
|
|
|
|
p_magic = save_magic;
|
|
p_cpo = save_cpo;
|
|
|
|
// If not found, try expanding the name, like done for bufexists().
|
|
if (buf == NULL) {
|
|
buf = find_buffer(tv);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/// Like tv_get_buf() but give an error message if the type is wrong.
|
|
buf_T *tv_get_buf_from_arg(typval_T *const tv) FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (!tv_check_str_or_nr(tv)) {
|
|
return NULL;
|
|
}
|
|
emsg_off++;
|
|
buf_T *const buf = tv_get_buf(tv, false);
|
|
emsg_off--;
|
|
return buf;
|
|
}
|
|
|
|
/// Get the buffer from "arg" and give an error and return NULL if it is not
|
|
/// valid.
|
|
buf_T *get_buf_arg(typval_T *arg)
|
|
{
|
|
emsg_off++;
|
|
buf_T *buf = tv_get_buf(arg, false);
|
|
emsg_off--;
|
|
if (buf == NULL) {
|
|
semsg(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/// "byte2line(byte)" function
|
|
static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
long boff = tv_get_number(&argvars[0]) - 1;
|
|
if (boff < 0) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0,
|
|
&boff, false);
|
|
}
|
|
}
|
|
|
|
static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
|
|
{
|
|
const char *const str = tv_get_string_chk(&argvars[0]);
|
|
varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
|
|
rettv->vval.v_number = -1;
|
|
if (str == NULL || idx < 0) {
|
|
return;
|
|
}
|
|
|
|
const char *t = str;
|
|
for (; idx > 0; idx--) {
|
|
if (*t == NUL) { // EOL reached.
|
|
return;
|
|
}
|
|
if (comp) {
|
|
t += utf_ptr2len(t);
|
|
} else {
|
|
t += utfc_ptr2len(t);
|
|
}
|
|
}
|
|
rettv->vval.v_number = (varnumber_T)(t - str);
|
|
}
|
|
|
|
/// "byteidx()" function
|
|
static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
byteidx(argvars, rettv, false);
|
|
}
|
|
|
|
/// "byteidxcomp()" function
|
|
static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
byteidx(argvars, rettv, true);
|
|
}
|
|
|
|
/// "call(func, arglist [, dict])" function
|
|
static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[1].v_type != VAR_LIST) {
|
|
emsg(_(e_listreq));
|
|
return;
|
|
}
|
|
if (argvars[1].vval.v_list == NULL) {
|
|
return;
|
|
}
|
|
|
|
bool owned = false;
|
|
char_u *func;
|
|
partial_T *partial = NULL;
|
|
if (argvars[0].v_type == VAR_FUNC) {
|
|
func = (char_u *)argvars[0].vval.v_string;
|
|
} else if (argvars[0].v_type == VAR_PARTIAL) {
|
|
partial = argvars[0].vval.v_partial;
|
|
func = (char_u *)partial_name(partial);
|
|
} else if (nlua_is_table_from_lua(&argvars[0])) {
|
|
// TODO(tjdevries): UnifiedCallback
|
|
func = nlua_register_table_as_callable(&argvars[0]);
|
|
owned = true;
|
|
} else {
|
|
func = (char_u *)tv_get_string(&argvars[0]);
|
|
}
|
|
|
|
if (*func == NUL) {
|
|
return; // type error or empty name
|
|
}
|
|
|
|
dict_T *selfdict = NULL;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
if (argvars[2].v_type != VAR_DICT) {
|
|
emsg(_(e_dictreq));
|
|
if (owned) {
|
|
func_unref(func);
|
|
}
|
|
return;
|
|
}
|
|
selfdict = argvars[2].vval.v_dict;
|
|
}
|
|
|
|
func_call(func, &argvars[1], partial, selfdict, rettv);
|
|
if (owned) {
|
|
func_unref(func);
|
|
}
|
|
}
|
|
|
|
/// "changenr()" function
|
|
static void f_changenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = curbuf->b_u_seq_cur;
|
|
}
|
|
|
|
/// "chanclose(id[, stream])" function
|
|
static void f_chanclose(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING
|
|
&& argvars[1].v_type != VAR_UNKNOWN)) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
ChannelPart part = kChannelPartAll;
|
|
if (argvars[1].v_type == VAR_STRING) {
|
|
char *stream = argvars[1].vval.v_string;
|
|
if (!strcmp(stream, "stdin")) {
|
|
part = kChannelPartStdin;
|
|
} else if (!strcmp(stream, "stdout")) {
|
|
part = kChannelPartStdout;
|
|
} else if (!strcmp(stream, "stderr")) {
|
|
part = kChannelPartStderr;
|
|
} else if (!strcmp(stream, "rpc")) {
|
|
part = kChannelPartRpc;
|
|
} else {
|
|
semsg(_("Invalid channel stream \"%s\""), stream);
|
|
return;
|
|
}
|
|
}
|
|
const char *error;
|
|
rettv->vval.v_number = channel_close((uint64_t)argvars[0].vval.v_number, part, &error);
|
|
if (!rettv->vval.v_number) {
|
|
emsg(error);
|
|
}
|
|
}
|
|
|
|
/// "chansend(id, data)" function
|
|
static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) {
|
|
// First argument is the channel id and second is the data to write
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
ptrdiff_t input_len = 0;
|
|
char *input = NULL;
|
|
if (argvars[1].v_type == VAR_BLOB) {
|
|
const blob_T *const b = argvars[1].vval.v_blob;
|
|
input_len = tv_blob_len(b);
|
|
if (input_len > 0) {
|
|
input = xmemdup(b->bv_ga.ga_data, (size_t)input_len);
|
|
}
|
|
} else {
|
|
input = save_tv_as_string(&argvars[1], &input_len, false);
|
|
}
|
|
|
|
if (!input) {
|
|
// Either the error has been handled by save_tv_as_string(),
|
|
// or there is no input to send.
|
|
return;
|
|
}
|
|
uint64_t id = (uint64_t)argvars[0].vval.v_number;
|
|
const char *error = NULL;
|
|
rettv->vval.v_number = (varnumber_T)channel_send(id, input, (size_t)input_len, true, &error);
|
|
if (error) {
|
|
emsg(error);
|
|
}
|
|
}
|
|
|
|
/// "char2nr(string)" function
|
|
static void f_char2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
if (!tv_check_num(&argvars[1])) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
rettv->vval.v_number = utf_ptr2char(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/// Get the current cursor column and store it in 'rettv'.
|
|
///
|
|
/// @return the character index of the column if 'charcol' is true,
|
|
/// otherwise the byte index of the column.
|
|
static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
|
|
{
|
|
colnr_T col = 0;
|
|
int fnum = curbuf->b_fnum;
|
|
|
|
pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol);
|
|
if (fp != NULL && fnum == curbuf->b_fnum) {
|
|
if (fp->col == MAXCOL) {
|
|
// '> can be MAXCOL, get the length of the line then
|
|
if (fp->lnum <= curbuf->b_ml.ml_line_count) {
|
|
col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
|
|
} else {
|
|
col = MAXCOL;
|
|
}
|
|
} else {
|
|
col = fp->col + 1;
|
|
// col(".") when the cursor is on the NUL at the end of the line
|
|
// because of "coladd" can be seen as an extra column.
|
|
if (virtual_active() && fp == &curwin->w_cursor) {
|
|
char *p = (char *)get_cursor_pos_ptr();
|
|
if (curwin->w_cursor.coladd >=
|
|
(colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) {
|
|
int l;
|
|
if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
|
|
col += l;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rettv->vval.v_number = col;
|
|
}
|
|
|
|
/// "charcol()" function
|
|
static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
get_col(argvars, rettv, true);
|
|
}
|
|
|
|
/// "charidx()" function
|
|
static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (argvars[0].v_type != VAR_STRING
|
|
|| argvars[1].v_type != VAR_NUMBER
|
|
|| (argvars[2].v_type != VAR_UNKNOWN
|
|
&& argvars[2].v_type != VAR_NUMBER
|
|
&& argvars[2].v_type != VAR_BOOL)) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
const char *str = tv_get_string_chk(&argvars[0]);
|
|
varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
|
|
if (str == NULL || idx < 0) {
|
|
return;
|
|
}
|
|
int countcc = 0;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
countcc = (int)tv_get_number(&argvars[2]);
|
|
}
|
|
if (countcc < 0 || countcc > 1) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
int (*ptr2len)(const char *);
|
|
if (countcc) {
|
|
ptr2len = utf_ptr2len;
|
|
} else {
|
|
ptr2len = utfc_ptr2len;
|
|
}
|
|
|
|
const char *p;
|
|
int len;
|
|
for (p = str, len = 0; p <= str + idx; len++) {
|
|
if (*p == NUL) {
|
|
return;
|
|
}
|
|
p += ptr2len(p);
|
|
}
|
|
|
|
rettv->vval.v_number = len > 0 ? len - 1 : 0;
|
|
}
|
|
|
|
/// "chdir(dir)" function
|
|
static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
if (argvars[0].v_type != VAR_STRING) {
|
|
// Returning an empty string means it failed.
|
|
// No error message, for historic reasons.
|
|
return;
|
|
}
|
|
|
|
// Return the current directory
|
|
char_u *cwd = xmalloc(MAXPATHL);
|
|
if (os_dirname(cwd, MAXPATHL) != FAIL) {
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
slash_adjust(cwd);
|
|
#endif
|
|
rettv->vval.v_string = (char *)vim_strsave(cwd);
|
|
}
|
|
xfree(cwd);
|
|
|
|
CdScope scope = kCdScopeGlobal;
|
|
if (curwin->w_localdir != NULL) {
|
|
scope = kCdScopeWindow;
|
|
} else if (curtab->tp_localdir != NULL) {
|
|
scope = kCdScopeTabpage;
|
|
}
|
|
|
|
if (!changedir_func(argvars[0].vval.v_string, scope)) {
|
|
// Directory change failed
|
|
XFREE_CLEAR(rettv->vval.v_string);
|
|
}
|
|
}
|
|
|
|
/// "cindent(lnum)" function
|
|
static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
pos_T pos = curwin->w_cursor;
|
|
linenr_T lnum = tv_get_lnum(argvars);
|
|
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
|
|
curwin->w_cursor.lnum = lnum;
|
|
rettv->vval.v_number = get_c_indent();
|
|
curwin->w_cursor = pos;
|
|
} else {
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
}
|
|
|
|
win_T *get_optional_window(typval_T *argvars, int idx)
|
|
{
|
|
win_T *win = curwin;
|
|
|
|
if (argvars[idx].v_type != VAR_UNKNOWN) {
|
|
win = find_win_by_nr_or_id(&argvars[idx]);
|
|
if (win == NULL) {
|
|
emsg(_(e_invalwindow));
|
|
return NULL;
|
|
}
|
|
}
|
|
return win;
|
|
}
|
|
|
|
/// "col(string)" function
|
|
static void f_col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
get_col(argvars, rettv, false);
|
|
}
|
|
|
|
/// "confirm(message, buttons[, default [, type]])" function
|
|
static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char buf[NUMBUFLEN];
|
|
char buf2[NUMBUFLEN];
|
|
const char *buttons = NULL;
|
|
int def = 1;
|
|
int type = VIM_GENERIC;
|
|
bool error = false;
|
|
|
|
const char *message = tv_get_string_chk(&argvars[0]);
|
|
if (message == NULL) {
|
|
error = true;
|
|
}
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
buttons = tv_get_string_buf_chk(&argvars[1], buf);
|
|
if (buttons == NULL) {
|
|
error = true;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
def = (int)tv_get_number_chk(&argvars[2], &error);
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
const char *typestr = tv_get_string_buf_chk(&argvars[3], buf2);
|
|
if (typestr == NULL) {
|
|
error = true;
|
|
} else {
|
|
switch (TOUPPER_ASC(*typestr)) {
|
|
case 'E':
|
|
type = VIM_ERROR; break;
|
|
case 'Q':
|
|
type = VIM_QUESTION; break;
|
|
case 'I':
|
|
type = VIM_INFO; break;
|
|
case 'W':
|
|
type = VIM_WARNING; break;
|
|
case 'G':
|
|
type = VIM_GENERIC; break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (buttons == NULL || *buttons == NUL) {
|
|
buttons = _("&Ok");
|
|
}
|
|
|
|
if (!error) {
|
|
rettv->vval.v_number = do_dialog(type, NULL, (char_u *)message, (char_u *)buttons, def, NULL,
|
|
false);
|
|
}
|
|
}
|
|
|
|
/// "copy()" function
|
|
static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
var_item_copy(NULL, &argvars[0], rettv, false, 0);
|
|
}
|
|
|
|
/// "count()" function
|
|
static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
long n = 0;
|
|
int ic = 0;
|
|
bool error = false;
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
ic = (int)tv_get_number_chk(&argvars[2], &error);
|
|
}
|
|
|
|
if (argvars[0].v_type == VAR_STRING) {
|
|
const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]);
|
|
const char_u *p = (char_u *)argvars[0].vval.v_string;
|
|
|
|
if (!error && expr != NULL && *expr != NUL && p != NULL) {
|
|
if (ic) {
|
|
const size_t len = STRLEN(expr);
|
|
|
|
while (*p != NUL) {
|
|
if (mb_strnicmp(p, expr, len) == 0) {
|
|
n++;
|
|
p += len;
|
|
} else {
|
|
MB_PTR_ADV(p);
|
|
}
|
|
}
|
|
} else {
|
|
char_u *next;
|
|
while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) {
|
|
n++;
|
|
p = next + STRLEN(expr);
|
|
}
|
|
}
|
|
}
|
|
} else if (argvars[0].v_type == VAR_LIST) {
|
|
list_T *l = argvars[0].vval.v_list;
|
|
|
|
if (l != NULL) {
|
|
listitem_T *li = tv_list_first(l);
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
long idx = tv_get_number_chk(&argvars[3], &error);
|
|
if (!error) {
|
|
li = tv_list_find(l, (int)idx);
|
|
if (li == NULL) {
|
|
semsg(_(e_listidx), (int64_t)idx);
|
|
}
|
|
}
|
|
}
|
|
if (error) {
|
|
li = NULL;
|
|
}
|
|
}
|
|
|
|
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
|
|
if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
} else if (argvars[0].v_type == VAR_DICT) {
|
|
dict_T *d = argvars[0].vval.v_dict;
|
|
|
|
if (d != NULL) {
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
emsg(_(e_invarg));
|
|
}
|
|
}
|
|
|
|
int todo = error ? 0 : (int)d->dv_hashtab.ht_used;
|
|
for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
|
|
if (!HASHITEM_EMPTY(hi)) {
|
|
todo--;
|
|
if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
semsg(_(e_listdictarg), "count()");
|
|
}
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
/// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
|
|
///
|
|
/// Checks the existence of a cscope connection.
|
|
static void f_cscope_connection(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int num = 0;
|
|
const char *dbpath = NULL;
|
|
const char *prepend = NULL;
|
|
char buf[NUMBUFLEN];
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN
|
|
&& argvars[1].v_type != VAR_UNKNOWN) {
|
|
num = (int)tv_get_number(&argvars[0]);
|
|
dbpath = tv_get_string(&argvars[1]);
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
prepend = tv_get_string_buf(&argvars[2], buf);
|
|
}
|
|
}
|
|
|
|
rettv->vval.v_number = cs_connection(num, (char_u *)dbpath,
|
|
(char_u *)prepend);
|
|
}
|
|
|
|
/// "ctxget([{index}])" function
|
|
static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
size_t index = 0;
|
|
if (argvars[0].v_type == VAR_NUMBER) {
|
|
index = (size_t)argvars[0].vval.v_number;
|
|
} else if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
semsg(_(e_invarg2), "expected nothing or a Number as an argument");
|
|
return;
|
|
}
|
|
|
|
Context *ctx = ctx_get(index);
|
|
if (ctx == NULL) {
|
|
semsg(_(e_invargNval), "index", "out of bounds");
|
|
return;
|
|
}
|
|
|
|
Dictionary ctx_dict = ctx_to_dict(ctx);
|
|
Error err = ERROR_INIT;
|
|
object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
|
|
api_free_dictionary(ctx_dict);
|
|
api_clear_error(&err);
|
|
}
|
|
|
|
/// "ctxpop()" function
|
|
static void f_ctxpop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (!ctx_restore(NULL, kCtxAll)) {
|
|
emsg(_("Context stack is empty"));
|
|
}
|
|
}
|
|
|
|
/// "ctxpush([{types}])" function
|
|
static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int types = kCtxAll;
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
types = 0;
|
|
TV_LIST_ITER(argvars[0].vval.v_list, li, {
|
|
typval_T *tv_li = TV_LIST_ITEM_TV(li);
|
|
if (tv_li->v_type == VAR_STRING) {
|
|
if (strequal((char *)tv_li->vval.v_string, "regs")) {
|
|
types |= kCtxRegs;
|
|
} else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
|
|
types |= kCtxJumps;
|
|
} else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
|
|
types |= kCtxBufs;
|
|
} else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
|
|
types |= kCtxGVars;
|
|
} else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
|
|
types |= kCtxSFuncs;
|
|
} else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
|
|
types |= kCtxFuncs;
|
|
}
|
|
}
|
|
});
|
|
} else if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
semsg(_(e_invarg2), "expected nothing or a List as an argument");
|
|
return;
|
|
}
|
|
ctx_save(NULL, types);
|
|
}
|
|
|
|
/// "ctxset({context}[, {index}])" function
|
|
static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_DICT) {
|
|
semsg(_(e_invarg2), "expected dictionary as first argument");
|
|
return;
|
|
}
|
|
|
|
size_t index = 0;
|
|
if (argvars[1].v_type == VAR_NUMBER) {
|
|
index = (size_t)argvars[1].vval.v_number;
|
|
} else if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
semsg(_(e_invarg2), "expected nothing or a Number as second argument");
|
|
return;
|
|
}
|
|
|
|
Context *ctx = ctx_get(index);
|
|
if (ctx == NULL) {
|
|
semsg(_(e_invargNval), "index", "out of bounds");
|
|
return;
|
|
}
|
|
|
|
int save_did_emsg = did_emsg;
|
|
did_emsg = false;
|
|
|
|
Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
|
|
Context tmp = CONTEXT_INIT;
|
|
ctx_from_dict(dict, &tmp);
|
|
|
|
if (did_emsg) {
|
|
ctx_free(&tmp);
|
|
} else {
|
|
ctx_free(ctx);
|
|
*ctx = tmp;
|
|
}
|
|
|
|
api_free_dictionary(dict);
|
|
did_emsg = save_did_emsg;
|
|
}
|
|
|
|
/// "ctxsize()" function
|
|
static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = (varnumber_T)ctx_size();
|
|
}
|
|
|
|
/// Set the cursor position.
|
|
/// If 'charcol' is true, then use the column number as a character offset.
|
|
/// Otherwise use the column number as a byte offset.
|
|
static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
|
|
{
|
|
long line, col;
|
|
long coladd = 0;
|
|
bool set_curswant = true;
|
|
|
|
rettv->vval.v_number = -1;
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
pos_T pos;
|
|
colnr_T curswant = -1;
|
|
|
|
if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
line = pos.lnum;
|
|
col = pos.col;
|
|
coladd = pos.coladd;
|
|
if (curswant >= 0) {
|
|
curwin->w_curswant = curswant - 1;
|
|
set_curswant = false;
|
|
}
|
|
} else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING)
|
|
&& (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) {
|
|
line = tv_get_lnum(argvars);
|
|
col = (long)tv_get_number_chk(&argvars[1], NULL);
|
|
if (charcol) {
|
|
col = buf_charidx_to_byteidx(curbuf, (linenr_T)line, (int)col) + 1;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
coladd = (long)tv_get_number_chk(&argvars[2], NULL);
|
|
}
|
|
} else {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
if (line < 0 || col < 0 || coladd < 0) {
|
|
return; // type error; errmsg already given
|
|
}
|
|
if (line > 0) {
|
|
curwin->w_cursor.lnum = (linenr_T)line;
|
|
}
|
|
if (col > 0) {
|
|
curwin->w_cursor.col = (colnr_T)col - 1;
|
|
}
|
|
curwin->w_cursor.coladd = (colnr_T)coladd;
|
|
|
|
// Make sure the cursor is in a valid position.
|
|
check_cursor();
|
|
// Correct cursor for multi-byte character.
|
|
mb_adjust_cursor();
|
|
|
|
curwin->w_set_curswant = set_curswant;
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
|
|
/// "cursor(lnum, col)" function, or
|
|
/// "cursor(list)"
|
|
///
|
|
/// Moves the cursor to the specified line and column.
|
|
///
|
|
/// @return 0 when the position could be set, -1 otherwise.
|
|
static void f_cursor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
set_cursorpos(argvars, rettv, false);
|
|
}
|
|
|
|
/// "debugbreak()" function
|
|
static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = FAIL;
|
|
int pid = (int)tv_get_number(&argvars[0]);
|
|
if (pid == 0) {
|
|
emsg(_(e_invarg));
|
|
} else {
|
|
#ifdef WIN32
|
|
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
|
|
|
|
if (hProcess != NULL) {
|
|
DebugBreakProcess(hProcess);
|
|
CloseHandle(hProcess);
|
|
rettv->vval.v_number = OK;
|
|
}
|
|
#else
|
|
uv_kill(pid, SIGINT);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// "deepcopy()" function
|
|
static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int noref = 0;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
noref = (int)tv_get_number_chk(&argvars[1], NULL);
|
|
}
|
|
if (noref < 0 || noref > 1) {
|
|
emsg(_(e_invarg));
|
|
} else {
|
|
var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
|
|
? get_copyID()
|
|
: 0));
|
|
}
|
|
}
|
|
|
|
/// "delete()" function
|
|
static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
const char *const name = tv_get_string(&argvars[0]);
|
|
if (*name == NUL) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
char nbuf[NUMBUFLEN];
|
|
const char *flags;
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
flags = tv_get_string_buf(&argvars[1], nbuf);
|
|
} else {
|
|
flags = "";
|
|
}
|
|
|
|
if (*flags == NUL) {
|
|
// delete a file
|
|
rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
|
|
} else if (strcmp(flags, "d") == 0) {
|
|
// delete an empty directory
|
|
rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
|
|
} else if (strcmp(flags, "rf") == 0) {
|
|
// delete a directory recursively
|
|
rettv->vval.v_number = delete_recursive(name);
|
|
} else {
|
|
semsg(_(e_invexpr2), flags);
|
|
}
|
|
}
|
|
|
|
/// dictwatcheradd(dict, key, funcref) function
|
|
static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_DICT) {
|
|
semsg(_(e_invarg2), "dict");
|
|
return;
|
|
} else if (argvars[0].vval.v_dict == NULL) {
|
|
const char *const arg_errmsg = _("dictwatcheradd() argument");
|
|
const size_t arg_errmsg_len = strlen(arg_errmsg);
|
|
semsg(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg);
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
|
|
semsg(_(e_invarg2), "key");
|
|
return;
|
|
}
|
|
|
|
const char *const key_pattern = tv_get_string_chk(argvars + 1);
|
|
if (key_pattern == NULL) {
|
|
return;
|
|
}
|
|
const size_t key_pattern_len = strlen(key_pattern);
|
|
|
|
Callback callback;
|
|
if (!callback_from_typval(&callback, &argvars[2])) {
|
|
semsg(_(e_invarg2), "funcref");
|
|
return;
|
|
}
|
|
|
|
tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len,
|
|
callback);
|
|
}
|
|
|
|
/// dictwatcherdel(dict, key, funcref) function
|
|
static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_DICT) {
|
|
semsg(_(e_invarg2), "dict");
|
|
return;
|
|
}
|
|
|
|
if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
|
|
semsg(_(e_invarg2), "funcref");
|
|
return;
|
|
}
|
|
|
|
const char *const key_pattern = tv_get_string_chk(argvars + 1);
|
|
if (key_pattern == NULL) {
|
|
return;
|
|
}
|
|
|
|
Callback callback;
|
|
if (!callback_from_typval(&callback, &argvars[2])) {
|
|
return;
|
|
}
|
|
|
|
if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern,
|
|
strlen(key_pattern), callback)) {
|
|
emsg("Couldn't find a watcher matching key and callback");
|
|
}
|
|
|
|
callback_free(&callback);
|
|
}
|
|
|
|
/// "deletebufline()" function
|
|
static void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_T *const buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
rettv->vval.v_number = 1; // FAIL
|
|
return;
|
|
}
|
|
const bool is_curbuf = buf == curbuf;
|
|
const bool save_VIsual_active = VIsual_active;
|
|
|
|
linenr_T last;
|
|
const linenr_T first = tv_get_lnum_buf(&argvars[1], buf);
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
last = tv_get_lnum_buf(&argvars[2], buf);
|
|
} else {
|
|
last = first;
|
|
}
|
|
|
|
if (buf->b_ml.ml_mfp == NULL || first < 1
|
|
|| first > buf->b_ml.ml_line_count || last < first) {
|
|
rettv->vval.v_number = 1; // FAIL
|
|
return;
|
|
}
|
|
|
|
buf_T *curbuf_save = NULL;
|
|
win_T *curwin_save = NULL;
|
|
if (!is_curbuf) {
|
|
VIsual_active = false;
|
|
curbuf_save = curbuf;
|
|
curwin_save = curwin;
|
|
curbuf = buf;
|
|
find_win_for_curbuf();
|
|
}
|
|
if (last > curbuf->b_ml.ml_line_count) {
|
|
last = curbuf->b_ml.ml_line_count;
|
|
}
|
|
const long count = last - first + 1;
|
|
|
|
// When coming here from Insert mode, sync undo, so that this can be
|
|
// undone separately from what was previously inserted.
|
|
if (u_sync_once == 2) {
|
|
u_sync_once = 1; // notify that u_sync() was called
|
|
u_sync(true);
|
|
}
|
|
|
|
if (u_save(first - 1, last + 1) == FAIL) {
|
|
rettv->vval.v_number = 1; // FAIL
|
|
} else {
|
|
for (linenr_T lnum = first; lnum <= last; lnum++) {
|
|
ml_delete(first, true);
|
|
}
|
|
|
|
FOR_ALL_TAB_WINDOWS(tp, wp) {
|
|
if (wp->w_buffer == buf) {
|
|
if (wp->w_cursor.lnum > last) {
|
|
wp->w_cursor.lnum -= (linenr_T)count;
|
|
} else if (wp->w_cursor.lnum > first) {
|
|
wp->w_cursor.lnum = first;
|
|
}
|
|
if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
|
|
wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
|
|
}
|
|
}
|
|
}
|
|
check_cursor_col();
|
|
deleted_lines_mark(first, count);
|
|
}
|
|
|
|
if (!is_curbuf) {
|
|
curbuf = curbuf_save;
|
|
curwin = curwin_save;
|
|
VIsual_active = save_VIsual_active;
|
|
}
|
|
}
|
|
|
|
/// "did_filetype()" function
|
|
static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = did_filetype;
|
|
}
|
|
|
|
/// "diff_filler()" function
|
|
static void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars)));
|
|
}
|
|
|
|
/// "diff_hlID()" function
|
|
static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T lnum = tv_get_lnum(argvars);
|
|
static linenr_T prev_lnum = 0;
|
|
static varnumber_T changedtick = 0;
|
|
static int fnum = 0;
|
|
static int change_start = 0;
|
|
static int change_end = 0;
|
|
static hlf_T hlID = (hlf_T)0;
|
|
|
|
if (lnum < 0) { // ignore type error in {lnum} arg
|
|
lnum = 0;
|
|
}
|
|
if (lnum != prev_lnum
|
|
|| changedtick != buf_get_changedtick(curbuf)
|
|
|| fnum != curbuf->b_fnum) {
|
|
// New line, buffer, change: need to get the values.
|
|
int filler_lines = diff_check(curwin, lnum);
|
|
if (filler_lines < 0) {
|
|
if (filler_lines == -1) {
|
|
change_start = MAXCOL;
|
|
change_end = -1;
|
|
if (diff_find_change(curwin, lnum, &change_start, &change_end)) {
|
|
hlID = HLF_ADD; // added line
|
|
} else {
|
|
hlID = HLF_CHD; // changed line
|
|
}
|
|
} else {
|
|
hlID = HLF_ADD; // added line
|
|
}
|
|
} else {
|
|
hlID = (hlf_T)0;
|
|
}
|
|
prev_lnum = lnum;
|
|
changedtick = buf_get_changedtick(curbuf);
|
|
fnum = curbuf->b_fnum;
|
|
}
|
|
|
|
if (hlID == HLF_CHD || hlID == HLF_TXD) {
|
|
int col = (int)tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
|
|
if (col >= change_start && col <= change_end) {
|
|
hlID = HLF_TXD; // Changed text.
|
|
} else {
|
|
hlID = HLF_CHD; // Changed line.
|
|
}
|
|
}
|
|
rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1);
|
|
}
|
|
|
|
/// "empty({expr})" function
|
|
static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
bool n = true;
|
|
|
|
switch (argvars[0].v_type) {
|
|
case VAR_STRING:
|
|
case VAR_FUNC:
|
|
n = argvars[0].vval.v_string == NULL
|
|
|| *argvars[0].vval.v_string == NUL;
|
|
break;
|
|
case VAR_PARTIAL:
|
|
n = false;
|
|
break;
|
|
case VAR_NUMBER:
|
|
n = argvars[0].vval.v_number == 0;
|
|
break;
|
|
case VAR_FLOAT:
|
|
n = argvars[0].vval.v_float == 0.0;
|
|
break;
|
|
case VAR_LIST:
|
|
n = (tv_list_len(argvars[0].vval.v_list) == 0);
|
|
break;
|
|
case VAR_DICT:
|
|
n = (tv_dict_len(argvars[0].vval.v_dict) == 0);
|
|
break;
|
|
case VAR_BOOL:
|
|
switch (argvars[0].vval.v_bool) {
|
|
case kBoolVarTrue:
|
|
n = false;
|
|
break;
|
|
case kBoolVarFalse:
|
|
n = true;
|
|
break;
|
|
}
|
|
break;
|
|
case VAR_SPECIAL:
|
|
n = argvars[0].vval.v_special == kSpecialVarNull;
|
|
break;
|
|
case VAR_BLOB:
|
|
n = (tv_blob_len(argvars[0].vval.v_blob) == 0);
|
|
break;
|
|
case VAR_UNKNOWN:
|
|
internal_error("f_empty(UNKNOWN)");
|
|
break;
|
|
}
|
|
|
|
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 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, 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)
|
|
{
|
|
char buf[NUMBUFLEN];
|
|
|
|
rettv->vval.v_string = (char *)vim_strsave_escaped((const char_u *)tv_get_string(&argvars[0]),
|
|
(const char_u *)tv_get_string_buf(&argvars[1],
|
|
buf));
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "getenv()" function
|
|
static void f_getenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0]));
|
|
|
|
if (p == NULL) {
|
|
rettv->v_type = VAR_SPECIAL;
|
|
rettv->vval.v_special = kSpecialVarNull;
|
|
return;
|
|
}
|
|
rettv->vval.v_string = (char *)p;
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "eval()" function
|
|
static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *s = tv_get_string_chk(&argvars[0]);
|
|
if (s != NULL) {
|
|
s = (const char *)skipwhite(s);
|
|
}
|
|
|
|
const char *const expr_start = s;
|
|
if (s == NULL || eval1((char **)&s, rettv, true) == FAIL) {
|
|
if (expr_start != NULL && !aborting()) {
|
|
semsg(_(e_invexpr2), expr_start);
|
|
}
|
|
need_clr_eos = false;
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
} else if (*s != NUL) {
|
|
semsg(_(e_trailing_arg), s);
|
|
}
|
|
}
|
|
|
|
/// "eventhandler()" function
|
|
static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = vgetc_busy;
|
|
}
|
|
|
|
/// "executable()" function
|
|
static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (tv_check_for_string(&argvars[0]) == FAIL) {
|
|
return;
|
|
}
|
|
|
|
// Check in $PATH and also check directly if there is a directory name
|
|
rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true);
|
|
}
|
|
|
|
typedef struct {
|
|
const list_T *const l;
|
|
const listitem_T *li;
|
|
} GetListLineCookie;
|
|
|
|
static char *get_list_line(int c, void *cookie, int indent, bool do_concat)
|
|
{
|
|
GetListLineCookie *const p = (GetListLineCookie *)cookie;
|
|
|
|
const listitem_T *const item = p->li;
|
|
if (item == NULL) {
|
|
return NULL;
|
|
}
|
|
char buf[NUMBUFLEN];
|
|
const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf);
|
|
p->li = TV_LIST_ITEM_NEXT(p->l, item);
|
|
return s == NULL ? NULL : xstrdup(s);
|
|
}
|
|
|
|
static void execute_common(typval_T *argvars, typval_T *rettv, int arg_off)
|
|
{
|
|
const int save_msg_silent = msg_silent;
|
|
const int save_emsg_silent = emsg_silent;
|
|
const bool save_emsg_noredir = emsg_noredir;
|
|
const bool save_redir_off = redir_off;
|
|
garray_T *const save_capture_ga = capture_ga;
|
|
const int save_msg_col = msg_col;
|
|
bool echo_output = false;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[arg_off + 1].v_type != VAR_UNKNOWN) {
|
|
char buf[NUMBUFLEN];
|
|
const char *const s = tv_get_string_buf_chk(&argvars[arg_off + 1], buf);
|
|
|
|
if (s == NULL) {
|
|
return;
|
|
}
|
|
if (*s == NUL) {
|
|
echo_output = true;
|
|
}
|
|
if (strncmp(s, "silent", 6) == 0) {
|
|
msg_silent++;
|
|
}
|
|
if (strcmp(s, "silent!") == 0) {
|
|
emsg_silent = true;
|
|
emsg_noredir = true;
|
|
}
|
|
} else {
|
|
msg_silent++;
|
|
}
|
|
|
|
garray_T capture_local;
|
|
ga_init(&capture_local, (int)sizeof(char), 80);
|
|
capture_ga = &capture_local;
|
|
redir_off = false;
|
|
if (!echo_output) {
|
|
msg_col = 0; // prevent leading spaces
|
|
}
|
|
|
|
if (argvars[arg_off].v_type != VAR_LIST) {
|
|
do_cmdline_cmd(tv_get_string(&argvars[arg_off]));
|
|
} else if (argvars[arg_off].vval.v_list != NULL) {
|
|
list_T *const list = argvars[arg_off].vval.v_list;
|
|
tv_list_ref(list);
|
|
GetListLineCookie cookie = {
|
|
.l = list,
|
|
.li = tv_list_first(list),
|
|
};
|
|
do_cmdline(NULL, get_list_line, (void *)&cookie,
|
|
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
|
|
tv_list_unref(list);
|
|
}
|
|
msg_silent = save_msg_silent;
|
|
emsg_silent = save_emsg_silent;
|
|
emsg_noredir = save_emsg_noredir;
|
|
redir_off = save_redir_off;
|
|
// "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
|
|
if (echo_output) {
|
|
// When not working silently: put it in column zero. A following
|
|
// "echon" will overwrite the message, unavoidably.
|
|
msg_col = 0;
|
|
} else {
|
|
// When working silently: Put it back where it was, since nothing
|
|
// should have been written.
|
|
msg_col = save_msg_col;
|
|
}
|
|
|
|
ga_append(capture_ga, NUL);
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = capture_ga->ga_data;
|
|
|
|
capture_ga = save_capture_ga;
|
|
}
|
|
|
|
/// "execute(command)" function
|
|
static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
execute_common(argvars, rettv, 0);
|
|
}
|
|
|
|
/// "win_execute(win_id, command)" function
|
|
static void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// Return an empty string if something fails.
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
int id = (int)tv_get_number(argvars);
|
|
tabpage_T *tp;
|
|
win_T *wp = win_id2wp_tp(id, &tp);
|
|
if (wp != NULL && tp != NULL) {
|
|
WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, 1));
|
|
}
|
|
}
|
|
|
|
/// "exepath()" function
|
|
static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (tv_check_for_nonempty_string(&argvars[0]) == FAIL) {
|
|
return;
|
|
}
|
|
|
|
char *path = NULL;
|
|
|
|
(void)os_can_exe(tv_get_string(&argvars[0]), &path, true);
|
|
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
if (path != NULL) {
|
|
slash_adjust((char_u *)path);
|
|
}
|
|
#endif
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = path;
|
|
}
|
|
|
|
/// "exists()" function
|
|
static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int n = false;
|
|
|
|
const char *p = tv_get_string(&argvars[0]);
|
|
if (*p == '$') { // Environment variable.
|
|
// First try "normal" environment variables (fast).
|
|
if (os_env_exists(p + 1)) {
|
|
n = true;
|
|
} else {
|
|
// Try expanding things like $VIM and ${HOME}.
|
|
char *const exp = expand_env_save((char *)p);
|
|
if (exp != NULL && *exp != '$') {
|
|
n = true;
|
|
}
|
|
xfree(exp);
|
|
}
|
|
} else if (*p == '&' || *p == '+') { // Option.
|
|
n = (get_option_tv(&p, NULL, true) == OK);
|
|
if (*skipwhite(p) != NUL) {
|
|
n = false; // Trailing garbage.
|
|
}
|
|
} else if (*p == '*') { // Internal or user defined function.
|
|
n = function_exists(p + 1, false);
|
|
} else if (*p == ':') {
|
|
n = cmd_exists(p + 1);
|
|
} else if (*p == '#') {
|
|
if (p[1] == '#') {
|
|
n = autocmd_supported(p + 2);
|
|
} else {
|
|
n = au_exists(p + 1);
|
|
}
|
|
} else { // Internal variable.
|
|
n = var_exists(p);
|
|
}
|
|
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
/// "expand()" function
|
|
static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
|
|
bool error = false;
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
char_u *p_csl_save = p_csl;
|
|
|
|
// avoid using 'completeslash' here
|
|
p_csl = empty_option;
|
|
#endif
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
if (argvars[1].v_type != VAR_UNKNOWN
|
|
&& argvars[2].v_type != VAR_UNKNOWN
|
|
&& tv_get_number_chk(&argvars[2], &error)
|
|
&& !error) {
|
|
tv_list_set_ret(rettv, NULL);
|
|
}
|
|
|
|
const char *s = tv_get_string(&argvars[0]);
|
|
if (*s == '%' || *s == '#' || *s == '<') {
|
|
if (p_verbose == 0) {
|
|
emsg_off++;
|
|
}
|
|
size_t len;
|
|
char *errormsg = NULL;
|
|
char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL, false);
|
|
if (p_verbose == 0) {
|
|
emsg_off--;
|
|
} else if (errormsg != NULL) {
|
|
emsg(errormsg);
|
|
}
|
|
if (rettv->v_type == VAR_LIST) {
|
|
tv_list_alloc_ret(rettv, (result != NULL));
|
|
if (result != NULL) {
|
|
tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
|
|
}
|
|
XFREE_CLEAR(result);
|
|
} else {
|
|
rettv->vval.v_string = (char *)result;
|
|
}
|
|
} else {
|
|
// When the optional second argument is non-zero, don't remove matches
|
|
// for 'wildignore' and don't put matches for 'suffixes' at the end.
|
|
if (argvars[1].v_type != VAR_UNKNOWN
|
|
&& tv_get_number_chk(&argvars[1], &error)) {
|
|
options |= WILD_KEEP_ALL;
|
|
}
|
|
if (!error) {
|
|
expand_T xpc;
|
|
ExpandInit(&xpc);
|
|
xpc.xp_context = EXPAND_FILES;
|
|
if (p_wic) {
|
|
options += WILD_ICASE;
|
|
}
|
|
if (rettv->v_type == VAR_STRING) {
|
|
rettv->vval.v_string = (char *)ExpandOne(&xpc, (char_u *)s, NULL, options,
|
|
WILD_ALL);
|
|
} else {
|
|
ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP);
|
|
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
|
|
for (int i = 0; i < xpc.xp_numfiles; i++) {
|
|
tv_list_append_string(rettv->vval.v_list,
|
|
(const char *)xpc.xp_files[i], -1);
|
|
}
|
|
ExpandCleanup(&xpc);
|
|
}
|
|
} else {
|
|
rettv->vval.v_string = NULL;
|
|
}
|
|
}
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
p_csl = p_csl_save;
|
|
#endif
|
|
}
|
|
|
|
/// "menu_get(path [, modes])" function
|
|
static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
int modes = MENU_ALL_MODES;
|
|
if (argvars[1].v_type == VAR_STRING) {
|
|
const char *const strmodes = tv_get_string(&argvars[1]);
|
|
modes = get_menu_cmd_modes(strmodes, false, NULL, NULL);
|
|
}
|
|
menu_get((char *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list);
|
|
}
|
|
|
|
/// "expandcmd()" function
|
|
/// Expand all the special characters in a command string.
|
|
static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char *errormsg = NULL;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
char *cmdstr = xstrdup(tv_get_string(&argvars[0]));
|
|
|
|
exarg_T eap = {
|
|
.cmd = cmdstr,
|
|
.arg = cmdstr,
|
|
.usefilter = false,
|
|
.nextcmd = NULL,
|
|
.cmdidx = CMD_USER,
|
|
};
|
|
eap.argt |= EX_NOSPC;
|
|
|
|
emsg_off++;
|
|
expand_filename(&eap, &cmdstr, &errormsg);
|
|
emsg_off--;
|
|
|
|
rettv->vval.v_string = cmdstr;
|
|
}
|
|
|
|
/// "flatten(list[, {maxdepth}])" function
|
|
static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
bool error = false;
|
|
|
|
if (argvars[0].v_type != VAR_LIST) {
|
|
semsg(_(e_listarg), "flatten()");
|
|
return;
|
|
}
|
|
|
|
long maxdepth;
|
|
if (argvars[1].v_type == VAR_UNKNOWN) {
|
|
maxdepth = 999999;
|
|
} else {
|
|
maxdepth = (long)tv_get_number_chk(&argvars[1], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
if (maxdepth < 0) {
|
|
emsg(_("E900: maxdepth must be non-negative number"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
list_T *list = argvars[0].vval.v_list;
|
|
if (list != NULL
|
|
&& !var_check_lock(tv_list_locked(list),
|
|
N_("flatten() argument"),
|
|
TV_TRANSLATE)
|
|
&& tv_list_flatten(list, maxdepth) == OK) {
|
|
tv_copy(&argvars[0], rettv);
|
|
}
|
|
}
|
|
|
|
/// "extend(list, list [, idx])" function
|
|
/// "extend(dict, dict [, action])" function
|
|
static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const arg_errmsg = N_("extend() argument");
|
|
|
|
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
|
|
bool error = false;
|
|
|
|
list_T *const l1 = argvars[0].vval.v_list;
|
|
list_T *const l2 = argvars[1].vval.v_list;
|
|
if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
|
|
listitem_T *item;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
long before = (long)tv_get_number_chk(&argvars[2], &error);
|
|
if (error) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
|
|
if (before == tv_list_len(l1)) {
|
|
item = NULL;
|
|
} else {
|
|
item = tv_list_find(l1, (int)before);
|
|
if (item == NULL) {
|
|
semsg(_(e_listidx), (int64_t)before);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
item = NULL;
|
|
}
|
|
tv_list_extend(l1, l2, item);
|
|
|
|
tv_copy(&argvars[0], rettv);
|
|
}
|
|
} else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
|
|
VAR_DICT) {
|
|
dict_T *const d1 = argvars[0].vval.v_dict;
|
|
dict_T *const d2 = argvars[1].vval.v_dict;
|
|
if (d1 == NULL) {
|
|
const bool locked = var_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
|
|
(void)locked;
|
|
assert(locked == true);
|
|
} else if (d2 == NULL) {
|
|
// Do nothing
|
|
tv_copy(&argvars[0], rettv);
|
|
} else if (!var_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
|
|
const char *action = "force";
|
|
// Check the third argument.
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
const char *const av[] = { "keep", "force", "error" };
|
|
|
|
action = tv_get_string_chk(&argvars[2]);
|
|
if (action == NULL) {
|
|
return; // Type error; error message already given.
|
|
}
|
|
size_t i;
|
|
for (i = 0; i < ARRAY_SIZE(av); i++) {
|
|
if (strcmp(action, av[i]) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == 3) {
|
|
semsg(_(e_invarg2), action);
|
|
return;
|
|
}
|
|
}
|
|
|
|
tv_dict_extend(d1, d2, action);
|
|
|
|
tv_copy(&argvars[0], rettv);
|
|
}
|
|
} else {
|
|
semsg(_(e_listdictarg), "extend()");
|
|
}
|
|
}
|
|
|
|
/// "feedkeys()" function
|
|
static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// This is not allowed in the sandbox. If the commands would still be
|
|
// executed in the sandbox it would be OK, but it probably happens later,
|
|
// when "sandbox" is no longer set.
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
const char *const keys = tv_get_string(&argvars[0]);
|
|
char nbuf[NUMBUFLEN];
|
|
const char *flags = NULL;
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
flags = tv_get_string_buf(&argvars[1], nbuf);
|
|
}
|
|
|
|
nvim_feedkeys(cstr_as_string((char *)keys),
|
|
cstr_as_string((char *)flags), true);
|
|
}
|
|
|
|
/// "filereadable()" function
|
|
static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const p = tv_get_string(&argvars[0]);
|
|
rettv->vval.v_number =
|
|
(*p && !os_isdir((const char_u *)p) && os_file_is_readable(p));
|
|
}
|
|
|
|
/// @return 0 for not writable
|
|
/// 1 for writable file
|
|
/// 2 for a dir which we have rights to write into.
|
|
static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *filename = tv_get_string(&argvars[0]);
|
|
rettv->vval.v_number = os_file_is_writable(filename);
|
|
}
|
|
|
|
static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
|
|
{
|
|
char_u *fresult = NULL;
|
|
char_u *path = *curbuf->b_p_path == NUL ? p_path : (char_u *)curbuf->b_p_path;
|
|
int count = 1;
|
|
bool first = true;
|
|
bool error = false;
|
|
|
|
rettv->vval.v_string = NULL;
|
|
rettv->v_type = VAR_STRING;
|
|
|
|
const char *fname = tv_get_string(&argvars[0]);
|
|
|
|
char pathbuf[NUMBUFLEN];
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
|
|
if (p == NULL) {
|
|
error = true;
|
|
} else {
|
|
if (*p != NUL) {
|
|
path = (char_u *)p;
|
|
}
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
count = (int)tv_get_number_chk(&argvars[2], &error);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count < 0) {
|
|
tv_list_alloc_ret(rettv, kListLenUnknown);
|
|
}
|
|
|
|
if (*fname != NUL && !error) {
|
|
do {
|
|
if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) {
|
|
xfree(fresult);
|
|
}
|
|
fresult = find_file_in_path_option(first ? (char_u *)fname : NULL,
|
|
first ? strlen(fname) : 0,
|
|
0, first, path,
|
|
find_what, (char_u *)curbuf->b_ffname,
|
|
(find_what == FINDFILE_DIR
|
|
? (char_u *)""
|
|
: (char_u *)curbuf->b_p_sua));
|
|
first = false;
|
|
|
|
if (fresult != NULL && rettv->v_type == VAR_LIST) {
|
|
tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
|
|
}
|
|
} while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
|
|
}
|
|
|
|
if (rettv->v_type == VAR_STRING) {
|
|
rettv->vval.v_string = (char *)fresult;
|
|
}
|
|
}
|
|
|
|
/// "filter()" function
|
|
static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
filter_map(argvars, rettv, false);
|
|
}
|
|
|
|
/// "finddir({fname}[, {path}[, {count}]])" function
|
|
static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
findfilendir(argvars, rettv, FINDFILE_DIR);
|
|
}
|
|
|
|
/// "findfile({fname}[, {path}[, {count}]])" function
|
|
static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
findfilendir(argvars, rettv, FINDFILE_FILE);
|
|
}
|
|
|
|
/// "float2nr({float})" function
|
|
static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
float_T f;
|
|
|
|
if (tv_get_float_chk(argvars, &f)) {
|
|
if (f <= (float_T) - VARNUMBER_MAX + DBL_EPSILON) {
|
|
rettv->vval.v_number = -VARNUMBER_MAX;
|
|
} else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) {
|
|
rettv->vval.v_number = VARNUMBER_MAX;
|
|
} else {
|
|
rettv->vval.v_number = (varnumber_T)f;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "fmod()" function
|
|
static void f_fmod(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
float_T fx;
|
|
float_T fy;
|
|
|
|
rettv->v_type = VAR_FLOAT;
|
|
if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
|
|
rettv->vval.v_float = fmod(fx, fy);
|
|
} else {
|
|
rettv->vval.v_float = 0.0;
|
|
}
|
|
}
|
|
|
|
/// "fnameescape({string})" function
|
|
static void f_fnameescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_string = vim_strsave_fnameescape(tv_get_string(&argvars[0]), VSE_NONE);
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "fnamemodify({fname}, {mods})" function
|
|
static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char_u *fbuf = NULL;
|
|
size_t len = 0;
|
|
char buf[NUMBUFLEN];
|
|
const char *fname = tv_get_string_chk(&argvars[0]);
|
|
const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
|
|
if (mods == NULL || fname == NULL) {
|
|
fname = NULL;
|
|
} else {
|
|
len = strlen(fname);
|
|
if (*mods != NUL) {
|
|
size_t usedlen = 0;
|
|
(void)modify_fname((char *)mods, false, &usedlen,
|
|
(char **)&fname, (char **)&fbuf, &len);
|
|
}
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
if (fname == NULL) {
|
|
rettv->vval.v_string = NULL;
|
|
} else {
|
|
rettv->vval.v_string = xmemdupz(fname, len);
|
|
}
|
|
xfree(fbuf);
|
|
}
|
|
|
|
/// "foreground()" function
|
|
static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{}
|
|
|
|
static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
common_function(argvars, rettv, true);
|
|
}
|
|
|
|
static void f_function(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
common_function(argvars, rettv, false);
|
|
}
|
|
|
|
/// "garbagecollect()" function
|
|
static void f_garbagecollect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// This is postponed until we are back at the toplevel, because we may be
|
|
// using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]".
|
|
want_garbage_collect = true;
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) {
|
|
garbage_collect_at_exit = true;
|
|
}
|
|
}
|
|
|
|
/// "get()" function
|
|
static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
typval_T *tv = NULL;
|
|
bool what_is_dict = false;
|
|
|
|
if (argvars[0].v_type == VAR_BLOB) {
|
|
bool error = false;
|
|
int idx = (int)tv_get_number_chk(&argvars[1], &error);
|
|
|
|
if (!error) {
|
|
rettv->v_type = VAR_NUMBER;
|
|
if (idx < 0) {
|
|
idx = tv_blob_len(argvars[0].vval.v_blob) + idx;
|
|
}
|
|
if (idx < 0 || idx >= tv_blob_len(argvars[0].vval.v_blob)) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
rettv->vval.v_number = tv_blob_get(argvars[0].vval.v_blob, idx);
|
|
tv = rettv;
|
|
}
|
|
}
|
|
} else if (argvars[0].v_type == VAR_LIST) {
|
|
list_T *l = argvars[0].vval.v_list;
|
|
if (l != NULL) {
|
|
bool error = false;
|
|
|
|
listitem_T *li = tv_list_find(l, (int)tv_get_number_chk(&argvars[1], &error));
|
|
if (!error && li != NULL) {
|
|
tv = TV_LIST_ITEM_TV(li);
|
|
}
|
|
}
|
|
} else if (argvars[0].v_type == VAR_DICT) {
|
|
dict_T *d = argvars[0].vval.v_dict;
|
|
if (d != NULL) {
|
|
dictitem_T *di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
|
|
if (di != NULL) {
|
|
tv = &di->di_tv;
|
|
}
|
|
}
|
|
} else if (tv_is_func(argvars[0])) {
|
|
partial_T *pt;
|
|
partial_T fref_pt;
|
|
|
|
if (argvars[0].v_type == VAR_PARTIAL) {
|
|
pt = argvars[0].vval.v_partial;
|
|
} else {
|
|
CLEAR_FIELD(fref_pt);
|
|
fref_pt.pt_name = (char_u *)argvars[0].vval.v_string;
|
|
pt = &fref_pt;
|
|
}
|
|
|
|
if (pt != NULL) {
|
|
const char *const what = tv_get_string(&argvars[1]);
|
|
|
|
if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) {
|
|
rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
|
|
const char *const n = (const char *)partial_name(pt);
|
|
assert(n != NULL);
|
|
rettv->vval.v_string = xstrdup(n);
|
|
if (rettv->v_type == VAR_FUNC) {
|
|
func_ref((char_u *)rettv->vval.v_string);
|
|
}
|
|
} else if (strcmp(what, "dict") == 0) {
|
|
what_is_dict = true;
|
|
if (pt->pt_dict != NULL) {
|
|
tv_dict_set_ret(rettv, pt->pt_dict);
|
|
}
|
|
} else if (strcmp(what, "args") == 0) {
|
|
rettv->v_type = VAR_LIST;
|
|
tv_list_alloc_ret(rettv, pt->pt_argc);
|
|
for (int i = 0; i < pt->pt_argc; i++) {
|
|
tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
|
|
}
|
|
} else {
|
|
semsg(_(e_invarg2), what);
|
|
}
|
|
|
|
// When {what} == "dict" and pt->pt_dict == NULL, evaluate the
|
|
// third argument
|
|
if (!what_is_dict) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
semsg(_(e_listdictblobarg), "get()");
|
|
}
|
|
|
|
if (tv == NULL) {
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
tv_copy(&argvars[2], rettv);
|
|
}
|
|
} else {
|
|
tv_copy(tv, rettv);
|
|
}
|
|
}
|
|
|
|
/// "getbufinfo()" function
|
|
static void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_T *argbuf = NULL;
|
|
bool filtered = false;
|
|
bool sel_buflisted = false;
|
|
bool sel_bufloaded = false;
|
|
bool sel_bufmodified = false;
|
|
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
|
|
// List of all the buffers or selected buffers
|
|
if (argvars[0].v_type == VAR_DICT) {
|
|
dict_T *sel_d = argvars[0].vval.v_dict;
|
|
|
|
if (sel_d != NULL) {
|
|
dictitem_T *di;
|
|
|
|
filtered = true;
|
|
|
|
di = tv_dict_find(sel_d, S_LEN("buflisted"));
|
|
if (di != NULL && tv_get_number(&di->di_tv)) {
|
|
sel_buflisted = true;
|
|
}
|
|
|
|
di = tv_dict_find(sel_d, S_LEN("bufloaded"));
|
|
if (di != NULL && tv_get_number(&di->di_tv)) {
|
|
sel_bufloaded = true;
|
|
}
|
|
di = tv_dict_find(sel_d, S_LEN("bufmodified"));
|
|
if (di != NULL && tv_get_number(&di->di_tv)) {
|
|
sel_bufmodified = true;
|
|
}
|
|
}
|
|
} else if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
// Information about one buffer. Argument specifies the buffer
|
|
argbuf = tv_get_buf_from_arg(&argvars[0]);
|
|
if (argbuf == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Return information about all the buffers or a specified buffer
|
|
FOR_ALL_BUFFERS(buf) {
|
|
if (argbuf != NULL && argbuf != buf) {
|
|
continue;
|
|
}
|
|
if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
|
|
|| (sel_buflisted && !buf->b_p_bl)
|
|
|| (sel_bufmodified && !buf->b_changed))) {
|
|
continue;
|
|
}
|
|
|
|
dict_T *const d = get_buffer_info(buf);
|
|
tv_list_append_dict(rettv->vval.v_list, d);
|
|
if (argbuf != NULL) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get line or list of lines from buffer "buf" into "rettv".
|
|
///
|
|
/// @param retlist if true, then the lines are returned as a Vim List.
|
|
///
|
|
/// @return range (from start to end) of lines in rettv from the specified
|
|
/// buffer.
|
|
static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv)
|
|
{
|
|
rettv->v_type = (retlist ? VAR_LIST : VAR_STRING);
|
|
rettv->vval.v_string = NULL;
|
|
|
|
if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) {
|
|
if (retlist) {
|
|
tv_list_alloc_ret(rettv, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (retlist) {
|
|
if (start < 1) {
|
|
start = 1;
|
|
}
|
|
if (end > buf->b_ml.ml_line_count) {
|
|
end = buf->b_ml.ml_line_count;
|
|
}
|
|
tv_list_alloc_ret(rettv, end - start + 1);
|
|
while (start <= end) {
|
|
tv_list_append_string(rettv->vval.v_list,
|
|
(const char *)ml_get_buf(buf, start++, false), -1);
|
|
}
|
|
} else {
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string =
|
|
(char *)((start >= 1 && start <= buf->b_ml.ml_line_count)
|
|
? vim_strsave(ml_get_buf(buf, start, false)) : NULL);
|
|
}
|
|
}
|
|
|
|
/// "getbufline()" function
|
|
static void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
|
|
|
|
const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
|
|
const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN
|
|
? lnum
|
|
: tv_get_lnum_buf(&argvars[2], buf));
|
|
|
|
get_buffer_lines(buf, lnum, end, true, rettv);
|
|
}
|
|
|
|
/// "getchangelist()" function
|
|
static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, 2);
|
|
|
|
const buf_T *buf;
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
buf = curbuf;
|
|
} else {
|
|
vim_ignored = (int)tv_get_number(&argvars[0]); // issue errmsg if type error
|
|
emsg_off++;
|
|
buf = tv_get_buf(&argvars[0], false);
|
|
emsg_off--;
|
|
}
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
|
|
list_T *const l = tv_list_alloc(buf->b_changelistlen);
|
|
tv_list_append_list(rettv->vval.v_list, l);
|
|
// The current window change list index tracks only the position for the
|
|
// current buffer. For other buffers use the stored index for the current
|
|
// window, or, if that's not available, the change list length.
|
|
int changelistindex;
|
|
if (buf == curwin->w_buffer) {
|
|
changelistindex = curwin->w_changelistidx;
|
|
} else {
|
|
wininfo_T *wip;
|
|
|
|
FOR_ALL_BUF_WININFO(buf, wip) {
|
|
if (wip->wi_win == curwin) {
|
|
break;
|
|
}
|
|
}
|
|
changelistindex = wip != NULL ? wip->wi_changelistidx : buf->b_changelistlen;
|
|
}
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)changelistindex);
|
|
|
|
for (int i = 0; i < buf->b_changelistlen; i++) {
|
|
if (buf->b_changelist[i].mark.lnum == 0) {
|
|
continue;
|
|
}
|
|
dict_T *const d = tv_dict_alloc();
|
|
tv_list_append_dict(l, d);
|
|
tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum);
|
|
tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col);
|
|
tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd);
|
|
}
|
|
}
|
|
|
|
static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol)
|
|
{
|
|
pos_T *fp = NULL;
|
|
pos_T pos;
|
|
win_T *wp = curwin;
|
|
int fnum = -1;
|
|
|
|
if (getcurpos) {
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp != NULL) {
|
|
fp = &wp->w_cursor;
|
|
}
|
|
} else {
|
|
fp = &curwin->w_cursor;
|
|
}
|
|
if (fp != NULL && charcol) {
|
|
pos = *fp;
|
|
pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col);
|
|
fp = &pos;
|
|
}
|
|
} else {
|
|
fp = var2fpos(&argvars[0], true, &fnum, charcol);
|
|
}
|
|
|
|
list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos);
|
|
tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
|
|
tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0));
|
|
tv_list_append_number(l, ((fp != NULL)
|
|
? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
|
|
: (varnumber_T)0));
|
|
tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
|
|
if (getcurpos) {
|
|
const int save_set_curswant = curwin->w_set_curswant;
|
|
const colnr_T save_curswant = curwin->w_curswant;
|
|
const colnr_T save_virtcol = curwin->w_virtcol;
|
|
|
|
if (wp == curwin) {
|
|
update_curswant();
|
|
}
|
|
tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL)
|
|
? (varnumber_T)MAXCOL
|
|
: (varnumber_T)wp->w_curswant + 1));
|
|
|
|
// Do not change "curswant", as it is unexpected that a get
|
|
// function has a side effect.
|
|
if (wp == curwin && save_set_curswant) {
|
|
curwin->w_set_curswant = save_set_curswant;
|
|
curwin->w_curswant = save_curswant;
|
|
curwin->w_virtcol = save_virtcol;
|
|
curwin->w_valid &= ~VALID_VIRTCOL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "getcharpos()" function
|
|
static void f_getcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
getpos_both(argvars, rettv, false, true);
|
|
}
|
|
|
|
/// "getcharsearch()" function
|
|
static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
|
|
dict_T *dict = rettv->vval.v_dict;
|
|
|
|
tv_dict_add_str(dict, S_LEN("char"), last_csearch());
|
|
tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward());
|
|
tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until());
|
|
}
|
|
|
|
/// "getcmdcompltype()" function
|
|
static void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (char *)get_cmdline_completion();
|
|
}
|
|
|
|
/// "getcmdline()" function
|
|
static void f_getcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (char *)get_cmdline_str();
|
|
}
|
|
|
|
/// "getcmdpos()" function
|
|
static void f_getcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = get_cmdline_pos() + 1;
|
|
}
|
|
|
|
/// "getcmdscreenpos()" function
|
|
static void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = get_cmdline_screen_pos() + 1;
|
|
}
|
|
|
|
/// "getcmdtype()" function
|
|
static void f_getcmdtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xmallocz(1);
|
|
rettv->vval.v_string[0] = (char)get_cmdline_type();
|
|
}
|
|
|
|
/// "getcmdwintype()" function
|
|
static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
rettv->vval.v_string = xmallocz(1);
|
|
rettv->vval.v_string[0] = (char)cmdwin_type;
|
|
}
|
|
|
|
/// `getcwd([{win}[, {tab}]])` function
|
|
///
|
|
/// Every scope not specified implies the currently selected scope object.
|
|
///
|
|
/// @pre The arguments must be of type number.
|
|
/// @pre There may not be more than two arguments.
|
|
/// @pre An argument may not be -1 if preceding arguments are not all -1.
|
|
///
|
|
/// @post The return value will be a string.
|
|
static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// Possible scope of working directory to return.
|
|
CdScope scope = kCdScopeInvalid;
|
|
|
|
// Numbers of the scope objects (window, tab) we want the working directory
|
|
// of. A `-1` means to skip this scope, a `0` means the current object.
|
|
int scope_number[] = {
|
|
[kCdScopeWindow] = 0, // Number of window to look at.
|
|
[kCdScopeTabpage] = 0, // Number of tab to look at.
|
|
};
|
|
|
|
char *cwd = NULL; // Current working directory to print
|
|
char *from = NULL; // The original string to copy
|
|
|
|
tabpage_T *tp = curtab; // The tabpage to look at.
|
|
win_T *win = curwin; // The window to look at.
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
// Pre-conditions and scope extraction together
|
|
for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
|
|
// If there is no argument there are no more scopes after it, break out.
|
|
if (argvars[i].v_type == VAR_UNKNOWN) {
|
|
break;
|
|
}
|
|
if (argvars[i].v_type != VAR_NUMBER) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
scope_number[i] = (int)argvars[i].vval.v_number;
|
|
// It is an error for the scope number to be less than `-1`.
|
|
if (scope_number[i] < -1) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
// Use the narrowest scope the user requested
|
|
if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
|
|
// The scope is the current iteration step.
|
|
scope = i;
|
|
} else if (scope_number[i] < 0) {
|
|
scope = i + 1;
|
|
}
|
|
}
|
|
|
|
// Find the tabpage by number
|
|
if (scope_number[kCdScopeTabpage] > 0) {
|
|
tp = find_tabpage(scope_number[kCdScopeTabpage]);
|
|
if (!tp) {
|
|
emsg(_("E5000: Cannot find tab number."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Find the window in `tp` by number, `NULL` if none.
|
|
if (scope_number[kCdScopeWindow] >= 0) {
|
|
if (scope_number[kCdScopeTabpage] < 0) {
|
|
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
|
return;
|
|
}
|
|
|
|
if (scope_number[kCdScopeWindow] > 0) {
|
|
win = find_win_by_nr(&argvars[0], tp);
|
|
if (!win) {
|
|
emsg(_("E5002: Cannot find window number."));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
cwd = xmalloc(MAXPATHL);
|
|
|
|
switch (scope) {
|
|
case kCdScopeWindow:
|
|
assert(win);
|
|
from = win->w_localdir;
|
|
if (from) {
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
case kCdScopeTabpage:
|
|
assert(tp);
|
|
from = tp->tp_localdir;
|
|
if (from) {
|
|
break;
|
|
}
|
|
FALLTHROUGH;
|
|
case kCdScopeGlobal:
|
|
if (globaldir) { // `globaldir` is not always set.
|
|
from = globaldir;
|
|
break;
|
|
}
|
|
FALLTHROUGH; // In global directory, just need to get OS CWD.
|
|
case kCdScopeInvalid: // If called without any arguments, get OS CWD.
|
|
if (os_dirname((char_u *)cwd, MAXPATHL) == FAIL) {
|
|
from = ""; // Return empty string on failure.
|
|
}
|
|
}
|
|
|
|
if (from) {
|
|
STRLCPY(cwd, from, MAXPATHL);
|
|
}
|
|
|
|
rettv->vval.v_string = xstrdup(cwd);
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
slash_adjust(rettv->vval.v_string);
|
|
#endif
|
|
|
|
xfree(cwd);
|
|
}
|
|
|
|
/// "getfontname()" function
|
|
static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
}
|
|
|
|
/// "getfperm({fname})" function
|
|
static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char *perm = NULL;
|
|
char_u flags[] = "rwx";
|
|
|
|
const char *filename = tv_get_string(&argvars[0]);
|
|
int32_t file_perm = os_getperm(filename);
|
|
if (file_perm >= 0) {
|
|
perm = xstrdup("---------");
|
|
for (int i = 0; i < 9; i++) {
|
|
if (file_perm & (1 << (8 - i))) {
|
|
perm[i] = (char)flags[i % 3];
|
|
}
|
|
}
|
|
}
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = perm;
|
|
}
|
|
|
|
/// "getfsize({fname})" function
|
|
static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *fname = tv_get_string(&argvars[0]);
|
|
|
|
rettv->v_type = VAR_NUMBER;
|
|
|
|
FileInfo file_info;
|
|
if (os_fileinfo(fname, &file_info)) {
|
|
uint64_t filesize = os_fileinfo_size(&file_info);
|
|
if (os_isdir((const char_u *)fname)) {
|
|
rettv->vval.v_number = 0;
|
|
} else {
|
|
rettv->vval.v_number = (varnumber_T)filesize;
|
|
|
|
// non-perfect check for overflow
|
|
if ((uint64_t)rettv->vval.v_number != filesize) {
|
|
rettv->vval.v_number = -2;
|
|
}
|
|
}
|
|
} else {
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
}
|
|
|
|
/// "getftime({fname})" function
|
|
static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *fname = tv_get_string(&argvars[0]);
|
|
|
|
FileInfo file_info;
|
|
if (os_fileinfo(fname, &file_info)) {
|
|
rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
|
|
} else {
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
}
|
|
|
|
/// "getftype({fname})" function
|
|
static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char_u *type = NULL;
|
|
char *t;
|
|
|
|
const char *fname = tv_get_string(&argvars[0]);
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
FileInfo file_info;
|
|
if (os_fileinfo_link(fname, &file_info)) {
|
|
uint64_t mode = file_info.stat.st_mode;
|
|
if (S_ISREG(mode)) {
|
|
t = "file";
|
|
} else if (S_ISDIR(mode)) {
|
|
t = "dir";
|
|
} else if (S_ISLNK(mode)) {
|
|
t = "link";
|
|
} else if (S_ISBLK(mode)) {
|
|
t = "bdev";
|
|
} else if (S_ISCHR(mode)) {
|
|
t = "cdev";
|
|
} else if (S_ISFIFO(mode)) {
|
|
t = "fifo";
|
|
} else if (S_ISSOCK(mode)) {
|
|
t = "socket";
|
|
} else {
|
|
t = "other";
|
|
}
|
|
type = vim_strsave((char_u *)t);
|
|
}
|
|
rettv->vval.v_string = (char *)type;
|
|
}
|
|
|
|
/// "getjumplist()" function
|
|
static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
win_T *const wp = find_tabwin(&argvars[0], &argvars[1]);
|
|
if (wp == NULL) {
|
|
return;
|
|
}
|
|
|
|
cleanup_jumplist(wp, true);
|
|
|
|
list_T *const l = tv_list_alloc(wp->w_jumplistlen);
|
|
tv_list_append_list(rettv->vval.v_list, l);
|
|
tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx);
|
|
|
|
for (int i = 0; i < wp->w_jumplistlen; i++) {
|
|
if (wp->w_jumplist[i].fmark.mark.lnum == 0) {
|
|
continue;
|
|
}
|
|
dict_T *const d = tv_dict_alloc();
|
|
tv_list_append_dict(l, d);
|
|
tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum);
|
|
tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col);
|
|
tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd);
|
|
tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum);
|
|
if (wp->w_jumplist[i].fname != NULL) {
|
|
tv_dict_add_str(d, S_LEN("filename"), wp->w_jumplist[i].fname);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "getline(lnum, [end])" function
|
|
static void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T end;
|
|
bool retlist;
|
|
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
if (argvars[1].v_type == VAR_UNKNOWN) {
|
|
end = lnum;
|
|
retlist = false;
|
|
} else {
|
|
end = tv_get_lnum(&argvars[1]);
|
|
retlist = true;
|
|
}
|
|
|
|
get_buffer_lines(curbuf, lnum, end, retlist, rettv);
|
|
}
|
|
|
|
/// "getmarklist()" function
|
|
static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
get_global_marks(rettv->vval.v_list);
|
|
return;
|
|
}
|
|
|
|
buf_T *buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
|
|
get_buf_local_marks(buf, rettv->vval.v_list);
|
|
}
|
|
|
|
/// "getmousepos()" function
|
|
static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int row = mouse_row;
|
|
int col = mouse_col;
|
|
int grid = mouse_grid;
|
|
varnumber_T winid = 0;
|
|
varnumber_T winrow = 0;
|
|
varnumber_T wincol = 0;
|
|
linenr_T lnum = 0;
|
|
varnumber_T column = 0;
|
|
|
|
tv_dict_alloc_ret(rettv);
|
|
dict_T *d = rettv->vval.v_dict;
|
|
|
|
tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1);
|
|
tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1);
|
|
|
|
win_T *wp = mouse_find_win(&grid, &row, &col);
|
|
if (wp != NULL) {
|
|
int height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
|
|
// The height is adjusted by 1 when there is a bottom border. This is not
|
|
// necessary for a top border since `row` starts at -1 in that case.
|
|
if (row < height + wp->w_border_adj[2]) {
|
|
winid = wp->handle;
|
|
winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border
|
|
wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border
|
|
if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) {
|
|
(void)mouse_comp_pos(wp, &row, &col, &lnum);
|
|
col = vcol2col(wp, lnum, col);
|
|
column = col + 1;
|
|
}
|
|
}
|
|
}
|
|
tv_dict_add_nr(d, S_LEN("winid"), winid);
|
|
tv_dict_add_nr(d, S_LEN("winrow"), winrow);
|
|
tv_dict_add_nr(d, S_LEN("wincol"), wincol);
|
|
tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum);
|
|
tv_dict_add_nr(d, S_LEN("column"), column);
|
|
}
|
|
|
|
/// "getpid()" function
|
|
static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = os_get_pid();
|
|
}
|
|
|
|
/// "getcurpos(string)" function
|
|
static void f_getcurpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
getpos_both(argvars, rettv, true, false);
|
|
}
|
|
|
|
static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
getpos_both(argvars, rettv, true, true);
|
|
}
|
|
|
|
/// "getpos(string)" function
|
|
static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
getpos_both(argvars, rettv, false, false);
|
|
}
|
|
|
|
/// 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_u *strregname;
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
strregname = (const char_u *)tv_get_string_chk(&argvars[0]);
|
|
if (strregname == NULL) { // type error; errmsg already given
|
|
return 0;
|
|
}
|
|
} else {
|
|
// Default to v:register
|
|
strregname = (char_u *)get_vim_var_str(VV_REG);
|
|
}
|
|
|
|
return *strregname == 0 ? '"' : *strregname;
|
|
}
|
|
|
|
/// "getreg()" function
|
|
static void f_getreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int arg2 = false;
|
|
bool return_list = false;
|
|
|
|
int regname = getreg_get_regname(argvars);
|
|
if (regname == 0) {
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
rettv->v_type = VAR_LIST;
|
|
rettv->vval.v_list =
|
|
get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);
|
|
if (rettv->vval.v_list == NULL) {
|
|
rettv->vval.v_list = tv_list_alloc(0);
|
|
}
|
|
tv_list_ref(rettv->vval.v_list);
|
|
} else {
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0);
|
|
}
|
|
}
|
|
|
|
/// "getregtype()" function
|
|
static void f_getregtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// on error return an empty string
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
int regname = getreg_get_regname(argvars);
|
|
if (regname == 0) {
|
|
return;
|
|
}
|
|
|
|
colnr_T reglen = 0;
|
|
char buf[NUMBUFLEN + 2];
|
|
MotionType reg_type = get_reg_type(regname, ®len);
|
|
format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
|
|
|
|
rettv->vval.v_string = xstrdup(buf);
|
|
}
|
|
|
|
/// "gettabinfo()" function
|
|
static void f_gettabinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tabpage_T *tparg = NULL;
|
|
|
|
tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN
|
|
? 1
|
|
: kListLenMayKnow));
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
// Information about one tab page
|
|
tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
|
|
if (tparg == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get information about a specific tab page or all tab pages
|
|
int tpnr = 0;
|
|
FOR_ALL_TABS(tp) {
|
|
tpnr++;
|
|
if (tparg != NULL && tp != tparg) {
|
|
continue;
|
|
}
|
|
dict_T *const d = get_tabpage_info(tp, tpnr);
|
|
tv_list_append_dict(rettv->vval.v_list, d);
|
|
if (tparg != NULL) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "gettagstack()" function
|
|
static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = curwin; // default is current window
|
|
|
|
tv_dict_alloc_ret(rettv);
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
get_tagstack(wp, rettv->vval.v_dict);
|
|
}
|
|
|
|
/// "getwininfo()" function
|
|
static void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wparg = NULL;
|
|
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
wparg = win_id2wp((int)tv_get_number(&argvars[0]));
|
|
if (wparg == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Collect information about either all the windows across all the tab
|
|
// pages or one particular window.
|
|
int16_t tabnr = 0;
|
|
FOR_ALL_TABS(tp) {
|
|
tabnr++;
|
|
int16_t winnr = 0;
|
|
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
|
|
winnr++;
|
|
if (wparg != NULL && wp != wparg) {
|
|
continue;
|
|
}
|
|
dict_T *const d = get_win_info(wp, tabnr, winnr);
|
|
tv_list_append_dict(rettv->vval.v_list, d);
|
|
if (wparg != NULL) {
|
|
// found information about a specific window
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Dummy timer callback. Used by f_wait().
|
|
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
|
|
{}
|
|
|
|
/// Dummy timer close callback. Used by f_wait().
|
|
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
|
|
{
|
|
xfree(tw);
|
|
}
|
|
|
|
/// "wait(timeout, condition[, interval])" function
|
|
static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
semsg(_(e_invargval), "1");
|
|
return;
|
|
}
|
|
if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN)
|
|
|| (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) {
|
|
semsg(_(e_invargval), "3");
|
|
return;
|
|
}
|
|
|
|
int timeout = (int)argvars[0].vval.v_number;
|
|
typval_T expr = argvars[1];
|
|
int interval = argvars[2].v_type == VAR_NUMBER
|
|
? (int)argvars[2].vval.v_number
|
|
: 200; // Default.
|
|
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
|
|
|
|
// Start dummy timer.
|
|
time_watcher_init(&main_loop, tw, NULL);
|
|
tw->events = main_loop.events;
|
|
tw->blockable = true;
|
|
time_watcher_start(tw, dummy_timer_due_cb, (uint64_t)interval, (uint64_t)interval);
|
|
|
|
typval_T argv = TV_INITIAL_VALUE;
|
|
typval_T exprval = TV_INITIAL_VALUE;
|
|
bool error = false;
|
|
const int called_emsg_before = called_emsg;
|
|
|
|
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
|
|
eval_expr_typval(&expr, &argv, 0, &exprval) != OK
|
|
|| tv_get_number_chk(&exprval, &error)
|
|
|| called_emsg > called_emsg_before || error || got_int);
|
|
|
|
if (called_emsg > called_emsg_before || error) {
|
|
rettv->vval.v_number = -3;
|
|
} else if (got_int) {
|
|
got_int = false;
|
|
vgetc();
|
|
rettv->vval.v_number = -2;
|
|
} else if (tv_get_number_chk(&exprval, &error)) {
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
|
|
// Stop dummy timer
|
|
time_watcher_stop(tw);
|
|
time_watcher_close(tw, dummy_timer_close_cb);
|
|
}
|
|
|
|
/// "win_screenpos()" function
|
|
static void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, 2);
|
|
const win_T *const wp = find_win_by_nr_or_id(&argvars[0]);
|
|
tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1);
|
|
tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
|
|
}
|
|
|
|
/// Move the window wp into a new split of targetwin in a given direction
|
|
static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags)
|
|
{
|
|
int height = wp->w_height;
|
|
win_T *oldwin = curwin;
|
|
|
|
if (wp == targetwin || wp == aucmd_win) {
|
|
return;
|
|
}
|
|
|
|
// Jump to the target window
|
|
if (curwin != targetwin) {
|
|
win_goto(targetwin);
|
|
}
|
|
|
|
// Remove the old window and frame from the tree of frames
|
|
int dir;
|
|
(void)winframe_remove(wp, &dir, NULL);
|
|
win_remove(wp, NULL);
|
|
last_status(false); // may need to remove last status line
|
|
(void)win_comp_pos(); // recompute window positions
|
|
|
|
// Split a window on the desired side and put the old window there
|
|
(void)win_split_ins(size, flags, wp, dir);
|
|
|
|
// If splitting horizontally, try to preserve height
|
|
if (size == 0 && !(flags & WSP_VERT)) {
|
|
win_setheight_win(height, wp);
|
|
if (p_ea) {
|
|
win_equal(wp, true, 'v');
|
|
}
|
|
}
|
|
|
|
if (oldwin != curwin) {
|
|
win_goto(oldwin);
|
|
}
|
|
}
|
|
|
|
/// "win_splitmove()" function
|
|
static void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
win_T *targetwin = find_win_by_nr_or_id(&argvars[1]);
|
|
|
|
if (wp == NULL || targetwin == NULL || wp == targetwin
|
|
|| !win_valid(wp) || !win_valid(targetwin)
|
|
|| win_valid_floating(wp) || win_valid_floating(targetwin)) {
|
|
emsg(_(e_invalwindow));
|
|
rettv->vval.v_number = -1;
|
|
return;
|
|
}
|
|
|
|
int flags = 0, size = 0;
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
dict_T *d;
|
|
dictitem_T *di;
|
|
|
|
if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
d = argvars[2].vval.v_dict;
|
|
if (tv_dict_get_number(d, "vertical")) {
|
|
flags |= WSP_VERT;
|
|
}
|
|
if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) {
|
|
flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE;
|
|
}
|
|
size = (int)tv_dict_get_number(d, "size");
|
|
}
|
|
|
|
win_move_into_split(wp, targetwin, size, flags);
|
|
}
|
|
|
|
/// "getwinpos({timeout})" function
|
|
static void f_getwinpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, 2);
|
|
tv_list_append_number(rettv->vval.v_list, -1);
|
|
tv_list_append_number(rettv->vval.v_list, -1);
|
|
}
|
|
|
|
/// "getwinposx()" function
|
|
static void f_getwinposx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
|
|
/// "getwinposy()" function
|
|
static void f_getwinposy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
|
|
/// "glob()" function
|
|
static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int options = WILD_SILENT|WILD_USE_NL;
|
|
expand_T xpc;
|
|
bool error = false;
|
|
|
|
// When the optional second argument is non-zero, don't remove matches
|
|
// for 'wildignore' and don't put matches for 'suffixes' at the end.
|
|
rettv->v_type = VAR_STRING;
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
if (tv_get_number_chk(&argvars[1], &error)) {
|
|
options |= WILD_KEEP_ALL;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
if (tv_get_number_chk(&argvars[2], &error)) {
|
|
tv_list_set_ret(rettv, NULL);
|
|
}
|
|
if (argvars[3].v_type != VAR_UNKNOWN
|
|
&& tv_get_number_chk(&argvars[3], &error)) {
|
|
options |= WILD_ALLLINKS;
|
|
}
|
|
}
|
|
}
|
|
if (!error) {
|
|
ExpandInit(&xpc);
|
|
xpc.xp_context = EXPAND_FILES;
|
|
if (p_wic) {
|
|
options += WILD_ICASE;
|
|
}
|
|
if (rettv->v_type == VAR_STRING) {
|
|
rettv->vval.v_string = (char *)ExpandOne(&xpc, (char_u *)
|
|
tv_get_string(&argvars[0]), NULL, options,
|
|
WILD_ALL);
|
|
} else {
|
|
ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options,
|
|
WILD_ALL_KEEP);
|
|
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
|
|
for (int i = 0; i < xpc.xp_numfiles; i++) {
|
|
tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
|
|
-1);
|
|
}
|
|
ExpandCleanup(&xpc);
|
|
}
|
|
} else {
|
|
rettv->vval.v_string = NULL;
|
|
}
|
|
}
|
|
|
|
/// "globpath()" function
|
|
static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath.
|
|
bool error = false;
|
|
|
|
// Return a string, or a list if the optional third argument is non-zero.
|
|
rettv->v_type = VAR_STRING;
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
// When the optional second argument is non-zero, don't remove matches
|
|
// for 'wildignore' and don't put matches for 'suffixes' at the end.
|
|
if (tv_get_number_chk(&argvars[2], &error)) {
|
|
flags |= WILD_KEEP_ALL;
|
|
}
|
|
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
if (tv_get_number_chk(&argvars[3], &error)) {
|
|
tv_list_set_ret(rettv, NULL);
|
|
}
|
|
if (argvars[4].v_type != VAR_UNKNOWN
|
|
&& tv_get_number_chk(&argvars[4], &error)) {
|
|
flags |= WILD_ALLLINKS;
|
|
}
|
|
}
|
|
}
|
|
|
|
char buf1[NUMBUFLEN];
|
|
const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
|
|
if (file != NULL && !error) {
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char_u *), 10);
|
|
globpath((char *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
|
|
|
|
if (rettv->v_type == VAR_STRING) {
|
|
rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
|
|
} else {
|
|
tv_list_alloc_ret(rettv, ga.ga_len);
|
|
for (int i = 0; i < ga.ga_len; i++) {
|
|
tv_list_append_string(rettv->vval.v_list,
|
|
((const char **)(ga.ga_data))[i], -1);
|
|
}
|
|
}
|
|
|
|
ga_clear_strings(&ga);
|
|
} else {
|
|
rettv->vval.v_string = NULL;
|
|
}
|
|
}
|
|
|
|
/// "glob2regpat()" function
|
|
static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false);
|
|
}
|
|
|
|
/// "has()" function
|
|
static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
static const char *const has_list[] = {
|
|
#if defined(BSD) && !defined(__APPLE__)
|
|
"bsd",
|
|
#endif
|
|
#ifdef __linux__
|
|
"linux",
|
|
#endif
|
|
#ifdef SUN_SYSTEM
|
|
"sun",
|
|
#endif
|
|
#ifdef UNIX
|
|
"unix",
|
|
#endif
|
|
#if defined(WIN32)
|
|
"win32",
|
|
#endif
|
|
#ifdef _WIN64
|
|
"win64",
|
|
#endif
|
|
#ifndef CASE_INSENSITIVE_FILENAME
|
|
"fname_case",
|
|
#endif
|
|
#ifdef HAVE_ACL
|
|
"acl",
|
|
#endif
|
|
"autochdir",
|
|
"arabic",
|
|
"autocmd",
|
|
"browsefilter",
|
|
"byte_offset",
|
|
"cindent",
|
|
"cmdline_compl",
|
|
"cmdline_hist",
|
|
"cmdwin",
|
|
"comments",
|
|
"conceal",
|
|
"cscope",
|
|
"cursorbind",
|
|
"cursorshape",
|
|
#ifdef DEBUG
|
|
"debug",
|
|
#endif
|
|
"dialog_con",
|
|
"diff",
|
|
"digraphs",
|
|
"eval", // always present, of course!
|
|
"ex_extra",
|
|
"extra_search",
|
|
"file_in_path",
|
|
"filterpipe",
|
|
"find_in_path",
|
|
"float",
|
|
"folding",
|
|
#if defined(UNIX)
|
|
"fork",
|
|
#endif
|
|
"gettext",
|
|
#if defined(HAVE_ICONV)
|
|
"iconv",
|
|
#endif
|
|
"insert_expand",
|
|
"jumplist",
|
|
"keymap",
|
|
"lambda",
|
|
"langmap",
|
|
"libcall",
|
|
"linebreak",
|
|
"lispindent",
|
|
"listcmds",
|
|
"localmap",
|
|
#ifdef __APPLE__
|
|
"mac",
|
|
"macunix",
|
|
"osx",
|
|
"osxdarwin",
|
|
#endif
|
|
"menu",
|
|
"mksession",
|
|
"modify_fname",
|
|
"mouse",
|
|
"multi_byte",
|
|
"multi_lang",
|
|
"nanotime",
|
|
"num64",
|
|
"packages",
|
|
"path_extra",
|
|
"persistent_undo",
|
|
"postscript",
|
|
"printer",
|
|
"profile",
|
|
"pythonx",
|
|
"reltime",
|
|
"quickfix",
|
|
"rightleft",
|
|
"scrollbind",
|
|
"showcmd",
|
|
"cmdline_info",
|
|
"shada",
|
|
"signs",
|
|
"smartindent",
|
|
"startuptime",
|
|
"statusline",
|
|
"spell",
|
|
"syntax",
|
|
#if !defined(UNIX)
|
|
"system", // TODO(SplinterOfChaos): This IS defined for UNIX!
|
|
#endif
|
|
"tablineat",
|
|
"tag_binary",
|
|
"termguicolors",
|
|
"termresponse",
|
|
"textobjects",
|
|
"timers",
|
|
"title",
|
|
"user-commands", // was accidentally included in 5.4
|
|
"user_commands",
|
|
"vartabs",
|
|
"vertsplit",
|
|
"vimscript-1",
|
|
"virtualedit",
|
|
"visual",
|
|
"visualextra",
|
|
"vreplace",
|
|
"wildignore",
|
|
"wildmenu",
|
|
"windows",
|
|
"winaltkeys",
|
|
"writebackup",
|
|
"nvim",
|
|
};
|
|
|
|
// XXX: eval_has_provider() may shell out :(
|
|
const int save_shell_error = (int)get_vim_var_nr(VV_SHELL_ERROR);
|
|
bool n = false;
|
|
const char *const name = tv_get_string(&argvars[0]);
|
|
for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) {
|
|
if (STRICMP(name, has_list[i]) == 0) {
|
|
n = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!n) {
|
|
if (STRNICMP(name, "patch", 5) == 0) {
|
|
if (name[5] == '-'
|
|
&& strlen(name) >= 11
|
|
&& ascii_isdigit(name[6])
|
|
&& ascii_isdigit(name[8])
|
|
&& ascii_isdigit(name[10])) {
|
|
int major = atoi(name + 6);
|
|
int minor = atoi(name + 8);
|
|
|
|
// Expect "patch-9.9.01234".
|
|
n = (major < VIM_VERSION_MAJOR
|
|
|| (major == VIM_VERSION_MAJOR
|
|
&& (minor < VIM_VERSION_MINOR
|
|
|| (minor == VIM_VERSION_MINOR
|
|
&& has_vim_patch(atoi(name + 10))))));
|
|
} else {
|
|
n = has_vim_patch(atoi(name + 5));
|
|
}
|
|
} else if (STRNICMP(name, "nvim-", 5) == 0) {
|
|
// Expect "nvim-x.y.z"
|
|
n = has_nvim_version(name + 5);
|
|
} else if (STRICMP(name, "vim_starting") == 0) {
|
|
n = (starting != 0);
|
|
} else if (STRICMP(name, "ttyin") == 0) {
|
|
n = stdin_isatty;
|
|
} else if (STRICMP(name, "ttyout") == 0) {
|
|
n = stdout_isatty;
|
|
} else if (STRICMP(name, "multi_byte_encoding") == 0) {
|
|
n = true;
|
|
} else if (STRICMP(name, "syntax_items") == 0) {
|
|
n = syntax_present(curwin);
|
|
} else if (STRICMP(name, "clipboard_working") == 0) {
|
|
n = eval_has_provider("clipboard");
|
|
} else if (STRICMP(name, "wsl") == 0) {
|
|
n = has_wsl();
|
|
#ifdef UNIX
|
|
} else if (STRICMP(name, "unnamedplus") == 0) {
|
|
n = eval_has_provider("clipboard");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (!n && eval_has_provider(name)) {
|
|
n = true;
|
|
}
|
|
|
|
set_vim_var_nr(VV_SHELL_ERROR, save_shell_error);
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
static bool has_wsl(void)
|
|
{
|
|
static TriState has_wsl = kNone;
|
|
if (has_wsl == kNone) {
|
|
Error err = ERROR_INIT;
|
|
Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.loop.os_uname()['release']:lower()"
|
|
":match('microsoft') and true or false"),
|
|
(Array)ARRAY_DICT_INIT, &err);
|
|
assert(!ERROR_SET(&err));
|
|
assert(o.type == kObjectTypeBoolean);
|
|
has_wsl = o.data.boolean ? kTrue : kFalse;
|
|
api_free_object(o);
|
|
}
|
|
return has_wsl == kTrue;
|
|
}
|
|
|
|
/// `haslocaldir([{win}[, {tab}]])` function
|
|
///
|
|
/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
|
|
/// scope object is not specified the current one is implied. This function
|
|
/// share a lot of code with `f_getcwd`.
|
|
///
|
|
/// @pre The arguments must be of type number.
|
|
/// @pre There may not be more than two arguments.
|
|
/// @pre An argument may not be -1 if preceding arguments are not all -1.
|
|
///
|
|
/// @post The return value will be either the number `1` or `0`.
|
|
static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// Possible scope of working directory to return.
|
|
CdScope scope = kCdScopeInvalid;
|
|
|
|
// Numbers of the scope objects (window, tab) we want the working directory
|
|
// of. A `-1` means to skip this scope, a `0` means the current object.
|
|
int scope_number[] = {
|
|
[kCdScopeWindow] = 0, // Number of window to look at.
|
|
[kCdScopeTabpage] = 0, // Number of tab to look at.
|
|
};
|
|
|
|
tabpage_T *tp = curtab; // The tabpage to look at.
|
|
win_T *win = curwin; // The window to look at.
|
|
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
// Pre-conditions and scope extraction together
|
|
for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
|
|
if (argvars[i].v_type == VAR_UNKNOWN) {
|
|
break;
|
|
}
|
|
if (argvars[i].v_type != VAR_NUMBER) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
scope_number[i] = (int)argvars[i].vval.v_number;
|
|
if (scope_number[i] < -1) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
// Use the narrowest scope the user requested
|
|
if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
|
|
// The scope is the current iteration step.
|
|
scope = i;
|
|
} else if (scope_number[i] < 0) {
|
|
scope = i + 1;
|
|
}
|
|
}
|
|
|
|
// If the user didn't specify anything, default to window scope
|
|
if (scope == kCdScopeInvalid) {
|
|
scope = MIN_CD_SCOPE;
|
|
}
|
|
|
|
// Find the tabpage by number
|
|
if (scope_number[kCdScopeTabpage] > 0) {
|
|
tp = find_tabpage(scope_number[kCdScopeTabpage]);
|
|
if (!tp) {
|
|
emsg(_("E5000: Cannot find tab number."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Find the window in `tp` by number, `NULL` if none.
|
|
if (scope_number[kCdScopeWindow] >= 0) {
|
|
if (scope_number[kCdScopeTabpage] < 0) {
|
|
emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
|
|
return;
|
|
}
|
|
|
|
if (scope_number[kCdScopeWindow] > 0) {
|
|
win = find_win_by_nr(&argvars[0], tp);
|
|
if (!win) {
|
|
emsg(_("E5002: Cannot find window number."));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (scope) {
|
|
case kCdScopeWindow:
|
|
assert(win);
|
|
rettv->vval.v_number = win->w_localdir ? 1 : 0;
|
|
break;
|
|
case kCdScopeTabpage:
|
|
assert(tp);
|
|
rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
|
|
break;
|
|
case kCdScopeGlobal:
|
|
// The global scope never has a local directory
|
|
break;
|
|
case kCdScopeInvalid:
|
|
// We should never get here
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/// "highlightID(name)" function
|
|
static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/// "highlight_exists()" function
|
|
static void f_hlexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/// "hostname()" function
|
|
static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char hostname[256];
|
|
|
|
os_get_hostname(hostname, 256);
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (char *)vim_strsave((char_u *)hostname);
|
|
}
|
|
|
|
/// iconv() function
|
|
static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
vimconv_T vimconv;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
const char *const str = tv_get_string(&argvars[0]);
|
|
char buf1[NUMBUFLEN];
|
|
char_u *const from = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[1], buf1)));
|
|
char buf2[NUMBUFLEN];
|
|
char_u *const to = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[2], buf2)));
|
|
vimconv.vc_type = CONV_NONE;
|
|
convert_setup(&vimconv, from, to);
|
|
|
|
// If the encodings are equal, no conversion needed.
|
|
if (vimconv.vc_type == CONV_NONE) {
|
|
rettv->vval.v_string = xstrdup(str);
|
|
} else {
|
|
rettv->vval.v_string = (char *)string_convert(&vimconv, (char_u *)str, NULL);
|
|
}
|
|
|
|
convert_setup(&vimconv, NULL, NULL);
|
|
xfree(from);
|
|
xfree(to);
|
|
}
|
|
|
|
/// "indent()" function
|
|
static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
|
|
rettv->vval.v_number = get_indent_lnum(lnum);
|
|
} else {
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
}
|
|
|
|
/// "index()" function
|
|
static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
long idx = 0;
|
|
bool ic = false;
|
|
|
|
rettv->vval.v_number = -1;
|
|
if (argvars[0].v_type == VAR_BLOB) {
|
|
bool error = false;
|
|
int start = 0;
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
start = (int)tv_get_number_chk(&argvars[2], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
}
|
|
blob_T *const b = argvars[0].vval.v_blob;
|
|
if (b == NULL) {
|
|
return;
|
|
}
|
|
if (start < 0) {
|
|
start = tv_blob_len(b) + start;
|
|
if (start < 0) {
|
|
start = 0;
|
|
}
|
|
}
|
|
for (idx = start; idx < tv_blob_len(b); idx++) {
|
|
typval_T tv;
|
|
tv.v_type = VAR_NUMBER;
|
|
tv.vval.v_number = tv_blob_get(b, (int)idx);
|
|
if (tv_equal(&tv, &argvars[1], ic, false)) {
|
|
rettv->vval.v_number = idx;
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
} else if (argvars[0].v_type != VAR_LIST) {
|
|
emsg(_(e_listblobreq));
|
|
return;
|
|
}
|
|
list_T *const l = argvars[0].vval.v_list;
|
|
if (l != NULL) {
|
|
listitem_T *item = tv_list_first(l);
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
bool error = false;
|
|
|
|
// Start at specified item.
|
|
idx = tv_list_uidx(l, (int)tv_get_number_chk(&argvars[2], &error));
|
|
if (error || idx == -1) {
|
|
item = NULL;
|
|
} else {
|
|
item = tv_list_find(l, (int)idx);
|
|
assert(item != NULL);
|
|
}
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
ic = !!tv_get_number_chk(&argvars[3], &error);
|
|
if (error) {
|
|
item = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
|
|
if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {
|
|
rettv->vval.v_number = idx;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool inputsecret_flag = false;
|
|
|
|
/// "input()" function
|
|
/// Also handles inputsecret() when inputsecret is set.
|
|
static void f_input(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
get_user_input(argvars, rettv, false, inputsecret_flag);
|
|
}
|
|
|
|
/// "inputdialog()" function
|
|
static void f_inputdialog(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
get_user_input(argvars, rettv, true, inputsecret_flag);
|
|
}
|
|
|
|
/// "inputlist()" function
|
|
static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_LIST) {
|
|
semsg(_(e_listarg), "inputlist()");
|
|
return;
|
|
}
|
|
|
|
msg_start();
|
|
msg_row = Rows - 1; // for when 'cmdheight' > 1
|
|
lines_left = Rows; // avoid more prompt
|
|
msg_scroll = true;
|
|
msg_clr_eos();
|
|
|
|
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
|
|
msg_puts(tv_get_string(TV_LIST_ITEM_TV(li)));
|
|
msg_putchar('\n');
|
|
});
|
|
|
|
// Ask for choice.
|
|
int mouse_used;
|
|
int selected = prompt_for_number(&mouse_used);
|
|
if (mouse_used) {
|
|
selected -= lines_left;
|
|
}
|
|
|
|
rettv->vval.v_number = selected;
|
|
}
|
|
|
|
static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL };
|
|
|
|
/// "inputrestore()" function
|
|
static void f_inputrestore(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (!GA_EMPTY(&ga_userinput)) {
|
|
ga_userinput.ga_len--;
|
|
restore_typeahead((tasave_T *)(ga_userinput.ga_data)
|
|
+ ga_userinput.ga_len);
|
|
// default return is zero == OK
|
|
} else if (p_verbose > 1) {
|
|
verb_msg(_("called inputrestore() more often than inputsave()"));
|
|
rettv->vval.v_number = 1; // Failed
|
|
}
|
|
}
|
|
|
|
/// "inputsave()" function
|
|
static void f_inputsave(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// Add an entry to the stack of typeahead storage.
|
|
tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput);
|
|
save_typeahead(p);
|
|
}
|
|
|
|
/// "inputsecret()" function
|
|
static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
cmdline_star++;
|
|
inputsecret_flag = true;
|
|
f_input(argvars, rettv, fptr);
|
|
cmdline_star--;
|
|
inputsecret_flag = false;
|
|
}
|
|
|
|
/// "insert()" function
|
|
static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
list_T *l;
|
|
bool error = false;
|
|
|
|
if (argvars[0].v_type == VAR_BLOB) {
|
|
blob_T *const b = argvars[0].vval.v_blob;
|
|
|
|
if (b == NULL
|
|
|| var_check_lock(b->bv_lock, N_("insert() argument"),
|
|
TV_TRANSLATE)) {
|
|
return;
|
|
}
|
|
|
|
long before = 0;
|
|
const int len = tv_blob_len(b);
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
before = (long)tv_get_number_chk(&argvars[2], &error);
|
|
if (error) {
|
|
return; // type error; errmsg already given
|
|
}
|
|
if (before < 0 || before > len) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[2]));
|
|
return;
|
|
}
|
|
}
|
|
const int val = (int)tv_get_number_chk(&argvars[1], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
if (val < 0 || val > 255) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[1]));
|
|
return;
|
|
}
|
|
|
|
ga_grow(&b->bv_ga, 1);
|
|
char_u *const p = (char_u *)b->bv_ga.ga_data;
|
|
memmove(p + before + 1, p + before, (size_t)(len - before));
|
|
*(p + before) = (char_u)val;
|
|
b->bv_ga.ga_len++;
|
|
|
|
tv_copy(&argvars[0], rettv);
|
|
} else if (argvars[0].v_type != VAR_LIST) {
|
|
semsg(_(e_listblobarg), "insert()");
|
|
} else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
|
|
N_("insert() argument"), TV_TRANSLATE)) {
|
|
long before = 0;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
before = tv_get_number_chk(&argvars[2], &error);
|
|
}
|
|
if (error) {
|
|
// type error; errmsg already given
|
|
return;
|
|
}
|
|
|
|
listitem_T *item = NULL;
|
|
if (before != tv_list_len(l)) {
|
|
item = tv_list_find(l, (int)before);
|
|
if (item == NULL) {
|
|
semsg(_(e_listidx), (int64_t)before);
|
|
l = NULL;
|
|
}
|
|
}
|
|
if (l != NULL) {
|
|
tv_list_insert_tv(l, &argvars[1], item);
|
|
tv_copy(&argvars[0], rettv);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "interrupt()" function
|
|
static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED,
|
|
EvalFuncData fptr FUNC_ATTR_UNUSED)
|
|
{
|
|
got_int = true;
|
|
}
|
|
|
|
/// "invert(expr)" function
|
|
static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
|
|
}
|
|
|
|
/// "isdirectory()" function
|
|
static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/// "islocked()" function
|
|
static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
lval_T lv;
|
|
|
|
rettv->vval.v_number = -1;
|
|
const char_u *const end = (char_u *)get_lval((char *)tv_get_string(&argvars[0]),
|
|
NULL,
|
|
&lv, false, false,
|
|
GLV_NO_AUTOLOAD|GLV_READ_ONLY,
|
|
FNE_CHECK_START);
|
|
if (end != NULL && lv.ll_name != NULL) {
|
|
if (*end != NUL) {
|
|
semsg(_(e_trailing_arg), end);
|
|
} else {
|
|
if (lv.ll_tv == NULL) {
|
|
dictitem_T *di = find_var(lv.ll_name, lv.ll_name_len, NULL, true);
|
|
if (di != NULL) {
|
|
// Consider a variable locked when:
|
|
// 1. the variable itself is locked
|
|
// 2. the value of the variable is locked.
|
|
// 3. the List or Dict value is locked.
|
|
rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
|
|
|| tv_islocked(&di->di_tv));
|
|
}
|
|
} else if (lv.ll_range) {
|
|
emsg(_("E786: Range not allowed"));
|
|
} else if (lv.ll_newkey != NULL) {
|
|
semsg(_(e_dictkey), lv.ll_newkey);
|
|
} else if (lv.ll_list != NULL) {
|
|
// List item.
|
|
rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li));
|
|
} else {
|
|
// Dictionary item.
|
|
rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
|
|
}
|
|
}
|
|
}
|
|
|
|
clear_lval(&lv);
|
|
}
|
|
|
|
/// "isinf()" function
|
|
static void f_isinf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type == VAR_FLOAT
|
|
&& xisinf(argvars[0].vval.v_float)) {
|
|
rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1;
|
|
}
|
|
}
|
|
|
|
/// "isnan()" function
|
|
static void f_isnan(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT
|
|
&& xisnan(argvars[0].vval.v_float);
|
|
}
|
|
|
|
/// "id()" function
|
|
static void f_id(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars);
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xmalloc((size_t)len + 1);
|
|
vim_vsnprintf_typval(rettv->vval.v_string, (size_t)len + 1, "%p", dummy_ap, argvars);
|
|
}
|
|
|
|
/// "jobpid(id)" function
|
|
static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
Channel *data = find_job((uint64_t)argvars[0].vval.v_number, true);
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
Process *proc = &data->stream.proc;
|
|
rettv->vval.v_number = proc->pid;
|
|
}
|
|
|
|
/// "jobresize(job, width, height)" function
|
|
static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER
|
|
|| argvars[2].v_type != VAR_NUMBER) {
|
|
// job id, width, height
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
Channel *data = find_job((uint64_t)argvars[0].vval.v_number, true);
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
if (data->stream.proc.type != kProcessTypePty) {
|
|
emsg(_(e_channotpty));
|
|
return;
|
|
}
|
|
|
|
pty_process_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
|
|
(uint16_t)argvars[2].vval.v_number);
|
|
rettv->vval.v_number = 1;
|
|
}
|
|
|
|
static const char *ignored_env_vars[] = {
|
|
#ifndef WIN32
|
|
"COLUMNS",
|
|
"LINES",
|
|
"TERMCAP",
|
|
"COLORFGBG",
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
/// According to comments in src/win/process.c of libuv, Windows has a few
|
|
/// "essential" environment variables.
|
|
static const char *required_env_vars[] = {
|
|
#ifdef WIN32
|
|
"HOMEDRIVE",
|
|
"HOMEPATH",
|
|
"LOGONSERVER",
|
|
"PATH",
|
|
"SYSTEMDRIVE",
|
|
"SYSTEMROOT",
|
|
"TEMP",
|
|
"USERDOMAIN",
|
|
"USERNAME",
|
|
"USERPROFILE",
|
|
"WINDIR",
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
static dict_T *create_environment(const dictitem_T *job_env, const bool clear_env, const bool pty,
|
|
const char * const pty_term_name)
|
|
{
|
|
dict_T *env = tv_dict_alloc();
|
|
|
|
if (!clear_env) {
|
|
typval_T temp_env = TV_INITIAL_VALUE;
|
|
f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL });
|
|
tv_dict_extend(env, temp_env.vval.v_dict, "force");
|
|
tv_dict_free(temp_env.vval.v_dict);
|
|
|
|
if (pty) {
|
|
// These environment variables generally shouldn't be propagated to the
|
|
// child process. We're removing them here so the user can still decide
|
|
// they want to explicitly set them.
|
|
for (size_t i = 0;
|
|
i < ARRAY_SIZE(ignored_env_vars) && ignored_env_vars[i];
|
|
i++) {
|
|
dictitem_T *dv = tv_dict_find(env, ignored_env_vars[i], -1);
|
|
if (dv) {
|
|
tv_dict_item_remove(env, dv);
|
|
}
|
|
}
|
|
#ifndef WIN32
|
|
// Set COLORTERM to "truecolor" if termguicolors is set and 256
|
|
// otherwise, but only if it was set in the parent terminal at all
|
|
dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM"));
|
|
if (dv) {
|
|
tv_dict_item_remove(env, dv);
|
|
tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// For a pty, we need a sane $TERM set. We can't rely on nvim's environment,
|
|
// because the child process is going to be communicating with nvim, not the
|
|
// parent terminal. Set a sane default, but let the user override it in the
|
|
// job's environment if they want.
|
|
if (pty) {
|
|
dictitem_T *dv = tv_dict_find(env, S_LEN("TERM"));
|
|
if (dv) {
|
|
tv_dict_item_remove(env, dv);
|
|
}
|
|
tv_dict_add_str(env, S_LEN("TERM"), pty_term_name);
|
|
}
|
|
|
|
// Set $NVIM (in the child process) to v:servername. #3118
|
|
char *nvim_addr = get_vim_var_str(VV_SEND_SERVER);
|
|
if (nvim_addr[0] != '\0') {
|
|
dictitem_T *dv = tv_dict_find(env, S_LEN("NVIM"));
|
|
if (dv) {
|
|
tv_dict_item_remove(env, dv);
|
|
}
|
|
tv_dict_add_str(env, S_LEN("NVIM"), nvim_addr);
|
|
}
|
|
|
|
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) {
|
|
// Now that the custom environment is configured, we need to ensure certain
|
|
// environment variables are present.
|
|
for (size_t i = 0;
|
|
i < ARRAY_SIZE(required_env_vars) && required_env_vars[i];
|
|
i++) {
|
|
size_t len = strlen(required_env_vars[i]);
|
|
dictitem_T *dv = tv_dict_find(env, required_env_vars[i], (ptrdiff_t)len);
|
|
if (!dv) {
|
|
const char *env_var = os_getenv(required_env_vars[i]);
|
|
if (env_var) {
|
|
tv_dict_add_str(env, required_env_vars[i], len, env_var);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return env;
|
|
}
|
|
|
|
/// "jobstart()" function
|
|
static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
bool executable = true;
|
|
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
|
|
dict_T *env = NULL;
|
|
if (!argv) {
|
|
rettv->vval.v_number = executable ? 0 : -1;
|
|
return; // Did error message in tv_to_argv.
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
|
|
// Wrong argument types
|
|
semsg(_(e_invarg2), "expected dictionary");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
|
|
dict_T *job_opts = NULL;
|
|
bool detach = false;
|
|
bool rpc = false;
|
|
bool pty = false;
|
|
bool clear_env = false;
|
|
bool overlapped = false;
|
|
ChannelStdinMode stdin_mode = kChannelStdinPipe;
|
|
CallbackReader on_stdout = CALLBACK_READER_INIT,
|
|
on_stderr = CALLBACK_READER_INIT;
|
|
Callback on_exit = CALLBACK_NONE;
|
|
char *cwd = NULL;
|
|
dictitem_T *job_env = NULL;
|
|
if (argvars[1].v_type == VAR_DICT) {
|
|
job_opts = argvars[1].vval.v_dict;
|
|
|
|
detach = tv_dict_get_number(job_opts, "detach") != 0;
|
|
rpc = tv_dict_get_number(job_opts, "rpc") != 0;
|
|
pty = tv_dict_get_number(job_opts, "pty") != 0;
|
|
clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
|
|
overlapped = tv_dict_get_number(job_opts, "overlapped") != 0;
|
|
|
|
char *s = tv_dict_get_string(job_opts, "stdin", false);
|
|
if (s) {
|
|
if (!strncmp(s, "null", NUMBUFLEN)) {
|
|
stdin_mode = kChannelStdinNull;
|
|
} else if (!strncmp(s, "pipe", NUMBUFLEN)) {
|
|
// Nothing to do, default value
|
|
} else {
|
|
semsg(_(e_invargNval), "stdin", s);
|
|
}
|
|
}
|
|
|
|
if (pty && rpc) {
|
|
semsg(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
if (pty && overlapped) {
|
|
semsg(_(e_invarg2),
|
|
"job cannot have both 'pty' and 'overlapped' options set");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
|
|
if (new_cwd && *new_cwd != NUL) {
|
|
cwd = new_cwd;
|
|
// The new cwd must be a directory.
|
|
if (!os_isdir((const char_u *)cwd)) {
|
|
semsg(_(e_invarg2), "expected valid directory");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
job_env = tv_dict_find(job_opts, S_LEN("env"));
|
|
if (job_env && job_env->di_tv.v_type != VAR_DICT) {
|
|
semsg(_(e_invarg2), "env");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
|
|
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint16_t width = 0, height = 0;
|
|
char *term_name = NULL;
|
|
|
|
if (pty) {
|
|
width = (uint16_t)tv_dict_get_number(job_opts, "width");
|
|
height = (uint16_t)tv_dict_get_number(job_opts, "height");
|
|
// Legacy method, before env option existed, to specify $TERM. No longer
|
|
// documented, but still usable to avoid breaking scripts.
|
|
term_name = tv_dict_get_string(job_opts, "TERM", false);
|
|
if (!term_name) {
|
|
term_name = "ansi";
|
|
}
|
|
}
|
|
|
|
env = create_environment(job_env, clear_env, pty, term_name);
|
|
|
|
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
|
|
rpc, overlapped, detach, stdin_mode, cwd,
|
|
width, height, env, &rettv->vval.v_number);
|
|
if (chan) {
|
|
channel_create_event(chan, NULL);
|
|
}
|
|
}
|
|
|
|
/// "jobstop()" function
|
|
static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
// Only argument is the job id
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
Channel *data = find_job((uint64_t)argvars[0].vval.v_number, false);
|
|
if (!data) {
|
|
return;
|
|
}
|
|
|
|
const char *error = NULL;
|
|
if (data->is_rpc) {
|
|
// Ignore return code, but show error later.
|
|
(void)channel_close(data->id, kChannelPartRpc, &error);
|
|
}
|
|
process_stop(&data->stream.proc);
|
|
rettv->vval.v_number = 1;
|
|
if (error) {
|
|
emsg(error);
|
|
}
|
|
}
|
|
|
|
/// "jobwait(ids[, timeout])" function
|
|
static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER
|
|
&& argvars[1].v_type != VAR_UNKNOWN)) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
ui_busy_start();
|
|
list_T *args = argvars[0].vval.v_list;
|
|
Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs));
|
|
MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
|
|
|
|
// Validate, prepare jobs for waiting.
|
|
int i = 0;
|
|
TV_LIST_ITER_CONST(args, arg, {
|
|
Channel *chan = NULL;
|
|
if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER
|
|
|| !(chan = find_channel((uint64_t)TV_LIST_ITEM_TV(arg)->vval.v_number))
|
|
|| chan->streamtype != kChannelStreamProc) {
|
|
jobs[i] = NULL; // Invalid job.
|
|
} else if (process_is_stopped(&chan->stream.proc)) {
|
|
// Job is stopped but not fully destroyed.
|
|
// Ensure all callbacks on its event queue are executed. #15402
|
|
process_wait(&chan->stream.proc, -1, NULL);
|
|
jobs[i] = NULL; // Invalid job.
|
|
} else {
|
|
jobs[i] = chan;
|
|
channel_incref(chan);
|
|
if (chan->stream.proc.status < 0) {
|
|
// Flush any events in the job's queue before temporarily replacing it.
|
|
multiqueue_process_events(chan->events);
|
|
multiqueue_replace_parent(chan->events, waiting_jobs);
|
|
}
|
|
}
|
|
i++;
|
|
});
|
|
|
|
int remaining = -1;
|
|
uint64_t before = 0;
|
|
if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) {
|
|
remaining = (int)argvars[1].vval.v_number;
|
|
before = os_hrtime();
|
|
}
|
|
|
|
for (i = 0; i < tv_list_len(args); i++) {
|
|
if (remaining == 0) {
|
|
break; // Timeout.
|
|
}
|
|
if (jobs[i] == NULL) {
|
|
continue; // Invalid job, will assign status=-3 below.
|
|
}
|
|
int status = process_wait(&jobs[i]->stream.proc, remaining,
|
|
waiting_jobs);
|
|
if (status < 0) {
|
|
break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
|
|
}
|
|
if (remaining > 0) {
|
|
uint64_t now = os_hrtime();
|
|
remaining = MIN(0, remaining - (int)((now - before) / 1000000));
|
|
before = now;
|
|
}
|
|
}
|
|
|
|
list_T *const rv = tv_list_alloc(tv_list_len(args));
|
|
|
|
// For each job:
|
|
// * Restore its parent queue if the job is still alive.
|
|
// * Append its status to the output list, or:
|
|
// -3 for "invalid job id"
|
|
// -2 for "interrupted" (user hit CTRL-C)
|
|
// -1 for jobs that were skipped or timed out
|
|
for (i = 0; i < tv_list_len(args); i++) {
|
|
if (jobs[i] == NULL) {
|
|
tv_list_append_number(rv, -3);
|
|
continue;
|
|
}
|
|
multiqueue_process_events(jobs[i]->events);
|
|
multiqueue_replace_parent(jobs[i]->events, main_loop.events);
|
|
|
|
tv_list_append_number(rv, jobs[i]->stream.proc.status);
|
|
channel_decref(jobs[i]);
|
|
}
|
|
|
|
multiqueue_free(waiting_jobs);
|
|
xfree(jobs);
|
|
ui_busy_stop();
|
|
tv_list_ref(rv);
|
|
rettv->v_type = VAR_LIST;
|
|
rettv->vval.v_list = rv;
|
|
}
|
|
|
|
/// json_decode() function
|
|
static void f_json_decode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char numbuf[NUMBUFLEN];
|
|
const char *s = NULL;
|
|
char *tofree = NULL;
|
|
size_t len;
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) {
|
|
emsg(_("E474: Failed to convert list to string"));
|
|
return;
|
|
}
|
|
s = tofree;
|
|
if (s == NULL) {
|
|
assert(len == 0);
|
|
s = "";
|
|
}
|
|
} else {
|
|
s = tv_get_string_buf_chk(&argvars[0], numbuf);
|
|
if (s) {
|
|
len = strlen(s);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
if (json_decode_string(s, len, rettv) == FAIL) {
|
|
semsg(_("E474: Failed to parse %.*s"), (int)len, s);
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
assert(rettv->v_type != VAR_UNKNOWN);
|
|
xfree(tofree);
|
|
}
|
|
|
|
/// json_encode() function
|
|
static void f_json_encode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = encode_tv2json(&argvars[0], NULL);
|
|
}
|
|
|
|
/// "last_buffer_nr()" function.
|
|
static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int n = 0;
|
|
|
|
FOR_ALL_BUFFERS(buf) {
|
|
if (n < buf->b_fnum) {
|
|
n = buf->b_fnum;
|
|
}
|
|
}
|
|
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
/// "len()" function
|
|
static void f_len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
switch (argvars[0].v_type) {
|
|
case VAR_STRING:
|
|
case VAR_NUMBER:
|
|
rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
|
|
break;
|
|
case VAR_BLOB:
|
|
rettv->vval.v_number = tv_blob_len(argvars[0].vval.v_blob);
|
|
break;
|
|
case VAR_LIST:
|
|
rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
|
|
break;
|
|
case VAR_DICT:
|
|
rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict);
|
|
break;
|
|
case VAR_UNKNOWN:
|
|
case VAR_BOOL:
|
|
case VAR_SPECIAL:
|
|
case VAR_FLOAT:
|
|
case VAR_PARTIAL:
|
|
case VAR_FUNC:
|
|
emsg(_("E701: Invalid type for len()"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
|
|
{
|
|
rettv->v_type = (VarType)out_type;
|
|
if (out_type != VAR_NUMBER) {
|
|
rettv->vval.v_string = NULL;
|
|
}
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
// The first two args (libname and funcname) must be strings
|
|
if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
|
|
return;
|
|
}
|
|
|
|
const char *libname = argvars[0].vval.v_string;
|
|
const char *funcname = argvars[1].vval.v_string;
|
|
|
|
VarType in_type = argvars[2].v_type;
|
|
|
|
// input variables
|
|
char *str_in = (in_type == VAR_STRING) ? argvars[2].vval.v_string : NULL;
|
|
int int_in = (int)argvars[2].vval.v_number;
|
|
|
|
// output variables
|
|
char **str_out = (out_type == VAR_STRING) ? &rettv->vval.v_string : NULL;
|
|
int int_out = 0;
|
|
|
|
bool success = os_libcall(libname, funcname,
|
|
str_in, int_in,
|
|
str_out, &int_out);
|
|
|
|
if (!success) {
|
|
semsg(_(e_libcall), funcname);
|
|
return;
|
|
}
|
|
|
|
if (out_type == VAR_NUMBER) {
|
|
rettv->vval.v_number = (varnumber_T)int_out;
|
|
}
|
|
}
|
|
|
|
/// "libcall()" function
|
|
static void f_libcall(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
libcall_common(argvars, rettv, VAR_STRING);
|
|
}
|
|
|
|
/// "libcallnr()" function
|
|
static void f_libcallnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
libcall_common(argvars, rettv, VAR_NUMBER);
|
|
}
|
|
|
|
/// "line(string, [winid])" function
|
|
static void f_line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T lnum = 0;
|
|
pos_T *fp = NULL;
|
|
int fnum;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
// use window specified in the second argument
|
|
int id = (int)tv_get_number(&argvars[1]);
|
|
tabpage_T *tp;
|
|
win_T *wp = win_id2wp_tp(id, &tp);
|
|
if (wp != NULL && tp != NULL) {
|
|
switchwin_T switchwin;
|
|
if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
|
|
check_cursor();
|
|
fp = var2fpos(&argvars[0], true, &fnum, false);
|
|
}
|
|
restore_win_noblock(&switchwin, true);
|
|
}
|
|
} else {
|
|
// use current window
|
|
fp = var2fpos(&argvars[0], true, &fnum, false);
|
|
}
|
|
|
|
if (fp != NULL) {
|
|
lnum = fp->lnum;
|
|
}
|
|
rettv->vval.v_number = lnum;
|
|
}
|
|
|
|
/// "line2byte(lnum)" function
|
|
static void f_line2byte(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false);
|
|
}
|
|
if (rettv->vval.v_number >= 0) {
|
|
rettv->vval.v_number++;
|
|
}
|
|
}
|
|
|
|
/// "lispindent(lnum)" function
|
|
static void f_lispindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const pos_T pos = curwin->w_cursor;
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
|
|
curwin->w_cursor.lnum = lnum;
|
|
rettv->vval.v_number = get_lisp_indent();
|
|
curwin->w_cursor = pos;
|
|
} else {
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
}
|
|
|
|
/// "localtime()" function
|
|
static void f_localtime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = (varnumber_T)time(NULL);
|
|
}
|
|
|
|
/// luaeval() function implementation
|
|
static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
const char *const str = tv_get_string_chk(&argvars[0]);
|
|
if (str == NULL) {
|
|
return;
|
|
}
|
|
|
|
nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
|
|
}
|
|
|
|
/// "map()" function
|
|
static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
filter_map(argvars, rettv, true);
|
|
}
|
|
|
|
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
|
|
const SomeMatchType type)
|
|
{
|
|
char_u *str = NULL;
|
|
long len = 0;
|
|
char_u *expr = NULL;
|
|
regmatch_T regmatch;
|
|
long start = 0;
|
|
long nth = 1;
|
|
colnr_T startcol = 0;
|
|
bool match = false;
|
|
list_T *l = NULL;
|
|
long idx = 0;
|
|
char_u *tofree = NULL;
|
|
|
|
// Make 'cpoptions' empty, the 'l' flag should not be used here.
|
|
char *save_cpo = p_cpo;
|
|
p_cpo = empty_option;
|
|
|
|
rettv->vval.v_number = -1;
|
|
switch (type) {
|
|
// matchlist(): return empty list when there are no matches.
|
|
case kSomeMatchList:
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
break;
|
|
// matchstrpos(): return ["", -1, -1, -1]
|
|
case kSomeMatchStrPos:
|
|
tv_list_alloc_ret(rettv, 4);
|
|
tv_list_append_string(rettv->vval.v_list, "", 0);
|
|
tv_list_append_number(rettv->vval.v_list, -1);
|
|
tv_list_append_number(rettv->vval.v_list, -1);
|
|
tv_list_append_number(rettv->vval.v_list, -1);
|
|
break;
|
|
case kSomeMatchStr:
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
break;
|
|
case kSomeMatch:
|
|
case kSomeMatchEnd:
|
|
// Do nothing: zero is default.
|
|
break;
|
|
}
|
|
|
|
listitem_T *li = NULL;
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
if ((l = argvars[0].vval.v_list) == NULL) {
|
|
goto theend;
|
|
}
|
|
li = tv_list_first(l);
|
|
} else {
|
|
expr = str = (char_u *)tv_get_string(&argvars[0]);
|
|
len = (long)STRLEN(str);
|
|
}
|
|
|
|
char patbuf[NUMBUFLEN];
|
|
const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
|
|
if (pat == NULL) {
|
|
goto theend;
|
|
}
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
bool error = false;
|
|
|
|
start = tv_get_number_chk(&argvars[2], &error);
|
|
if (error) {
|
|
goto theend;
|
|
}
|
|
if (l != NULL) {
|
|
idx = tv_list_uidx(l, (int)start);
|
|
if (idx == -1) {
|
|
goto theend;
|
|
}
|
|
li = tv_list_find(l, (int)idx);
|
|
} else {
|
|
if (start < 0) {
|
|
start = 0;
|
|
}
|
|
if (start > len) {
|
|
goto theend;
|
|
}
|
|
// When "count" argument is there ignore matches before "start",
|
|
// otherwise skip part of the string. Differs when pattern is "^"
|
|
// or "\<".
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
startcol = (colnr_T)start;
|
|
} else {
|
|
str += start;
|
|
len -= start;
|
|
}
|
|
}
|
|
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
nth = tv_get_number_chk(&argvars[3], &error);
|
|
}
|
|
if (error) {
|
|
goto theend;
|
|
}
|
|
}
|
|
|
|
regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING);
|
|
if (regmatch.regprog != NULL) {
|
|
regmatch.rm_ic = p_ic;
|
|
|
|
for (;;) {
|
|
if (l != NULL) {
|
|
if (li == NULL) {
|
|
match = false;
|
|
break;
|
|
}
|
|
xfree(tofree);
|
|
tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li),
|
|
NULL);
|
|
if (str == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
match = vim_regexec_nl(®match, str, startcol);
|
|
|
|
if (match && --nth <= 0) {
|
|
break;
|
|
}
|
|
if (l == NULL && !match) {
|
|
break;
|
|
}
|
|
|
|
// Advance to just after the match.
|
|
if (l != NULL) {
|
|
li = TV_LIST_ITEM_NEXT(l, li);
|
|
idx++;
|
|
} else {
|
|
startcol = (colnr_T)(regmatch.startp[0]
|
|
+ utfc_ptr2len((char *)regmatch.startp[0]) - str);
|
|
if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
switch (type) {
|
|
case kSomeMatchStrPos: {
|
|
list_T *const ret_l = rettv->vval.v_list;
|
|
listitem_T *li1 = tv_list_first(ret_l);
|
|
listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
|
|
listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
|
|
listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
|
|
xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
|
|
|
|
const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
|
|
TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz((const char *)regmatch.startp[0], rd);
|
|
TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(regmatch.startp[0] - expr);
|
|
TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(regmatch.endp[0] - expr);
|
|
if (l != NULL) {
|
|
TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
|
|
}
|
|
break;
|
|
}
|
|
case kSomeMatchList:
|
|
// Return list with matched string and submatches.
|
|
for (int i = 0; i < NSUBEXP; i++) {
|
|
if (regmatch.endp[i] == NULL) {
|
|
tv_list_append_string(rettv->vval.v_list, NULL, 0);
|
|
} else {
|
|
tv_list_append_string(rettv->vval.v_list,
|
|
(const char *)regmatch.startp[i],
|
|
(regmatch.endp[i] - regmatch.startp[i]));
|
|
}
|
|
}
|
|
break;
|
|
case kSomeMatchStr:
|
|
// Return matched string.
|
|
if (l != NULL) {
|
|
tv_copy(TV_LIST_ITEM_TV(li), rettv);
|
|
} else {
|
|
rettv->vval.v_string = xmemdupz((const char *)regmatch.startp[0],
|
|
(size_t)(regmatch.endp[0] -
|
|
regmatch.startp[0]));
|
|
}
|
|
break;
|
|
case kSomeMatch:
|
|
case kSomeMatchEnd:
|
|
if (l != NULL) {
|
|
rettv->vval.v_number = idx;
|
|
} else {
|
|
if (type == kSomeMatch) {
|
|
rettv->vval.v_number =
|
|
(varnumber_T)(regmatch.startp[0] - str);
|
|
} else {
|
|
rettv->vval.v_number =
|
|
(varnumber_T)(regmatch.endp[0] - str);
|
|
}
|
|
rettv->vval.v_number += (varnumber_T)(str - expr);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
vim_regfree(regmatch.regprog);
|
|
}
|
|
|
|
theend:
|
|
if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) {
|
|
// matchstrpos() without a list: drop the second item
|
|
list_T *const ret_l = rettv->vval.v_list;
|
|
tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));
|
|
}
|
|
|
|
xfree(tofree);
|
|
p_cpo = save_cpo;
|
|
}
|
|
|
|
/// "match()" function
|
|
static void f_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatch);
|
|
}
|
|
|
|
/// "matchend()" function
|
|
static void f_matchend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchEnd);
|
|
}
|
|
|
|
/// "matchlist()" function
|
|
static void f_matchlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchList);
|
|
}
|
|
|
|
/// "matchstr()" function
|
|
static void f_matchstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchStr);
|
|
}
|
|
|
|
/// "matchstrpos()" function
|
|
static void f_matchstrpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchStrPos);
|
|
}
|
|
|
|
/// Get maximal/minimal number value in a list or dictionary
|
|
///
|
|
/// @param[in] tv List or dictionary to work with. If it contains something
|
|
/// that is not an integer number (or cannot be coerced to
|
|
/// it) error is given.
|
|
/// @param[out] rettv Location where result will be saved. Only assigns
|
|
/// vval.v_number, type is not touched. Returns zero for
|
|
/// empty lists/dictionaries.
|
|
/// @param[in] domax Determines whether maximal or minimal value is desired.
|
|
static void max_min(const typval_T *const tv, typval_T *const rettv, const bool domax)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
bool error = false;
|
|
|
|
rettv->vval.v_number = 0;
|
|
varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX);
|
|
if (tv->v_type == VAR_LIST) {
|
|
if (tv_list_len(tv->vval.v_list) == 0) {
|
|
return;
|
|
}
|
|
TV_LIST_ITER_CONST(tv->vval.v_list, li, {
|
|
const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
if (domax ? i > n : i < n) {
|
|
n = i;
|
|
}
|
|
});
|
|
} else if (tv->v_type == VAR_DICT) {
|
|
if (tv_dict_len(tv->vval.v_dict) == 0) {
|
|
return;
|
|
}
|
|
TV_DICT_ITER(tv->vval.v_dict, di, {
|
|
const varnumber_T i = tv_get_number_chk(&di->di_tv, &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
if (domax ? i > n : i < n) {
|
|
n = i;
|
|
}
|
|
});
|
|
} else {
|
|
semsg(_(e_listdictarg), domax ? "max()" : "min()");
|
|
return;
|
|
}
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
/// "max()" function
|
|
static void f_max(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
max_min(argvars, rettv, true);
|
|
}
|
|
|
|
/// "min()" function
|
|
static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
max_min(argvars, rettv, false);
|
|
}
|
|
|
|
/// "mkdir()" function
|
|
static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int prot = 0755; // -V536
|
|
|
|
rettv->vval.v_number = FAIL;
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
char buf[NUMBUFLEN];
|
|
const char *const dir = tv_get_string_buf(&argvars[0], buf);
|
|
if (*dir == NUL) {
|
|
return;
|
|
}
|
|
|
|
if (*path_tail(dir) == NUL) {
|
|
// Remove trailing slashes.
|
|
*path_tail_with_sep((char *)dir) = NUL;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
prot = (int)tv_get_number_chk(&argvars[2], NULL);
|
|
if (prot == -1) {
|
|
return;
|
|
}
|
|
}
|
|
if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
|
|
char *failed_dir;
|
|
int ret = os_mkdir_recurse(dir, prot, &failed_dir);
|
|
if (ret != 0) {
|
|
semsg(_(e_mkdir), failed_dir, os_strerror(ret));
|
|
xfree(failed_dir);
|
|
rettv->vval.v_number = FAIL;
|
|
return;
|
|
} else {
|
|
rettv->vval.v_number = OK;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
|
|
}
|
|
|
|
/// "mode()" function
|
|
static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char buf[MODE_MAX_LENGTH];
|
|
|
|
get_mode(buf);
|
|
|
|
// Clear out the minor mode when the argument is not a non-zero number or
|
|
// non-empty string.
|
|
if (!non_zero_arg(&argvars[0])) {
|
|
buf[1] = NUL;
|
|
}
|
|
|
|
rettv->vval.v_string = xstrdup(buf);
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "msgpackdump()" function
|
|
static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (argvars[0].v_type != VAR_LIST) {
|
|
semsg(_(e_listarg), "msgpackdump()");
|
|
return;
|
|
}
|
|
list_T *const list = argvars[0].vval.v_list;
|
|
msgpack_packer *packer;
|
|
if (argvars[1].v_type != VAR_UNKNOWN
|
|
&& strequal(tv_get_string(&argvars[1]), "B")) {
|
|
tv_blob_alloc_ret(rettv);
|
|
packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write);
|
|
} else {
|
|
packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow),
|
|
&encode_list_write);
|
|
}
|
|
const char *const msg = _("msgpackdump() argument, index %i");
|
|
// Assume that translation will not take more then 4 times more space
|
|
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
|
|
int idx = 0;
|
|
TV_LIST_ITER(list, li, {
|
|
vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
|
|
idx++;
|
|
if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
|
|
break;
|
|
}
|
|
});
|
|
msgpack_packer_free(packer);
|
|
}
|
|
|
|
static int msgpackparse_convert_item(const msgpack_object data, const msgpack_unpack_return result,
|
|
list_T *const ret_list, const bool fail_if_incomplete)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
switch (result) {
|
|
case MSGPACK_UNPACK_PARSE_ERROR:
|
|
semsg(_(e_invarg2), "Failed to parse msgpack string");
|
|
return FAIL;
|
|
case MSGPACK_UNPACK_NOMEM_ERROR:
|
|
emsg(_(e_outofmem));
|
|
return FAIL;
|
|
case MSGPACK_UNPACK_CONTINUE:
|
|
if (fail_if_incomplete) {
|
|
semsg(_(e_invarg2), "Incomplete msgpack string");
|
|
return FAIL;
|
|
}
|
|
return NOTDONE;
|
|
case MSGPACK_UNPACK_SUCCESS: {
|
|
typval_T tv = { .v_type = VAR_UNKNOWN };
|
|
if (msgpack_to_vim(data, &tv) == FAIL) {
|
|
semsg(_(e_invarg2), "Failed to convert msgpack string");
|
|
return FAIL;
|
|
}
|
|
tv_list_append_owned_tv(ret_list, tv);
|
|
return OK;
|
|
}
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list)
|
|
FUNC_ATTR_NONNULL_ARG(2)
|
|
{
|
|
if (tv_list_len(list) == 0) {
|
|
return;
|
|
}
|
|
if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) {
|
|
semsg(_(e_invarg2), "List item is not a string");
|
|
return;
|
|
}
|
|
ListReaderState lrstate = encode_init_lrstate(list);
|
|
msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
|
|
if (unpacker == NULL) {
|
|
emsg(_(e_outofmem));
|
|
return;
|
|
}
|
|
msgpack_unpacked unpacked;
|
|
msgpack_unpacked_init(&unpacked);
|
|
do {
|
|
if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
|
|
emsg(_(e_outofmem));
|
|
goto end;
|
|
}
|
|
size_t read_bytes;
|
|
const int rlret = encode_read_from_list(&lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE,
|
|
&read_bytes);
|
|
if (rlret == FAIL) {
|
|
semsg(_(e_invarg2), "List item is not a string");
|
|
goto end;
|
|
}
|
|
msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
|
|
if (read_bytes == 0) {
|
|
break;
|
|
}
|
|
while (unpacker->off < unpacker->used) {
|
|
const msgpack_unpack_return result
|
|
= msgpack_unpacker_next(unpacker, &unpacked);
|
|
const int conv_result = msgpackparse_convert_item(unpacked.data, result,
|
|
ret_list, rlret == OK);
|
|
if (conv_result == NOTDONE) {
|
|
break;
|
|
} else if (conv_result == FAIL) {
|
|
goto end;
|
|
}
|
|
}
|
|
if (rlret == OK) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
end:
|
|
msgpack_unpacker_free(unpacker);
|
|
msgpack_unpacked_destroy(&unpacked);
|
|
}
|
|
|
|
static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret_list)
|
|
FUNC_ATTR_NONNULL_ARG(2)
|
|
{
|
|
const int len = tv_blob_len(blob);
|
|
if (len == 0) {
|
|
return;
|
|
}
|
|
msgpack_unpacked unpacked;
|
|
msgpack_unpacked_init(&unpacked);
|
|
for (size_t offset = 0; offset < (size_t)len;) {
|
|
const msgpack_unpack_return result
|
|
= msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, (size_t)len, &offset);
|
|
if (msgpackparse_convert_item(unpacked.data, result, ret_list, true)
|
|
!= OK) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
msgpack_unpacked_destroy(&unpacked);
|
|
}
|
|
|
|
/// "msgpackparse" function
|
|
static void f_msgpackparse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
|
|
semsg(_(e_listblobarg), "msgpackparse()");
|
|
return;
|
|
}
|
|
list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
msgpackparse_unpack_list(argvars[0].vval.v_list, ret_list);
|
|
} else {
|
|
msgpackparse_unpack_blob(argvars[0].vval.v_blob, ret_list);
|
|
}
|
|
}
|
|
|
|
/// "nextnonblank()" function
|
|
static void f_nextnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T lnum;
|
|
|
|
for (lnum = tv_get_lnum(argvars);; lnum++) {
|
|
if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) {
|
|
lnum = 0;
|
|
break;
|
|
}
|
|
if (*skipwhite((char *)ml_get(lnum)) != NUL) {
|
|
break;
|
|
}
|
|
}
|
|
rettv->vval.v_number = lnum;
|
|
}
|
|
|
|
/// "nr2char()" function
|
|
static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
if (!tv_check_num(&argvars[1])) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool error = false;
|
|
const varnumber_T num = tv_get_number_chk(&argvars[0], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
if (num < 0) {
|
|
emsg(_("E5070: Character number must not be less than zero"));
|
|
return;
|
|
}
|
|
if (num > INT_MAX) {
|
|
semsg(_("E5071: Character number must not be greater than INT_MAX (%i)"),
|
|
INT_MAX);
|
|
return;
|
|
}
|
|
|
|
char buf[MB_MAXBYTES];
|
|
const int len = utf_char2bytes((int)num, buf);
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xmemdupz(buf, (size_t)len);
|
|
}
|
|
|
|
/// "or(expr, expr)" function
|
|
static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
|
|
| tv_get_number_chk(&argvars[1], NULL);
|
|
}
|
|
|
|
/// "pathshorten()" function
|
|
static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int trim_len = 1;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
trim_len = (int)tv_get_number(&argvars[1]);
|
|
if (trim_len < 1) {
|
|
trim_len = 1;
|
|
}
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]);
|
|
if (p == NULL) {
|
|
rettv->vval.v_string = NULL;
|
|
} else {
|
|
rettv->vval.v_string = (char *)vim_strsave(p);
|
|
shorten_dir_len((char_u *)rettv->vval.v_string, trim_len);
|
|
}
|
|
}
|
|
|
|
/// "pow()" function
|
|
static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
float_T fx;
|
|
float_T fy;
|
|
|
|
rettv->v_type = VAR_FLOAT;
|
|
if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
|
|
rettv->vval.v_float = pow(fx, fy);
|
|
} else {
|
|
rettv->vval.v_float = 0.0;
|
|
}
|
|
}
|
|
|
|
/// "prevnonblank()" function
|
|
static void f_prevnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T lnum = tv_get_lnum(argvars);
|
|
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
|
|
lnum = 0;
|
|
} else {
|
|
while (lnum >= 1 && *skipwhite((char *)ml_get(lnum)) == NUL) {
|
|
lnum--;
|
|
}
|
|
}
|
|
rettv->vval.v_number = lnum;
|
|
}
|
|
|
|
/// "printf()" function
|
|
static void f_printf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
{
|
|
int saved_did_emsg = did_emsg;
|
|
|
|
// Get the required length, allocate the buffer and do it for real.
|
|
did_emsg = false;
|
|
char buf[NUMBUFLEN];
|
|
const char *fmt = tv_get_string_buf(&argvars[0], buf);
|
|
int len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
|
|
if (!did_emsg) {
|
|
char *s = xmalloc((size_t)len + 1);
|
|
rettv->vval.v_string = s;
|
|
(void)vim_vsnprintf_typval(s, (size_t)len + 1, fmt, dummy_ap, argvars + 1);
|
|
}
|
|
did_emsg |= saved_did_emsg;
|
|
}
|
|
}
|
|
|
|
/// "prompt_setcallback({buffer}, {callback})" function
|
|
static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
Callback prompt_callback = { .type = kCallbackNone };
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
buf_T *buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
|
|
if (!callback_from_typval(&prompt_callback, &argvars[1])) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
callback_free(&buf->b_prompt_callback);
|
|
buf->b_prompt_callback = prompt_callback;
|
|
}
|
|
|
|
/// "prompt_setinterrupt({buffer}, {callback})" function
|
|
static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
Callback interrupt_callback = { .type = kCallbackNone };
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
buf_T *buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
|
|
if (!callback_from_typval(&interrupt_callback, &argvars[1])) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
callback_free(&buf->b_prompt_interrupt);
|
|
buf->b_prompt_interrupt= interrupt_callback;
|
|
}
|
|
|
|
/// "prompt_getprompt({buffer})" function
|
|
static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
// return an empty string by default, e.g. it's not a prompt buffer
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
buf_T *const buf = tv_get_buf_from_arg(&argvars[0]);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (!bt_prompt(buf)) {
|
|
return;
|
|
}
|
|
|
|
rettv->vval.v_string = (char *)vim_strsave(buf_prompt_text(buf));
|
|
}
|
|
|
|
/// "prompt_setprompt({buffer}, {text})" function
|
|
static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
buf_T *buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
return;
|
|
}
|
|
|
|
const char *text = tv_get_string(&argvars[1]);
|
|
xfree(buf->b_prompt_text);
|
|
buf->b_prompt_text = xstrdup(text);
|
|
}
|
|
|
|
/// "pum_getpos()" function
|
|
static void f_pum_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
pum_set_event_info(rettv->vval.v_dict);
|
|
}
|
|
|
|
/// "pumvisible()" function
|
|
static void f_pumvisible(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (pum_visible()) {
|
|
rettv->vval.v_number = 1;
|
|
}
|
|
}
|
|
|
|
/// "py3eval()" and "pyxeval()" functions (always python3)
|
|
static void f_py3eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
script_host_eval("python3", argvars, rettv);
|
|
}
|
|
|
|
static void init_srand(uint32_t *const x)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
#ifndef MSWIN
|
|
static int dev_urandom_state = NOTDONE; // FAIL or OK once tried
|
|
|
|
if (dev_urandom_state != FAIL) {
|
|
const int fd = os_open("/dev/urandom", O_RDONLY, 0);
|
|
struct {
|
|
union {
|
|
uint32_t number;
|
|
char bytes[sizeof(uint32_t)];
|
|
} contents;
|
|
} buf;
|
|
|
|
// Attempt reading /dev/urandom.
|
|
if (fd == -1) {
|
|
dev_urandom_state = FAIL;
|
|
} else {
|
|
buf.contents.number = 0;
|
|
if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) {
|
|
dev_urandom_state = FAIL;
|
|
} else {
|
|
dev_urandom_state = OK;
|
|
*x = buf.contents.number;
|
|
}
|
|
os_close(fd);
|
|
}
|
|
}
|
|
if (dev_urandom_state != OK) {
|
|
// Reading /dev/urandom doesn't work, fall back to time().
|
|
#endif
|
|
// uncrustify:off
|
|
*x = (uint32_t)time(NULL);
|
|
#ifndef MSWIN
|
|
}
|
|
#endif
|
|
// uncrustify:on
|
|
}
|
|
|
|
static inline uint32_t splitmix32(uint32_t *const x)
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
|
|
{
|
|
uint32_t z = (*x += 0x9e3779b9);
|
|
z = (z ^ (z >> 16)) * 0x85ebca6b;
|
|
z = (z ^ (z >> 13)) * 0xc2b2ae35;
|
|
return z ^ (z >> 16);
|
|
}
|
|
|
|
static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *const y,
|
|
uint32_t *const z, uint32_t *const w)
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
|
|
{
|
|
#define ROTL(x, k) (((x) << (k)) | ((x) >> (32 - (k))))
|
|
const uint32_t result = ROTL(*y * 5, 7) * 9;
|
|
const uint32_t t = *y << 9;
|
|
*z ^= *x;
|
|
*w ^= *y;
|
|
*y ^= *z;
|
|
*x ^= *w;
|
|
*z ^= t;
|
|
*w = ROTL(*w, 11);
|
|
#undef ROTL
|
|
return result;
|
|
}
|
|
|
|
/// "rand()" function
|
|
static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
uint32_t result;
|
|
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
static uint32_t gx, gy, gz, gw;
|
|
static bool initialized = false;
|
|
|
|
// When no argument is given use the global seed list.
|
|
if (!initialized) {
|
|
// Initialize the global seed list.
|
|
uint32_t x = 0;
|
|
init_srand(&x);
|
|
|
|
gx = splitmix32(&x);
|
|
gy = splitmix32(&x);
|
|
gz = splitmix32(&x);
|
|
gw = splitmix32(&x);
|
|
initialized = true;
|
|
}
|
|
|
|
result = shuffle_xoshiro128starstar(&gx, &gy, &gz, &gw);
|
|
} else if (argvars[0].v_type == VAR_LIST) {
|
|
list_T *const l = argvars[0].vval.v_list;
|
|
if (tv_list_len(l) != 4) {
|
|
goto theend;
|
|
}
|
|
|
|
typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L));
|
|
typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L));
|
|
typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L));
|
|
typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L));
|
|
if (tvx->v_type != VAR_NUMBER) {
|
|
goto theend;
|
|
}
|
|
if (tvy->v_type != VAR_NUMBER) {
|
|
goto theend;
|
|
}
|
|
if (tvz->v_type != VAR_NUMBER) {
|
|
goto theend;
|
|
}
|
|
if (tvw->v_type != VAR_NUMBER) {
|
|
goto theend;
|
|
}
|
|
uint32_t x = (uint32_t)tvx->vval.v_number;
|
|
uint32_t y = (uint32_t)tvy->vval.v_number;
|
|
uint32_t z = (uint32_t)tvz->vval.v_number;
|
|
uint32_t w = (uint32_t)tvw->vval.v_number;
|
|
|
|
result = shuffle_xoshiro128starstar(&x, &y, &z, &w);
|
|
|
|
tvx->vval.v_number = (varnumber_T)x;
|
|
tvy->vval.v_number = (varnumber_T)y;
|
|
tvz->vval.v_number = (varnumber_T)z;
|
|
tvw->vval.v_number = (varnumber_T)w;
|
|
} else {
|
|
goto theend;
|
|
}
|
|
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = (varnumber_T)result;
|
|
return;
|
|
|
|
theend:
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[0]));
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
|
|
/// "srand()" function
|
|
static void f_srand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
uint32_t x = 0;
|
|
|
|
tv_list_alloc_ret(rettv, 4);
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
init_srand(&x);
|
|
} else {
|
|
bool error = false;
|
|
x = (uint32_t)tv_get_number_chk(&argvars[0], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x));
|
|
}
|
|
|
|
/// "perleval()" function
|
|
static void f_perleval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
script_host_eval("perl", argvars, rettv);
|
|
}
|
|
|
|
/// "rubyeval()" function
|
|
static void f_rubyeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
script_host_eval("ruby", argvars, rettv);
|
|
}
|
|
|
|
/// "range()" function
|
|
static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
varnumber_T end;
|
|
varnumber_T stride = 1;
|
|
bool error = false;
|
|
|
|
varnumber_T start = tv_get_number_chk(&argvars[0], &error);
|
|
if (argvars[1].v_type == VAR_UNKNOWN) {
|
|
end = start - 1;
|
|
start = 0;
|
|
} else {
|
|
end = tv_get_number_chk(&argvars[1], &error);
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
stride = tv_get_number_chk(&argvars[2], &error);
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
if (stride == 0) {
|
|
emsg(_("E726: Stride is zero"));
|
|
} else if (stride > 0 ? end + 1 < start : end - 1 > start) {
|
|
emsg(_("E727: Start past end"));
|
|
} else {
|
|
tv_list_alloc_ret(rettv, (end - start) / stride);
|
|
for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) {
|
|
tv_list_append_number(rettv->vval.v_list, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Evaluate "expr" (= "context") for readdir().
|
|
static varnumber_T readdir_checkitem(void *context, const char *name)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
typval_T *expr = (typval_T *)context;
|
|
typval_T argv[2];
|
|
varnumber_T retval = 0;
|
|
bool error = false;
|
|
|
|
if (expr->v_type == VAR_UNKNOWN) {
|
|
return 1;
|
|
}
|
|
|
|
typval_T save_val;
|
|
prepare_vimvar(VV_VAL, &save_val);
|
|
set_vim_var_string(VV_VAL, name, -1);
|
|
argv[0].v_type = VAR_STRING;
|
|
argv[0].vval.v_string = (char *)name;
|
|
|
|
typval_T rettv;
|
|
if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
|
|
goto theend;
|
|
}
|
|
|
|
retval = tv_get_number_chk(&rettv, &error);
|
|
if (error) {
|
|
retval = -1;
|
|
}
|
|
|
|
tv_clear(&rettv);
|
|
|
|
theend:
|
|
set_vim_var_string(VV_VAL, NULL, 0);
|
|
restore_vimvar(VV_VAL, &save_val);
|
|
return retval;
|
|
}
|
|
|
|
/// "readdir()" function
|
|
static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenUnknown);
|
|
|
|
const char *path = tv_get_string(&argvars[0]);
|
|
typval_T *expr = &argvars[1];
|
|
garray_T ga;
|
|
int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem);
|
|
if (ret == OK && ga.ga_len > 0) {
|
|
for (int i = 0; i < ga.ga_len; i++) {
|
|
const char *p = ((const char **)ga.ga_data)[i];
|
|
tv_list_append_string(rettv->vval.v_list, p, -1);
|
|
}
|
|
}
|
|
ga_clear_strings(&ga);
|
|
}
|
|
|
|
/// "readfile()" function
|
|
static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
bool binary = false;
|
|
bool blob = false;
|
|
FILE *fd;
|
|
char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
|
|
int io_size = sizeof(buf);
|
|
char_u *prev = NULL; // previously read bytes, if any
|
|
long prevlen = 0; // length of data in prev
|
|
long prevsize = 0; // size of prev buffer
|
|
long maxline = MAXLNUM;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
|
|
binary = true;
|
|
} else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
|
|
blob = true;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
maxline = tv_get_number(&argvars[2]);
|
|
}
|
|
}
|
|
|
|
// Always open the file in binary mode, library functions have a mind of
|
|
// their own about CR-LF conversion.
|
|
const char *const fname = tv_get_string(&argvars[0]);
|
|
|
|
if (os_isdir((const char_u *)fname)) {
|
|
semsg(_(e_isadir2), fname);
|
|
return;
|
|
}
|
|
if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
|
|
semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
|
|
return;
|
|
}
|
|
|
|
if (blob) {
|
|
tv_blob_alloc_ret(rettv);
|
|
if (!read_blob(fd, rettv->vval.v_blob)) {
|
|
semsg(_(e_notread), fname);
|
|
// An empty blob is returned on error.
|
|
tv_blob_free(rettv->vval.v_blob);
|
|
rettv->vval.v_blob = NULL;
|
|
}
|
|
fclose(fd);
|
|
return;
|
|
}
|
|
|
|
list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
|
|
|
|
while (maxline < 0 || tv_list_len(l) < maxline) {
|
|
int readlen = (int)fread(buf, 1, (size_t)io_size, fd);
|
|
|
|
// This for loop processes what was read, but is also entered at end
|
|
// of file so that either:
|
|
// - an incomplete line gets written
|
|
// - a "binary" file gets an empty line at the end if it ends in a
|
|
// newline.
|
|
char_u *p; // Position in buf.
|
|
char_u *start; // Start of current line.
|
|
for (p = buf, start = buf;
|
|
p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
|
|
p++) {
|
|
if (*p == '\n' || readlen <= 0) {
|
|
char_u *s = NULL;
|
|
size_t len = (size_t)(p - start);
|
|
|
|
// Finished a line. Remove CRs before NL.
|
|
if (readlen > 0 && !binary) {
|
|
while (len > 0 && start[len - 1] == '\r') {
|
|
len--;
|
|
}
|
|
// removal may cross back to the "prev" string
|
|
if (len == 0) {
|
|
while (prevlen > 0 && prev[prevlen - 1] == '\r') {
|
|
prevlen--;
|
|
}
|
|
}
|
|
}
|
|
if (prevlen == 0) {
|
|
assert(len < INT_MAX);
|
|
s = vim_strnsave(start, len);
|
|
} else {
|
|
// Change "prev" buffer to be the right size. This way
|
|
// the bytes are only copied once, and very long lines are
|
|
// allocated only once.
|
|
s = xrealloc(prev, (size_t)prevlen + len + 1);
|
|
memcpy(s + prevlen, start, len);
|
|
s[(size_t)prevlen + len] = NUL;
|
|
prev = NULL; // the list will own the string
|
|
prevlen = prevsize = 0;
|
|
}
|
|
|
|
tv_list_append_owned_tv(l, (typval_T) {
|
|
.v_type = VAR_STRING,
|
|
.v_lock = VAR_UNLOCKED,
|
|
.vval.v_string = (char *)s,
|
|
});
|
|
|
|
start = p + 1; // Step over newline.
|
|
if (maxline < 0) {
|
|
if (tv_list_len(l) > -maxline) {
|
|
assert(tv_list_len(l) == 1 + (-maxline));
|
|
tv_list_item_remove(l, tv_list_first(l));
|
|
}
|
|
} else if (tv_list_len(l) >= maxline) {
|
|
assert(tv_list_len(l) == maxline);
|
|
break;
|
|
}
|
|
if (readlen <= 0) {
|
|
break;
|
|
}
|
|
} else if (*p == NUL) {
|
|
*p = '\n';
|
|
// Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
|
|
// when finding the BF and check the previous two bytes.
|
|
} else if (*p == 0xbf && !binary) {
|
|
// Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
|
|
// these may be in the "prev" string.
|
|
char_u back1 = p >= buf + 1 ? p[-1]
|
|
: prevlen >= 1 ? prev[prevlen - 1] : NUL;
|
|
char_u back2 = p >= buf + 2 ? p[-2]
|
|
: p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
|
|
: prevlen >=
|
|
2 ? prev[prevlen - 2] : NUL;
|
|
|
|
if (back2 == 0xef && back1 == 0xbb) {
|
|
char_u *dest = p - 2;
|
|
|
|
// Usually a BOM is at the beginning of a file, and so at
|
|
// the beginning of a line; then we can just step over it.
|
|
if (start == dest) {
|
|
start = p + 1;
|
|
} else {
|
|
// have to shuffle buf to close gap
|
|
int adjust_prevlen = 0;
|
|
|
|
if (dest < buf) { // -V782
|
|
adjust_prevlen = (int)(buf - dest); // -V782
|
|
// adjust_prevlen must be 1 or 2.
|
|
dest = buf;
|
|
}
|
|
if (readlen > p - buf + 1) {
|
|
memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1);
|
|
}
|
|
readlen -= 3 - adjust_prevlen;
|
|
prevlen -= adjust_prevlen;
|
|
p = dest - 1;
|
|
}
|
|
}
|
|
}
|
|
} // for
|
|
|
|
if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
|
|
break;
|
|
}
|
|
if (start < p) {
|
|
// There's part of a line in buf, store it in "prev".
|
|
if (p - start + prevlen >= prevsize) {
|
|
// A common use case is ordinary text files and "prev" gets a
|
|
// fragment of a line, so the first allocation is made
|
|
// small, to avoid repeatedly 'allocing' large and
|
|
// 'reallocing' small.
|
|
if (prevsize == 0) {
|
|
prevsize = (long)(p - start);
|
|
} else {
|
|
long grow50pc = (prevsize * 3) / 2;
|
|
long growmin = (long)((p - start) * 2 + prevlen);
|
|
prevsize = grow50pc > growmin ? grow50pc : growmin;
|
|
}
|
|
prev = xrealloc(prev, (size_t)prevsize);
|
|
}
|
|
// Add the line part to end of "prev".
|
|
memmove(prev + prevlen, start, (size_t)(p - start));
|
|
prevlen += (long)(p - start);
|
|
}
|
|
} // while
|
|
|
|
xfree(prev);
|
|
fclose(fd);
|
|
}
|
|
|
|
/// "getreginfo()" function
|
|
static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int regname = getreg_get_regname(argvars);
|
|
if (regname == 0) {
|
|
return;
|
|
}
|
|
|
|
if (regname == '@') {
|
|
regname = '"';
|
|
}
|
|
|
|
tv_dict_alloc_ret(rettv);
|
|
dict_T *const dict = rettv->vval.v_dict;
|
|
|
|
list_T *const list = get_reg_contents(regname, kGRegExprSrc | kGRegList);
|
|
if (list == NULL) {
|
|
return;
|
|
}
|
|
(void)tv_dict_add_list(dict, S_LEN("regcontents"), list);
|
|
|
|
char buf[NUMBUFLEN + 2];
|
|
buf[0] = NUL;
|
|
buf[1] = NUL;
|
|
colnr_T reglen = 0;
|
|
switch (get_reg_type(regname, ®len)) {
|
|
case kMTLineWise:
|
|
buf[0] = 'V';
|
|
break;
|
|
case kMTCharWise:
|
|
buf[0] = 'v';
|
|
break;
|
|
case kMTBlockWise:
|
|
vim_snprintf(buf, sizeof(buf), "%c%d", Ctrl_V, reglen + 1);
|
|
break;
|
|
case kMTUnknown:
|
|
abort();
|
|
}
|
|
(void)tv_dict_add_str(dict, S_LEN("regtype"), buf);
|
|
|
|
buf[0] = (char)get_register_name(get_unname_register());
|
|
buf[1] = NUL;
|
|
if (regname == '"') {
|
|
(void)tv_dict_add_str(dict, S_LEN("points_to"), buf);
|
|
} else {
|
|
(void)tv_dict_add_bool(dict, S_LEN("isunnamed"),
|
|
regname == buf[0] ? kBoolVarTrue : kBoolVarFalse);
|
|
}
|
|
}
|
|
|
|
/// "reg_executing()" function
|
|
static void f_reg_executing(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
return_register(reg_executing, rettv);
|
|
}
|
|
|
|
/// "reg_recording()" function
|
|
static void f_reg_recording(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
return_register(reg_recording, rettv);
|
|
}
|
|
|
|
static void f_reg_recorded(typval_T *argvars, typval_T *rettv, EvalFuncData 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
|
|
/// exactly 2 items
|
|
/// @param[out] tm The proftime_T representation of `arg`
|
|
/// @return OK In case of success, FAIL in case of error
|
|
static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) {
|
|
return FAIL;
|
|
}
|
|
|
|
bool error = false;
|
|
varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
|
|
varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
|
|
if (error) {
|
|
return FAIL;
|
|
}
|
|
|
|
// in f_reltime() we split up the 64-bit proftime_T into two 32-bit
|
|
// values, now we combine them again.
|
|
union {
|
|
struct { int32_t low, high; } split;
|
|
proftime_T prof;
|
|
} u = { .split.high = (int32_t)n1, .split.low = (int32_t)n2 };
|
|
|
|
*tm = u.prof;
|
|
|
|
return OK;
|
|
}
|
|
|
|
/// f_reltime - return an item that represents a time value
|
|
///
|
|
/// @param[out] rettv Without an argument it returns the current time. With
|
|
/// one argument it returns the time passed since the argument.
|
|
/// With two arguments it returns the time passed between
|
|
/// the two arguments.
|
|
static void f_reltime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
proftime_T res;
|
|
proftime_T start;
|
|
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
// no arguments: get current time.
|
|
res = profile_start();
|
|
} else if (argvars[1].v_type == VAR_UNKNOWN) {
|
|
if (list2proftime(&argvars[0], &res) == FAIL) {
|
|
return;
|
|
}
|
|
res = profile_end(res);
|
|
} else {
|
|
// two arguments: compute the difference.
|
|
if (list2proftime(&argvars[0], &start) == FAIL
|
|
|| list2proftime(&argvars[1], &res) == FAIL) {
|
|
return;
|
|
}
|
|
res = profile_sub(res, start);
|
|
}
|
|
|
|
// we have to store the 64-bit proftime_T inside of a list of int's
|
|
// (varnumber_T is defined as int). For all our supported platforms, int's
|
|
// are at least 32-bits wide. So we'll use two 32-bit values to store it.
|
|
union {
|
|
struct { int32_t low, high; } split;
|
|
proftime_T prof;
|
|
} u = { .prof = res };
|
|
|
|
// statically assert that the union type conv will provide the correct
|
|
// results, if varnumber_T or proftime_T change, the union cast will need
|
|
// to be revised.
|
|
STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u),
|
|
"type punning will produce incorrect results on this platform");
|
|
|
|
tv_list_alloc_ret(rettv, 2);
|
|
tv_list_append_number(rettv->vval.v_list, u.split.high);
|
|
tv_list_append_number(rettv->vval.v_list, u.split.low);
|
|
}
|
|
|
|
/// "reltimestr()" function
|
|
static void f_reltimestr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
proftime_T tm;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (list2proftime(&argvars[0], &tm) == OK) {
|
|
rettv->vval.v_string = xstrdup(profile_msg(tm));
|
|
}
|
|
}
|
|
|
|
/// "remove()" function
|
|
static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const arg_errmsg = N_("remove() argument");
|
|
|
|
if (argvars[0].v_type == VAR_DICT) {
|
|
tv_dict_remove(argvars, rettv, arg_errmsg);
|
|
} else if (argvars[0].v_type == VAR_BLOB) {
|
|
tv_blob_remove(argvars, rettv, arg_errmsg);
|
|
} else if (argvars[0].v_type == VAR_LIST) {
|
|
tv_list_remove(argvars, rettv, arg_errmsg);
|
|
} else {
|
|
semsg(_(e_listdictblobarg), "remove()");
|
|
}
|
|
}
|
|
|
|
/// "rename({from}, {to})" function
|
|
static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
char buf[NUMBUFLEN];
|
|
rettv->vval.v_number = vim_rename((const char_u *)tv_get_string(&argvars[0]),
|
|
(const char_u *)tv_get_string_buf(&argvars[1], buf));
|
|
}
|
|
}
|
|
|
|
/// "repeat()" function
|
|
static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
varnumber_T n = tv_get_number(&argvars[1]);
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list));
|
|
while (n-- > 0) {
|
|
tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
|
|
}
|
|
} else {
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (n <= 0) {
|
|
return;
|
|
}
|
|
|
|
const char *const p = tv_get_string(&argvars[0]);
|
|
|
|
const size_t slen = strlen(p);
|
|
if (slen == 0) {
|
|
return;
|
|
}
|
|
const size_t len = slen * (size_t)n;
|
|
// Detect overflow.
|
|
if (len / (size_t)n != slen) {
|
|
return;
|
|
}
|
|
|
|
char *const r = xmallocz(len);
|
|
for (varnumber_T i = 0; i < n; i++) {
|
|
memmove(r + (size_t)i * slen, p, slen);
|
|
}
|
|
|
|
rettv->vval.v_string = r;
|
|
}
|
|
}
|
|
|
|
/// "resolve()" function
|
|
static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
const char *fname = tv_get_string(&argvars[0]);
|
|
#ifdef WIN32
|
|
char *v = os_resolve_shortcut(fname);
|
|
if (v == NULL) {
|
|
if (os_is_reparse_point_include(fname)) {
|
|
v = os_realpath(fname, v);
|
|
}
|
|
}
|
|
rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v);
|
|
#else
|
|
# ifdef HAVE_READLINK
|
|
{
|
|
bool is_relative_to_current = false;
|
|
bool has_trailing_pathsep = false;
|
|
int limit = 100;
|
|
|
|
char *p = xstrdup(fname);
|
|
|
|
if (p[0] == '.' && (vim_ispathsep(p[1])
|
|
|| (p[1] == '.' && (vim_ispathsep(p[2]))))) {
|
|
is_relative_to_current = true;
|
|
}
|
|
|
|
ptrdiff_t len = (ptrdiff_t)strlen(p);
|
|
if (len > 1 && after_pathsep(p, p + len)) {
|
|
has_trailing_pathsep = true;
|
|
p[len - 1] = NUL; // The trailing slash breaks readlink().
|
|
}
|
|
|
|
char *q = (char *)path_next_component(p);
|
|
char *remain = NULL;
|
|
if (*q != NUL) {
|
|
// Separate the first path component in "p", and keep the
|
|
// remainder (beginning with the path separator).
|
|
remain = xstrdup(q - 1);
|
|
q[-1] = NUL;
|
|
}
|
|
|
|
char *const buf = xmallocz(MAXPATHL);
|
|
|
|
char *cpy;
|
|
for (;;) {
|
|
for (;;) {
|
|
len = readlink(p, buf, MAXPATHL);
|
|
if (len <= 0) {
|
|
break;
|
|
}
|
|
buf[len] = NUL;
|
|
|
|
if (limit-- == 0) {
|
|
xfree(p);
|
|
xfree(remain);
|
|
emsg(_("E655: Too many symbolic links (cycle?)"));
|
|
rettv->vval.v_string = NULL;
|
|
xfree(buf);
|
|
return;
|
|
}
|
|
|
|
// Ensure that the result will have a trailing path separator
|
|
// if the argument has one. */
|
|
if (remain == NULL && has_trailing_pathsep) {
|
|
add_pathsep(buf);
|
|
}
|
|
|
|
// Separate the first path component in the link value and
|
|
// concatenate the remainders. */
|
|
q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
|
|
if (*q != NUL) {
|
|
cpy = remain;
|
|
remain = (remain
|
|
? (char *)concat_str((char_u *)q - 1, (char_u *)remain)
|
|
: xstrdup(q - 1));
|
|
xfree(cpy);
|
|
q[-1] = NUL;
|
|
}
|
|
|
|
q = path_tail(p);
|
|
if (q > p && *q == NUL) {
|
|
// Ignore trailing path separator.
|
|
q[-1] = NUL;
|
|
q = path_tail(p);
|
|
}
|
|
if (q > p && !path_is_absolute((const char_u *)buf)) {
|
|
// Symlink is relative to directory of argument. Replace the
|
|
// symlink with the resolved name in the same directory.
|
|
const size_t p_len = strlen(p);
|
|
const size_t buf_len = strlen(buf);
|
|
p = xrealloc(p, p_len + buf_len + 1);
|
|
memcpy(path_tail(p), buf, buf_len + 1);
|
|
} else {
|
|
xfree(p);
|
|
p = xstrdup(buf);
|
|
}
|
|
}
|
|
|
|
if (remain == NULL) {
|
|
break;
|
|
}
|
|
|
|
// Append the first path component of "remain" to "p".
|
|
q = (char *)path_next_component(remain + 1);
|
|
len = q - remain - (*q != NUL);
|
|
const size_t p_len = strlen(p);
|
|
cpy = xmallocz(p_len + (size_t)len);
|
|
memcpy(cpy, p, p_len + 1);
|
|
xstrlcat(cpy + p_len, remain, (size_t)len + 1);
|
|
xfree(p);
|
|
p = cpy;
|
|
|
|
// Shorten "remain".
|
|
if (*q != NUL) {
|
|
STRMOVE(remain, q - 1);
|
|
} else {
|
|
XFREE_CLEAR(remain);
|
|
}
|
|
}
|
|
|
|
// If the result is a relative path name, make it explicitly relative to
|
|
// the current directory if and only if the argument had this form.
|
|
if (!vim_ispathsep(*p)) {
|
|
if (is_relative_to_current
|
|
&& *p != NUL
|
|
&& !(p[0] == '.'
|
|
&& (p[1] == NUL
|
|
|| vim_ispathsep(p[1])
|
|
|| (p[1] == '.'
|
|
&& (p[2] == NUL
|
|
|| vim_ispathsep(p[2])))))) {
|
|
// Prepend "./".
|
|
cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p);
|
|
xfree(p);
|
|
p = cpy;
|
|
} else if (!is_relative_to_current) {
|
|
// Strip leading "./".
|
|
q = p;
|
|
while (q[0] == '.' && vim_ispathsep(q[1])) {
|
|
q += 2;
|
|
}
|
|
if (q > p) {
|
|
STRMOVE(p, p + 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure that the result will have no trailing path separator
|
|
// if the argument had none. But keep "/" or "//".
|
|
if (!has_trailing_pathsep) {
|
|
q = p + strlen(p);
|
|
if (after_pathsep(p, q)) {
|
|
*path_tail_with_sep(p) = NUL;
|
|
}
|
|
}
|
|
|
|
rettv->vval.v_string = p;
|
|
xfree(buf);
|
|
}
|
|
# else
|
|
char *v = os_realpath(fname, NULL);
|
|
rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
|
|
# endif
|
|
#endif
|
|
|
|
simplify_filename((char_u *)rettv->vval.v_string);
|
|
}
|
|
|
|
/// "reverse({list})" function
|
|
static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type == VAR_BLOB) {
|
|
blob_T *const b = argvars[0].vval.v_blob;
|
|
const int len = tv_blob_len(b);
|
|
|
|
for (int i = 0; i < len / 2; i++) {
|
|
const char_u tmp = tv_blob_get(b, i);
|
|
tv_blob_set(b, i, tv_blob_get(b, len - i - 1));
|
|
tv_blob_set(b, len - i - 1, tmp);
|
|
}
|
|
tv_blob_set_ret(rettv, b);
|
|
} else if (argvars[0].v_type != VAR_LIST) {
|
|
semsg(_(e_listblobarg), "reverse()");
|
|
} else {
|
|
list_T *const l = argvars[0].vval.v_list;
|
|
if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"),
|
|
TV_TRANSLATE)) {
|
|
tv_list_reverse(l);
|
|
tv_list_set_ret(rettv, l);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "reduce(list, { accumulator, element -> value } [, initial])" function
|
|
static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
|
|
emsg(_(e_listblobreq));
|
|
return;
|
|
}
|
|
|
|
const char *func_name;
|
|
partial_T *partial = NULL;
|
|
if (argvars[1].v_type == VAR_FUNC) {
|
|
func_name = argvars[1].vval.v_string;
|
|
} else if (argvars[1].v_type == VAR_PARTIAL) {
|
|
partial = argvars[1].vval.v_partial;
|
|
func_name = partial_name(partial);
|
|
} else {
|
|
func_name = tv_get_string(&argvars[1]);
|
|
}
|
|
if (*func_name == NUL) {
|
|
return; // type error or empty name
|
|
}
|
|
|
|
funcexe_T funcexe = FUNCEXE_INIT;
|
|
funcexe.evaluate = true;
|
|
funcexe.partial = partial;
|
|
|
|
typval_T initial;
|
|
typval_T argv[3];
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
list_T *const l = argvars[0].vval.v_list;
|
|
const listitem_T *li;
|
|
|
|
if (argvars[2].v_type == VAR_UNKNOWN) {
|
|
if (tv_list_len(l) == 0) {
|
|
semsg(_(e_reduceempty), "List");
|
|
return;
|
|
}
|
|
const listitem_T *const first = tv_list_first(l);
|
|
initial = *TV_LIST_ITEM_TV(first);
|
|
li = TV_LIST_ITEM_NEXT(l, first);
|
|
} else {
|
|
initial = argvars[2];
|
|
li = tv_list_first(l);
|
|
}
|
|
|
|
tv_copy(&initial, rettv);
|
|
|
|
if (l != NULL) {
|
|
const VarLockStatus prev_locked = tv_list_locked(l);
|
|
const int called_emsg_start = called_emsg;
|
|
|
|
tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
|
|
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
|
|
argv[0] = *rettv;
|
|
argv[1] = *TV_LIST_ITEM_TV(li);
|
|
rettv->v_type = VAR_UNKNOWN;
|
|
const int r = call_func((char *)func_name, -1, rettv, 2, argv, &funcexe);
|
|
tv_clear(&argv[0]);
|
|
if (r == FAIL || called_emsg != called_emsg_start) {
|
|
break;
|
|
}
|
|
}
|
|
tv_list_set_lock(l, prev_locked);
|
|
}
|
|
} else {
|
|
const blob_T *const b = argvars[0].vval.v_blob;
|
|
int i;
|
|
|
|
if (argvars[2].v_type == VAR_UNKNOWN) {
|
|
if (tv_blob_len(b) == 0) {
|
|
semsg(_(e_reduceempty), "Blob");
|
|
return;
|
|
}
|
|
initial.v_type = VAR_NUMBER;
|
|
initial.vval.v_number = tv_blob_get(b, 0);
|
|
i = 1;
|
|
} else if (argvars[2].v_type != VAR_NUMBER) {
|
|
emsg(_(e_number_exp));
|
|
return;
|
|
} else {
|
|
initial = argvars[2];
|
|
i = 0;
|
|
}
|
|
|
|
tv_copy(&initial, rettv);
|
|
for (; i < tv_blob_len(b); i++) {
|
|
argv[0] = *rettv;
|
|
argv[1].v_type = VAR_NUMBER;
|
|
argv[1].vval.v_number = tv_blob_get(b, i);
|
|
if (call_func((char *)func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SP_NOMOVE 0x01 ///< don't move cursor
|
|
#define SP_REPEAT 0x02 ///< repeat to find outer pair
|
|
#define SP_RETCOUNT 0x04 ///< return matchcount
|
|
#define SP_SETPCMARK 0x08 ///< set previous context mark
|
|
#define SP_START 0x10 ///< accept match at start position
|
|
#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern
|
|
#define SP_END 0x40 ///< leave cursor at end of match
|
|
#define SP_COLUMN 0x80 ///< start at cursor column
|
|
|
|
/// Get flags for a search function.
|
|
/// Possibly sets "p_ws".
|
|
///
|
|
/// @return BACKWARD, FORWARD or zero (for an error).
|
|
static int get_search_arg(typval_T *varp, int *flagsp)
|
|
{
|
|
int dir = FORWARD;
|
|
|
|
if (varp->v_type != VAR_UNKNOWN) {
|
|
char nbuf[NUMBUFLEN];
|
|
const char *flags = tv_get_string_buf_chk(varp, nbuf);
|
|
if (flags == NULL) {
|
|
return 0; // Type error; errmsg already given.
|
|
}
|
|
int mask;
|
|
while (*flags != NUL) {
|
|
switch (*flags) {
|
|
case 'b':
|
|
dir = BACKWARD; break;
|
|
case 'w':
|
|
p_ws = true; break;
|
|
case 'W':
|
|
p_ws = false; break;
|
|
default:
|
|
mask = 0;
|
|
if (flagsp != NULL) {
|
|
switch (*flags) {
|
|
case 'c':
|
|
mask = SP_START; break;
|
|
case 'e':
|
|
mask = SP_END; break;
|
|
case 'm':
|
|
mask = SP_RETCOUNT; break;
|
|
case 'n':
|
|
mask = SP_NOMOVE; break;
|
|
case 'p':
|
|
mask = SP_SUBPAT; break;
|
|
case 'r':
|
|
mask = SP_REPEAT; break;
|
|
case 's':
|
|
mask = SP_SETPCMARK; break;
|
|
case 'z':
|
|
mask = SP_COLUMN; break;
|
|
}
|
|
}
|
|
if (mask == 0) {
|
|
semsg(_(e_invarg2), flags);
|
|
dir = 0;
|
|
} else {
|
|
*flagsp |= mask;
|
|
}
|
|
}
|
|
if (dir == 0) {
|
|
break;
|
|
}
|
|
flags++;
|
|
}
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
/// Shared by search() and searchpos() functions.
|
|
static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
|
|
{
|
|
bool save_p_ws = p_ws;
|
|
int retval = 0; // default: FAIL
|
|
long lnum_stop = 0;
|
|
long time_limit = 0;
|
|
int options = SEARCH_KEEP;
|
|
bool use_skip = false;
|
|
|
|
const char *const pat = tv_get_string(&argvars[0]);
|
|
int dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
|
|
if (dir == 0) {
|
|
goto theend;
|
|
}
|
|
int flags = *flagsp;
|
|
if (flags & SP_START) {
|
|
options |= SEARCH_START;
|
|
}
|
|
if (flags & SP_END) {
|
|
options |= SEARCH_END;
|
|
}
|
|
if (flags & SP_COLUMN) {
|
|
options |= SEARCH_COL;
|
|
}
|
|
|
|
// Optional arguments: line number to stop searching, timeout and skip.
|
|
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
|
|
lnum_stop = tv_get_number_chk(&argvars[2], NULL);
|
|
if (lnum_stop < 0) {
|
|
goto theend;
|
|
}
|
|
if (argvars[3].v_type != VAR_UNKNOWN) {
|
|
time_limit = tv_get_number_chk(&argvars[3], NULL);
|
|
if (time_limit < 0) {
|
|
goto theend;
|
|
}
|
|
use_skip = eval_expr_valid_arg(&argvars[4]);
|
|
}
|
|
}
|
|
|
|
// Set the time limit, if there is one.
|
|
proftime_T tm = profile_setlimit(time_limit);
|
|
|
|
// This function does not accept SP_REPEAT and SP_RETCOUNT flags.
|
|
// Check to make sure only those flags are set.
|
|
// Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
|
|
// flags cannot be set. Check for that condition also.
|
|
if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
|
|
|| ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[1]));
|
|
goto theend;
|
|
}
|
|
|
|
pos_T save_cursor;
|
|
pos_T pos = save_cursor = curwin->w_cursor;
|
|
pos_T firstpos = { 0 };
|
|
searchit_arg_T sia = {
|
|
.sa_stop_lnum = (linenr_T)lnum_stop,
|
|
.sa_tm = &tm,
|
|
};
|
|
|
|
int subpatnum;
|
|
|
|
// Repeat until {skip} returns false.
|
|
for (;;) {
|
|
subpatnum
|
|
= searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia);
|
|
// finding the first match again means there is no match where {skip}
|
|
// evaluates to zero.
|
|
if (firstpos.lnum != 0 && equalpos(pos, firstpos)) {
|
|
subpatnum = FAIL;
|
|
}
|
|
|
|
if (subpatnum == FAIL || !use_skip) {
|
|
// didn't find it or no skip argument
|
|
break;
|
|
}
|
|
firstpos = pos;
|
|
|
|
// If the skip expression matches, ignore this match.
|
|
{
|
|
const pos_T save_pos = curwin->w_cursor;
|
|
|
|
curwin->w_cursor = pos;
|
|
bool err = false;
|
|
const bool do_skip = eval_expr_to_bool(&argvars[4], &err);
|
|
curwin->w_cursor = save_pos;
|
|
if (err) {
|
|
// Evaluating {skip} caused an error, break here.
|
|
subpatnum = FAIL;
|
|
break;
|
|
}
|
|
if (!do_skip) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// clear the start flag to avoid getting stuck here
|
|
options &= ~SEARCH_START;
|
|
}
|
|
|
|
if (subpatnum != FAIL) {
|
|
if (flags & SP_SUBPAT) {
|
|
retval = subpatnum;
|
|
} else {
|
|
retval = pos.lnum;
|
|
}
|
|
if (flags & SP_SETPCMARK) {
|
|
setpcmark();
|
|
}
|
|
curwin->w_cursor = pos;
|
|
if (match_pos != NULL) {
|
|
// Store the match cursor position
|
|
match_pos->lnum = pos.lnum;
|
|
match_pos->col = pos.col + 1;
|
|
}
|
|
// "/$" will put the cursor after the end of the line, may need to
|
|
// correct that here
|
|
check_cursor();
|
|
}
|
|
|
|
// If 'n' flag is used: restore cursor position.
|
|
if (flags & SP_NOMOVE) {
|
|
curwin->w_cursor = save_cursor;
|
|
} else {
|
|
curwin->w_set_curswant = true;
|
|
}
|
|
theend:
|
|
p_ws = save_p_ws;
|
|
|
|
return retval;
|
|
}
|
|
|
|
/// "rpcnotify()" function
|
|
static void f_rpcnotify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) {
|
|
semsg(_(e_invarg2), "Channel id must be a positive integer");
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_STRING) {
|
|
semsg(_(e_invarg2), "Event type must be a string");
|
|
return;
|
|
}
|
|
|
|
Array args = ARRAY_DICT_INIT;
|
|
|
|
for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
|
|
ADD(args, vim_to_object(tv));
|
|
}
|
|
|
|
if (!rpc_send_event((uint64_t)argvars[0].vval.v_number,
|
|
tv_get_string(&argvars[1]), args)) {
|
|
semsg(_(e_invarg2), "Channel doesn't exist");
|
|
return;
|
|
}
|
|
|
|
rettv->vval.v_number = 1;
|
|
}
|
|
|
|
/// "rpcrequest()" function
|
|
static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
const int l_provider_call_nesting = provider_call_nesting;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) {
|
|
semsg(_(e_invarg2), "Channel id must be a positive integer");
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_STRING) {
|
|
semsg(_(e_invarg2), "Method name must be a string");
|
|
return;
|
|
}
|
|
|
|
Array args = ARRAY_DICT_INIT;
|
|
|
|
for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
|
|
ADD(args, vim_to_object(tv));
|
|
}
|
|
|
|
sctx_T save_current_sctx;
|
|
char *save_autocmd_fname, *save_autocmd_match;
|
|
int save_autocmd_bufnr;
|
|
funccal_entry_T funccal_entry;
|
|
|
|
if (l_provider_call_nesting) {
|
|
// If this is called from a provider function, restore the scope
|
|
// information of the caller.
|
|
save_current_sctx = current_sctx;
|
|
save_autocmd_fname = autocmd_fname;
|
|
save_autocmd_match = autocmd_match;
|
|
save_autocmd_bufnr = autocmd_bufnr;
|
|
save_funccal(&funccal_entry);
|
|
|
|
current_sctx = provider_caller_scope.script_ctx;
|
|
ga_grow(&exestack, 1);
|
|
((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry;
|
|
autocmd_fname = provider_caller_scope.autocmd_fname;
|
|
autocmd_match = provider_caller_scope.autocmd_match;
|
|
autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
|
|
set_current_funccal((funccall_T *)(provider_caller_scope.funccalp));
|
|
}
|
|
|
|
Error err = ERROR_INIT;
|
|
|
|
uint64_t chan_id = (uint64_t)argvars[0].vval.v_number;
|
|
const char *method = tv_get_string(&argvars[1]);
|
|
|
|
ArenaMem res_mem = NULL;
|
|
Object result = rpc_send_call(chan_id, method, args, &res_mem, &err);
|
|
|
|
if (l_provider_call_nesting) {
|
|
current_sctx = save_current_sctx;
|
|
exestack.ga_len--;
|
|
autocmd_fname = save_autocmd_fname;
|
|
autocmd_match = save_autocmd_match;
|
|
autocmd_bufnr = save_autocmd_bufnr;
|
|
restore_funccal();
|
|
}
|
|
|
|
if (ERROR_SET(&err)) {
|
|
const char *name = NULL;
|
|
Channel *chan = find_channel(chan_id);
|
|
if (chan) {
|
|
name = rpc_client_name(chan);
|
|
}
|
|
msg_ext_set_kind("rpc_error");
|
|
if (name) {
|
|
semsg_multiline("Error invoking '%s' on channel %" PRIu64 " (%s):\n%s",
|
|
method, chan_id, name, err.msg);
|
|
} else {
|
|
semsg_multiline("Error invoking '%s' on channel %" PRIu64 ":\n%s",
|
|
method, chan_id, err.msg);
|
|
}
|
|
|
|
goto end;
|
|
}
|
|
|
|
if (!object_to_vim(result, rettv, &err)) {
|
|
semsg(_("Error converting the call result: %s"), err.msg);
|
|
}
|
|
|
|
end:
|
|
arena_mem_free(res_mem);
|
|
api_clear_error(&err);
|
|
}
|
|
|
|
/// "rpcstart()" function (DEPRECATED)
|
|
static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_STRING
|
|
|| (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
|
|
// Wrong argument types
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
list_T *args = NULL;
|
|
int argsl = 0;
|
|
if (argvars[1].v_type == VAR_LIST) {
|
|
args = argvars[1].vval.v_list;
|
|
argsl = tv_list_len(args);
|
|
// Assert that all list items are strings
|
|
int i = 0;
|
|
TV_LIST_ITER_CONST(args, arg, {
|
|
if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
|
|
semsg(_("E5010: List item %d of the second argument is not a string"),
|
|
i);
|
|
return;
|
|
}
|
|
i++;
|
|
});
|
|
}
|
|
|
|
if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
|
|
emsg(_(e_api_spawn_failed));
|
|
return;
|
|
}
|
|
|
|
// Allocate extra memory for the argument vector and the NULL pointer
|
|
int argvl = argsl + 2;
|
|
char **argv = xmalloc(sizeof(char_u *) * (size_t)argvl);
|
|
|
|
// Copy program name
|
|
argv[0] = xstrdup(argvars[0].vval.v_string);
|
|
|
|
int i = 1;
|
|
// Copy arguments to the vector
|
|
if (argsl > 0) {
|
|
TV_LIST_ITER_CONST(args, arg, {
|
|
argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
|
|
});
|
|
}
|
|
|
|
// The last item of argv must be NULL
|
|
argv[i] = NULL;
|
|
|
|
Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
|
|
CALLBACK_READER_INIT, CALLBACK_NONE,
|
|
false, true, false, false,
|
|
kChannelStdinPipe, NULL, 0, 0, NULL,
|
|
&rettv->vval.v_number);
|
|
if (chan) {
|
|
channel_create_event(chan, NULL);
|
|
}
|
|
}
|
|
|
|
/// "rpcstop()" function
|
|
static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
// Wrong argument types
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
// if called with a job, stop it, else closes the channel
|
|
uint64_t id = (uint64_t)argvars[0].vval.v_number;
|
|
if (find_job(id, false)) {
|
|
f_jobstop(argvars, rettv, fptr);
|
|
} else {
|
|
const char *error;
|
|
rettv->vval.v_number =
|
|
channel_close((uint64_t)argvars[0].vval.v_number, kChannelPartRpc, &error);
|
|
if (!rettv->vval.v_number) {
|
|
emsg(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "screenattr()" function
|
|
static void f_screenattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
|
|
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
|
|
|
|
ScreenGrid *grid;
|
|
screenchar_adjust(&grid, &row, &col);
|
|
|
|
int c;
|
|
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
|
|
c = -1;
|
|
} else {
|
|
c = grid->attrs[grid->line_offset[row] + (size_t)col];
|
|
}
|
|
rettv->vval.v_number = c;
|
|
}
|
|
|
|
/// "screenchar()" function
|
|
static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
|
|
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
|
|
|
|
ScreenGrid *grid;
|
|
screenchar_adjust(&grid, &row, &col);
|
|
|
|
int c;
|
|
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
|
|
c = -1;
|
|
} else {
|
|
c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col]);
|
|
}
|
|
rettv->vval.v_number = c;
|
|
}
|
|
|
|
/// "screenchars()" function
|
|
static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
|
|
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
|
|
|
|
ScreenGrid *grid;
|
|
screenchar_adjust(&grid, &row, &col);
|
|
|
|
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
|
|
tv_list_alloc_ret(rettv, 0);
|
|
return;
|
|
}
|
|
int pcc[MAX_MCO];
|
|
int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + (size_t)col], pcc);
|
|
int composing_len = 0;
|
|
while (pcc[composing_len] != 0) {
|
|
composing_len++;
|
|
}
|
|
tv_list_alloc_ret(rettv, composing_len + 1);
|
|
tv_list_append_number(rettv->vval.v_list, c);
|
|
for (int i = 0; i < composing_len; i++) {
|
|
tv_list_append_number(rettv->vval.v_list, pcc[i]);
|
|
}
|
|
}
|
|
|
|
/// "screencol()" function
|
|
///
|
|
/// First column is 1 to be consistent with virtcol().
|
|
static void f_screencol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = ui_current_col() + 1;
|
|
}
|
|
|
|
/// "screenpos({winid}, {lnum}, {col})" function
|
|
static void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
dict_T *dict = rettv->vval.v_dict;
|
|
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
return;
|
|
}
|
|
|
|
pos_T pos = {
|
|
.lnum = (linenr_T)tv_get_number(&argvars[1]),
|
|
.col = (colnr_T)tv_get_number(&argvars[2]) - 1,
|
|
.coladd = 0
|
|
};
|
|
int row = 0;
|
|
int scol = 0, ccol = 0, ecol = 0;
|
|
textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
|
|
|
|
tv_dict_add_nr(dict, S_LEN("row"), row);
|
|
tv_dict_add_nr(dict, S_LEN("col"), scol);
|
|
tv_dict_add_nr(dict, S_LEN("curscol"), ccol);
|
|
tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
|
|
}
|
|
|
|
/// "screenrow()" function
|
|
static void f_screenrow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = ui_current_row() + 1;
|
|
}
|
|
|
|
/// "screenstring()" function
|
|
static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_string = NULL;
|
|
rettv->v_type = VAR_STRING;
|
|
|
|
ScreenGrid *grid;
|
|
int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
|
|
int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
|
|
|
|
screenchar_adjust(&grid, &row, &col);
|
|
|
|
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
|
|
return;
|
|
}
|
|
|
|
rettv->vval.v_string = (char *)vim_strsave(grid->chars[grid->line_offset[row] + (size_t)col]);
|
|
}
|
|
|
|
/// "search()" function
|
|
static void f_search(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int flags = 0;
|
|
|
|
rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
|
|
}
|
|
|
|
/// "searchdecl()" function
|
|
static void f_searchdecl(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int locally = 1;
|
|
int thisblock = 0;
|
|
bool error = false;
|
|
|
|
rettv->vval.v_number = 1; // default: FAIL
|
|
|
|
const char *const name = tv_get_string_chk(&argvars[0]);
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
locally = tv_get_number_chk(&argvars[1], &error) == 0;
|
|
if (!error && argvars[2].v_type != VAR_UNKNOWN) {
|
|
thisblock = tv_get_number_chk(&argvars[2], &error) != 0;
|
|
}
|
|
}
|
|
if (!error && name != NULL) {
|
|
rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally,
|
|
thisblock, SEARCH_KEEP) == FAIL;
|
|
}
|
|
}
|
|
|
|
/// Used by searchpair() and searchpairpos()
|
|
static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
|
|
{
|
|
bool save_p_ws = p_ws;
|
|
int flags = 0;
|
|
int retval = 0; // default: FAIL
|
|
long lnum_stop = 0;
|
|
long time_limit = 0;
|
|
|
|
// Get the three pattern arguments: start, middle, end. Will result in an
|
|
// error if not a valid argument.
|
|
char nbuf1[NUMBUFLEN];
|
|
char nbuf2[NUMBUFLEN];
|
|
const char *spat = tv_get_string_chk(&argvars[0]);
|
|
const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
|
|
const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
|
|
if (spat == NULL || mpat == NULL || epat == NULL) {
|
|
goto theend; // Type error.
|
|
}
|
|
|
|
// Handle the optional fourth argument: flags.
|
|
int dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
|
|
if (dir == 0) {
|
|
goto theend;
|
|
}
|
|
|
|
// Don't accept SP_END or SP_SUBPAT.
|
|
// Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
|
|
if ((flags & (SP_END | SP_SUBPAT)) != 0
|
|
|| ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[3]));
|
|
goto theend;
|
|
}
|
|
|
|
// Using 'r' implies 'W', otherwise it doesn't work.
|
|
if (flags & SP_REPEAT) {
|
|
p_ws = false;
|
|
}
|
|
|
|
// Optional fifth argument: skip expression.
|
|
const typval_T *skip;
|
|
if (argvars[3].v_type == VAR_UNKNOWN
|
|
|| argvars[4].v_type == VAR_UNKNOWN) {
|
|
skip = NULL;
|
|
} else {
|
|
// Type is checked later.
|
|
skip = &argvars[4];
|
|
|
|
if (argvars[5].v_type != VAR_UNKNOWN) {
|
|
lnum_stop = tv_get_number_chk(&argvars[5], NULL);
|
|
if (lnum_stop < 0) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[5]));
|
|
goto theend;
|
|
}
|
|
if (argvars[6].v_type != VAR_UNKNOWN) {
|
|
time_limit = tv_get_number_chk(&argvars[6], NULL);
|
|
if (time_limit < 0) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[6]));
|
|
goto theend;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
retval = (int)do_searchpair(spat, mpat, epat, dir, skip,
|
|
flags, match_pos, (linenr_T)lnum_stop, time_limit);
|
|
|
|
theend:
|
|
p_ws = save_p_ws;
|
|
|
|
return retval;
|
|
}
|
|
|
|
/// "searchpair()" function
|
|
static void f_searchpair(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = searchpair_cmn(argvars, NULL);
|
|
}
|
|
|
|
/// "searchpairpos()" function
|
|
static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
pos_T match_pos;
|
|
int lnum = 0;
|
|
int col = 0;
|
|
|
|
tv_list_alloc_ret(rettv, 2);
|
|
|
|
if (searchpair_cmn(argvars, &match_pos) > 0) {
|
|
lnum = match_pos.lnum;
|
|
col = match_pos.col;
|
|
}
|
|
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
|
|
}
|
|
|
|
/// Search for a start/middle/end thing.
|
|
/// Used by searchpair(), see its documentation for the details.
|
|
///
|
|
/// @param spat start pattern
|
|
/// @param mpat middle pattern
|
|
/// @param epat end pattern
|
|
/// @param dir BACKWARD or FORWARD
|
|
/// @param skip skip expression
|
|
/// @param flags SP_SETPCMARK and other SP_ values
|
|
/// @param lnum_stop stop at this line if not zero
|
|
/// @param time_limit stop after this many msec
|
|
///
|
|
/// @returns 0 or -1 for no match,
|
|
long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir,
|
|
const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop,
|
|
long time_limit)
|
|
FUNC_ATTR_NONNULL_ARG(1, 2, 3)
|
|
{
|
|
long retval = 0;
|
|
int nest = 1;
|
|
bool use_skip = false;
|
|
int options = SEARCH_KEEP;
|
|
|
|
// Make 'cpoptions' empty, the 'l' flag should not be used here.
|
|
char *save_cpo = p_cpo;
|
|
p_cpo = empty_option;
|
|
|
|
// Set the time limit, if there is one.
|
|
proftime_T tm = profile_setlimit(time_limit);
|
|
|
|
// Make two search patterns: start/end (pat2, for in nested pairs) and
|
|
// start/middle/end (pat3, for the top pair).
|
|
const size_t pat2_len = strlen(spat) + strlen(epat) + 17;
|
|
char_u *pat2 = xmalloc(pat2_len);
|
|
const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25;
|
|
char_u *pat3 = xmalloc(pat3_len);
|
|
snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
|
|
if (*mpat == NUL) {
|
|
STRCPY(pat3, pat2);
|
|
} else {
|
|
snprintf((char *)pat3, pat3_len,
|
|
"\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat);
|
|
}
|
|
if (flags & SP_START) {
|
|
options |= SEARCH_START;
|
|
}
|
|
|
|
if (skip != NULL) {
|
|
use_skip = eval_expr_valid_arg(skip);
|
|
}
|
|
|
|
pos_T save_cursor = curwin->w_cursor;
|
|
pos_T pos = curwin->w_cursor;
|
|
pos_T firstpos;
|
|
clearpos(&firstpos);
|
|
pos_T foundpos;
|
|
clearpos(&foundpos);
|
|
char_u *pat = pat3;
|
|
for (;;) {
|
|
searchit_arg_T sia = {
|
|
.sa_stop_lnum = lnum_stop,
|
|
.sa_tm = &tm,
|
|
};
|
|
|
|
int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
|
|
options, RE_SEARCH, &sia);
|
|
if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
|
|
// didn't find it or found the first match again: FAIL
|
|
break;
|
|
}
|
|
|
|
if (firstpos.lnum == 0) {
|
|
firstpos = pos;
|
|
}
|
|
if (equalpos(pos, foundpos)) {
|
|
// Found the same position again. Can happen with a pattern that
|
|
// has "\zs" at the end and searching backwards. Advance one
|
|
// character and try again.
|
|
if (dir == BACKWARD) {
|
|
decl(&pos);
|
|
} else {
|
|
incl(&pos);
|
|
}
|
|
}
|
|
foundpos = pos;
|
|
|
|
// clear the start flag to avoid getting stuck here
|
|
options &= ~SEARCH_START;
|
|
|
|
// If the skip pattern matches, ignore this match.
|
|
if (use_skip) {
|
|
pos_T save_pos = curwin->w_cursor;
|
|
curwin->w_cursor = pos;
|
|
bool err = false;
|
|
const bool r = eval_expr_to_bool(skip, &err);
|
|
curwin->w_cursor = save_pos;
|
|
if (err) {
|
|
// Evaluating {skip} caused an error, break here.
|
|
curwin->w_cursor = save_cursor;
|
|
retval = -1;
|
|
break;
|
|
}
|
|
if (r) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) {
|
|
// Found end when searching backwards or start when searching
|
|
// forward: nested pair.
|
|
nest++;
|
|
pat = pat2; // nested, don't search for middle
|
|
} else {
|
|
// Found end when searching forward or start when searching
|
|
// backward: end of (nested) pair; or found middle in outer pair.
|
|
if (--nest == 1) {
|
|
pat = pat3; // outer level, search for middle
|
|
}
|
|
}
|
|
|
|
if (nest == 0) {
|
|
// Found the match: return matchcount or line number.
|
|
if (flags & SP_RETCOUNT) {
|
|
retval++;
|
|
} else {
|
|
retval = pos.lnum;
|
|
}
|
|
if (flags & SP_SETPCMARK) {
|
|
setpcmark();
|
|
}
|
|
curwin->w_cursor = pos;
|
|
if (!(flags & SP_REPEAT)) {
|
|
break;
|
|
}
|
|
nest = 1; // search for next unmatched
|
|
}
|
|
}
|
|
|
|
if (match_pos != NULL) {
|
|
// Store the match cursor position
|
|
match_pos->lnum = curwin->w_cursor.lnum;
|
|
match_pos->col = curwin->w_cursor.col + 1;
|
|
}
|
|
|
|
// If 'n' flag is used or search failed: restore cursor position.
|
|
if ((flags & SP_NOMOVE) || retval == 0) {
|
|
curwin->w_cursor = save_cursor;
|
|
}
|
|
|
|
xfree(pat2);
|
|
xfree(pat3);
|
|
if (p_cpo == empty_option) {
|
|
p_cpo = save_cpo;
|
|
} else {
|
|
// Darn, evaluating the {skip} expression changed the value.
|
|
// If it's still empty it was changed and restored, need to restore in
|
|
// the complicated way.
|
|
if (*p_cpo == NUL) {
|
|
set_option_value_give_err("cpo", 0L, save_cpo, 0);
|
|
}
|
|
free_string_option(save_cpo);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/// "searchpos()" function
|
|
static void f_searchpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
pos_T match_pos;
|
|
int flags = 0;
|
|
|
|
const int n = search_cmn(argvars, &match_pos, &flags);
|
|
|
|
tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT)));
|
|
|
|
const int lnum = (n > 0 ? match_pos.lnum : 0);
|
|
const int col = (n > 0 ? match_pos.col : 0);
|
|
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
|
|
if (flags & SP_SUBPAT) {
|
|
tv_list_append_number(rettv->vval.v_list, (varnumber_T)n);
|
|
}
|
|
}
|
|
|
|
/// "serverlist()" function
|
|
static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
size_t n;
|
|
char **addrs = server_address_list(&n);
|
|
|
|
// Copy addrs into a linked list.
|
|
list_T *const l = tv_list_alloc_ret(rettv, (ptrdiff_t)n);
|
|
for (size_t i = 0; i < n; i++) {
|
|
tv_list_append_allocated_string(l, addrs[i]);
|
|
}
|
|
xfree(addrs);
|
|
}
|
|
|
|
/// "serverstart()" function
|
|
static void f_serverstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL; // Address of the new server
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
char *address;
|
|
// If the user supplied an address, use it, otherwise use a temp.
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
if (argvars[0].v_type != VAR_STRING) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
} else {
|
|
address = xstrdup(tv_get_string(argvars));
|
|
}
|
|
} else {
|
|
address = server_address_new(NULL);
|
|
}
|
|
|
|
int result = server_start(address);
|
|
xfree(address);
|
|
|
|
if (result != 0) {
|
|
semsg("Failed to start server: %s",
|
|
result > 0 ? "Unknown system error" : uv_strerror(result));
|
|
return;
|
|
}
|
|
|
|
// Since it's possible server_start adjusted the given {address} (e.g.,
|
|
// "localhost:" will now have a port), return the final value to the user.
|
|
size_t n;
|
|
char **addrs = server_address_list(&n);
|
|
rettv->vval.v_string = addrs[n - 1];
|
|
|
|
n--;
|
|
for (size_t i = 0; i < n; i++) {
|
|
xfree(addrs[i]);
|
|
}
|
|
xfree(addrs);
|
|
}
|
|
|
|
/// "serverstop()" function
|
|
static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type != VAR_STRING) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
rettv->v_type = VAR_NUMBER;
|
|
rettv->vval.v_number = 0;
|
|
if (argvars[0].vval.v_string) {
|
|
bool rv = server_stop(argvars[0].vval.v_string);
|
|
rettv->vval.v_number = (rv ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
/// "setbufline()" function
|
|
static void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T lnum;
|
|
buf_T *buf;
|
|
|
|
buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL) {
|
|
rettv->vval.v_number = 1; // FAIL
|
|
} else {
|
|
lnum = tv_get_lnum_buf(&argvars[1], buf);
|
|
set_buffer_lines(buf, lnum, false, &argvars[2], rettv);
|
|
}
|
|
}
|
|
|
|
/// Set the cursor or mark position.
|
|
/// If 'charpos' is true, then use the column number as a character offset.
|
|
/// Otherwise use the column number as a byte offset.
|
|
static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
|
|
{
|
|
colnr_T curswant = -1;
|
|
|
|
rettv->vval.v_number = -1;
|
|
const char *const name = tv_get_string_chk(argvars);
|
|
if (name != NULL) {
|
|
pos_T pos;
|
|
int fnum;
|
|
if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) {
|
|
if (pos.col != MAXCOL && --pos.col < 0) {
|
|
pos.col = 0;
|
|
}
|
|
if (name[0] == '.' && name[1] == NUL) {
|
|
// set cursor; "fnum" is ignored
|
|
curwin->w_cursor = pos;
|
|
if (curswant >= 0) {
|
|
curwin->w_curswant = curswant - 1;
|
|
curwin->w_set_curswant = false;
|
|
}
|
|
check_cursor();
|
|
rettv->vval.v_number = 0;
|
|
} else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
|
|
// set mark
|
|
if (setmark_pos((uint8_t)name[1], &pos, fnum, NULL) == OK) {
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
} else {
|
|
emsg(_(e_invarg));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "setcharpos()" function
|
|
static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
set_position(argvars, rettv, true);
|
|
}
|
|
|
|
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_DICT) {
|
|
emsg(_(e_dictreq));
|
|
return;
|
|
}
|
|
|
|
dict_T *d = argvars[0].vval.v_dict;
|
|
if (d != NULL) {
|
|
char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false);
|
|
if (csearch != NULL) {
|
|
int pcc[MAX_MCO];
|
|
const int c = utfc_ptr2char(csearch, pcc);
|
|
set_last_csearch(c, csearch, utfc_ptr2len((char *)csearch));
|
|
}
|
|
|
|
dictitem_T *di = tv_dict_find(d, S_LEN("forward"));
|
|
if (di != NULL) {
|
|
set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD);
|
|
}
|
|
|
|
di = tv_dict_find(d, S_LEN("until"));
|
|
if (di != NULL) {
|
|
set_csearch_until(!!tv_get_number(&di->di_tv));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "setcmdpos()" function
|
|
static void f_setcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const int pos = (int)tv_get_number(&argvars[0]) - 1;
|
|
|
|
if (pos >= 0) {
|
|
rettv->vval.v_number = set_cmdline_pos(pos);
|
|
}
|
|
}
|
|
|
|
/// "setcursorcharpos" function
|
|
static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
set_cursorpos(argvars, rettv, true);
|
|
}
|
|
|
|
/// "setenv()" function
|
|
static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char namebuf[NUMBUFLEN];
|
|
char valbuf[NUMBUFLEN];
|
|
const char *name = tv_get_string_buf(&argvars[0], namebuf);
|
|
|
|
if (argvars[1].v_type == VAR_SPECIAL
|
|
&& argvars[1].vval.v_special == kSpecialVarNull) {
|
|
vim_unsetenv_ext(name);
|
|
} else {
|
|
vim_setenv_ext(name, tv_get_string_buf(&argvars[1], valbuf));
|
|
}
|
|
}
|
|
|
|
/// "setfperm({fname}, {mode})" function
|
|
static void f_setfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = 0;
|
|
|
|
const char *const fname = tv_get_string_chk(&argvars[0]);
|
|
if (fname == NULL) {
|
|
return;
|
|
}
|
|
|
|
char modebuf[NUMBUFLEN];
|
|
const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
|
|
if (mode_str == NULL) {
|
|
return;
|
|
}
|
|
if (strlen(mode_str) != 9) {
|
|
semsg(_(e_invarg2), mode_str);
|
|
return;
|
|
}
|
|
|
|
int mask = 1;
|
|
int mode = 0;
|
|
for (int i = 8; i >= 0; i--) {
|
|
if (mode_str[i] != '-') {
|
|
mode |= mask;
|
|
}
|
|
mask = mask << 1;
|
|
}
|
|
rettv->vval.v_number = os_setperm(fname, mode) == OK;
|
|
}
|
|
|
|
/// "setline()" function
|
|
static void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
linenr_T lnum = tv_get_lnum(&argvars[0]);
|
|
set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv);
|
|
}
|
|
|
|
/// "setpos()" function
|
|
static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
set_position(argvars, rettv, false);
|
|
}
|
|
|
|
/// Translate a register type string to the yank type and block length
|
|
static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
char *stropt = *pp;
|
|
switch (*stropt) {
|
|
case 'v':
|
|
case 'c': // character-wise selection
|
|
*yank_type = kMTCharWise;
|
|
break;
|
|
case 'V':
|
|
case 'l': // line-wise selection
|
|
*yank_type = kMTLineWise;
|
|
break;
|
|
case 'b':
|
|
case Ctrl_V: // block-wise selection
|
|
*yank_type = kMTBlockWise;
|
|
if (ascii_isdigit(stropt[1])) {
|
|
stropt++;
|
|
*block_len = getdigits_long(&stropt, false, 0) - 1;
|
|
stropt--;
|
|
}
|
|
break;
|
|
default:
|
|
return FAIL;
|
|
}
|
|
*pp = stropt;
|
|
return OK;
|
|
}
|
|
|
|
/// "setreg()" function
|
|
static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
bool append = false;
|
|
|
|
long block_len = -1;
|
|
MotionType yank_type = kMTUnknown;
|
|
|
|
rettv->vval.v_number = 1; // FAIL is default.
|
|
|
|
const char *const strregname = tv_get_string_chk(argvars);
|
|
if (strregname == NULL) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
char regname = *strregname;
|
|
if (regname == 0 || regname == '@') {
|
|
regname = '"';
|
|
}
|
|
|
|
const typval_T *regcontents = NULL;
|
|
char pointreg = 0;
|
|
if (argvars[1].v_type == VAR_DICT) {
|
|
dict_T *const d = argvars[1].vval.v_dict;
|
|
|
|
if (tv_dict_len(d) == 0) {
|
|
// Empty dict, clear the register (like setreg(0, []))
|
|
char *lstval[2] = { NULL, NULL };
|
|
write_reg_contents_lst(regname, lstval, false, kMTUnknown, -1);
|
|
return;
|
|
}
|
|
|
|
dictitem_T *const di = tv_dict_find(d, "regcontents", -1);
|
|
if (di != NULL) {
|
|
regcontents = &di->di_tv;
|
|
}
|
|
|
|
const char *stropt = tv_dict_get_string(d, "regtype", false);
|
|
if (stropt != NULL) {
|
|
const int ret = get_yank_type((char **)&stropt, &yank_type, &block_len);
|
|
|
|
if (ret == FAIL || *(++stropt) != NUL) {
|
|
semsg(_(e_invargval), "value");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (regname == '"') {
|
|
stropt = tv_dict_get_string(d, "points_to", false);
|
|
if (stropt != NULL) {
|
|
pointreg = *stropt;
|
|
regname = pointreg;
|
|
}
|
|
} else if (tv_dict_get_number(d, "isunnamed")) {
|
|
pointreg = regname;
|
|
}
|
|
} else {
|
|
regcontents = &argvars[1];
|
|
}
|
|
|
|
bool set_unnamed = false;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
if (yank_type != kMTUnknown) {
|
|
semsg(_(e_toomanyarg), "setreg");
|
|
return;
|
|
}
|
|
|
|
const char *stropt = tv_get_string_chk(&argvars[2]);
|
|
if (stropt == NULL) {
|
|
return; // Type error.
|
|
}
|
|
for (; *stropt != NUL; stropt++) {
|
|
switch (*stropt) {
|
|
case 'a':
|
|
case 'A': // append
|
|
append = true;
|
|
break;
|
|
case 'u':
|
|
case '"': // unnamed register
|
|
set_unnamed = true;
|
|
break;
|
|
default:
|
|
get_yank_type((char **)&stropt, &yank_type, &block_len);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (regcontents != NULL && regcontents->v_type == VAR_LIST) {
|
|
list_T *const ll = regcontents->vval.v_list;
|
|
// If the list is NULL handle like an empty list.
|
|
const int len = tv_list_len(ll);
|
|
|
|
// First half: use for pointers to result lines; second half: use for
|
|
// pointers to allocated copies.
|
|
char **lstval = xmalloc(sizeof(char *) * (((size_t)len + 1) * 2));
|
|
const char **curval = (const char **)lstval;
|
|
char **allocval = lstval + len + 2;
|
|
char **curallocval = allocval;
|
|
|
|
TV_LIST_ITER_CONST(ll, li, {
|
|
char buf[NUMBUFLEN];
|
|
*curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf);
|
|
if (*curval == NULL) {
|
|
goto free_lstval;
|
|
}
|
|
if (*curval == buf) {
|
|
// Need to make a copy,
|
|
// next tv_get_string_buf_chk() will overwrite the string.
|
|
*curallocval = xstrdup(*curval);
|
|
*curval = *curallocval;
|
|
curallocval++;
|
|
}
|
|
curval++;
|
|
});
|
|
*curval++ = NULL;
|
|
|
|
write_reg_contents_lst(regname, lstval, append, yank_type, (colnr_T)block_len);
|
|
|
|
free_lstval:
|
|
while (curallocval > allocval) {
|
|
xfree(*--curallocval);
|
|
}
|
|
xfree(lstval);
|
|
} else if (regcontents != NULL) {
|
|
const char *const strval = tv_get_string_chk(regcontents);
|
|
if (strval == NULL) {
|
|
return;
|
|
}
|
|
write_reg_contents_ex(regname, (const char_u *)strval, (ssize_t)STRLEN(strval),
|
|
append, yank_type, (colnr_T)block_len);
|
|
}
|
|
if (pointreg != 0) {
|
|
get_yank_register(pointreg, YREG_YANK);
|
|
}
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (set_unnamed) {
|
|
// Discard the result. We already handle the error case.
|
|
op_reg_set_previous(regname);
|
|
}
|
|
}
|
|
|
|
/// "settagstack()" function
|
|
static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
static char *e_invact2 = N_("E962: Invalid action: '%s'");
|
|
char action = 'r';
|
|
|
|
rettv->vval.v_number = -1;
|
|
|
|
// first argument: window number or id
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
return;
|
|
}
|
|
|
|
// second argument: dict with items to set in the tag stack
|
|
if (argvars[1].v_type != VAR_DICT) {
|
|
emsg(_(e_dictreq));
|
|
return;
|
|
}
|
|
dict_T *d = argvars[1].vval.v_dict;
|
|
if (d == NULL) {
|
|
return;
|
|
}
|
|
|
|
// third argument: action - 'a' for append and 'r' for replace.
|
|
// default is to replace the stack.
|
|
if (argvars[2].v_type == VAR_UNKNOWN) {
|
|
action = 'r';
|
|
} else if (argvars[2].v_type == VAR_STRING) {
|
|
const char *actstr;
|
|
actstr = tv_get_string_chk(&argvars[2]);
|
|
if (actstr == NULL) {
|
|
return;
|
|
}
|
|
if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't')
|
|
&& actstr[1] == NUL) {
|
|
action = *actstr;
|
|
} else {
|
|
semsg(_(e_invact2), actstr);
|
|
return;
|
|
}
|
|
} else {
|
|
emsg(_(e_stringreq));
|
|
return;
|
|
}
|
|
|
|
if (set_tagstack(wp, d, action) == OK) {
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
}
|
|
|
|
/// f_sha256 - sha256({string}) function
|
|
static void f_sha256(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *p = tv_get_string(&argvars[0]);
|
|
const char *hash = sha256_bytes((const uint8_t *)p, strlen(p), NULL, 0);
|
|
|
|
// make a copy of the hash (sha256_bytes returns a static buffer)
|
|
rettv->vval.v_string = xstrdup(hash);
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "shellescape({string})" function
|
|
static void f_shellescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const bool do_special = non_zero_arg(&argvars[1]);
|
|
|
|
rettv->vval.v_string =
|
|
(char *)vim_strsave_shellescape((const char_u *)tv_get_string(&argvars[0]), do_special,
|
|
do_special);
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// shiftwidth() function
|
|
static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = 0;
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
long col = (long)tv_get_number_chk(argvars, NULL);
|
|
if (col < 0) {
|
|
return; // type error; errmsg already given
|
|
}
|
|
rettv->vval.v_number = get_sw_value_col(curbuf, (colnr_T)col);
|
|
return;
|
|
}
|
|
rettv->vval.v_number = get_sw_value(curbuf);
|
|
}
|
|
|
|
/// "simplify()" function
|
|
static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const p = tv_get_string(&argvars[0]);
|
|
rettv->vval.v_string = xstrdup(p);
|
|
simplify_filename((char_u *)rettv->vval.v_string); // Simplify in place.
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "sockconnect()" function
|
|
static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) {
|
|
// Wrong argument types
|
|
semsg(_(e_invarg2), "expected dictionary");
|
|
return;
|
|
}
|
|
|
|
const char *mode = tv_get_string(&argvars[0]);
|
|
const char *address = tv_get_string(&argvars[1]);
|
|
|
|
bool tcp;
|
|
if (strcmp(mode, "tcp") == 0) {
|
|
tcp = true;
|
|
} else if (strcmp(mode, "pipe") == 0) {
|
|
tcp = false;
|
|
} else {
|
|
semsg(_(e_invarg2), "invalid mode");
|
|
return;
|
|
}
|
|
|
|
bool rpc = false;
|
|
CallbackReader on_data = CALLBACK_READER_INIT;
|
|
if (argvars[2].v_type == VAR_DICT) {
|
|
dict_T *opts = argvars[2].vval.v_dict;
|
|
rpc = tv_dict_get_number(opts, "rpc") != 0;
|
|
|
|
if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) {
|
|
return;
|
|
}
|
|
on_data.buffered = tv_dict_get_number(opts, "data_buffered");
|
|
if (on_data.buffered && on_data.cb.type == kCallbackNone) {
|
|
on_data.self = opts;
|
|
}
|
|
}
|
|
|
|
const char *error = NULL;
|
|
uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error);
|
|
|
|
if (error) {
|
|
semsg(_("connection failed: %s"), error);
|
|
}
|
|
|
|
rettv->vval.v_number = (varnumber_T)id;
|
|
rettv->v_type = VAR_NUMBER;
|
|
}
|
|
|
|
/// "stdioopen()" function
|
|
static void f_stdioopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_DICT) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
|
|
CallbackReader on_stdin = CALLBACK_READER_INIT;
|
|
dict_T *opts = argvars[0].vval.v_dict;
|
|
bool rpc = tv_dict_get_number(opts, "rpc") != 0;
|
|
|
|
if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
|
|
return;
|
|
}
|
|
if (!tv_dict_get_callback(opts, S_LEN("on_print"), &on_print)) {
|
|
return;
|
|
}
|
|
|
|
on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
|
|
if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
|
|
on_stdin.self = opts;
|
|
}
|
|
|
|
const char *error;
|
|
uint64_t id = channel_from_stdio(rpc, on_stdin, &error);
|
|
if (!id) {
|
|
semsg(e_stdiochan2, error);
|
|
}
|
|
|
|
rettv->vval.v_number = (varnumber_T)id;
|
|
rettv->v_type = VAR_NUMBER;
|
|
}
|
|
|
|
/// "reltimefloat()" function
|
|
static void f_reltimefloat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
proftime_T tm;
|
|
|
|
rettv->v_type = VAR_FLOAT;
|
|
rettv->vval.v_float = 0;
|
|
if (list2proftime(&argvars[0], &tm) == OK) {
|
|
rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;
|
|
}
|
|
}
|
|
|
|
/// "soundfold({word})" function
|
|
static void f_soundfold(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
const char *const s = tv_get_string(&argvars[0]);
|
|
rettv->vval.v_string = eval_soundfold(s);
|
|
}
|
|
|
|
/// "spellbadword()" function
|
|
static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const int wo_spell_save = curwin->w_p_spell;
|
|
|
|
if (!curwin->w_p_spell) {
|
|
did_set_spelllang(curwin);
|
|
curwin->w_p_spell = true;
|
|
}
|
|
|
|
if (*curwin->w_s->b_p_spl == NUL) {
|
|
emsg(_(e_no_spell));
|
|
curwin->w_p_spell = wo_spell_save;
|
|
return;
|
|
}
|
|
|
|
const char *word = "";
|
|
hlf_T attr = HLF_COUNT;
|
|
size_t len = 0;
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
// Find the start and length of the badly spelled word.
|
|
len = spell_move_to(curwin, FORWARD, true, true, &attr);
|
|
if (len != 0) {
|
|
word = (char *)get_cursor_pos_ptr();
|
|
curwin->w_set_curswant = true;
|
|
}
|
|
} else if (*curbuf->b_s.b_p_spl != NUL) {
|
|
const char *str = tv_get_string_chk(&argvars[0]);
|
|
int capcol = -1;
|
|
|
|
if (str != NULL) {
|
|
// Check the argument for spelling.
|
|
while (*str != NUL) {
|
|
len = spell_check(curwin, (char_u *)str, &attr, &capcol, false);
|
|
if (attr != HLF_COUNT) {
|
|
word = str;
|
|
break;
|
|
}
|
|
str += len;
|
|
capcol -= (int)len;
|
|
len = 0;
|
|
}
|
|
}
|
|
}
|
|
curwin->w_p_spell = wo_spell_save;
|
|
|
|
assert(len <= INT_MAX);
|
|
tv_list_alloc_ret(rettv, 2);
|
|
tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len);
|
|
tv_list_append_string(rettv->vval.v_list,
|
|
(attr == HLF_SPB ? "bad" :
|
|
attr == HLF_SPR ? "rare" :
|
|
attr == HLF_SPL ? "local" :
|
|
attr == HLF_SPC ? "caps" : NULL), -1);
|
|
}
|
|
|
|
/// "spellsuggest()" function
|
|
static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
garray_T ga = GA_EMPTY_INIT_VALUE;
|
|
const int wo_spell_save = curwin->w_p_spell;
|
|
|
|
if (!curwin->w_p_spell) {
|
|
did_set_spelllang(curwin);
|
|
curwin->w_p_spell = true;
|
|
}
|
|
|
|
if (*curwin->w_s->b_p_spl == NUL) {
|
|
emsg(_(e_no_spell));
|
|
curwin->w_p_spell = wo_spell_save;
|
|
return;
|
|
}
|
|
|
|
int maxcount;
|
|
bool need_capital = false;
|
|
const char *const str = tv_get_string(&argvars[0]);
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
bool typeerr = false;
|
|
maxcount = (int)tv_get_number_chk(&argvars[1], &typeerr);
|
|
if (maxcount <= 0) {
|
|
goto f_spellsuggest_return;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
need_capital = tv_get_number_chk(&argvars[2], &typeerr);
|
|
if (typeerr) {
|
|
goto f_spellsuggest_return;
|
|
}
|
|
}
|
|
} else {
|
|
maxcount = 25;
|
|
}
|
|
|
|
spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false);
|
|
|
|
f_spellsuggest_return:
|
|
tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len);
|
|
for (int i = 0; i < ga.ga_len; i++) {
|
|
char *const p = ((char **)ga.ga_data)[i];
|
|
tv_list_append_allocated_string(rettv->vval.v_list, p);
|
|
}
|
|
ga_clear(&ga);
|
|
curwin->w_p_spell = wo_spell_save;
|
|
}
|
|
|
|
static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
colnr_T col = 0;
|
|
bool keepempty = false;
|
|
bool typeerr = false;
|
|
|
|
// Make 'cpoptions' empty, the 'l' flag should not be used here.
|
|
char *save_cpo = p_cpo;
|
|
p_cpo = empty_option;
|
|
|
|
const char *str = tv_get_string(&argvars[0]);
|
|
const char *pat = NULL;
|
|
char patbuf[NUMBUFLEN];
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
pat = tv_get_string_buf_chk(&argvars[1], patbuf);
|
|
if (pat == NULL) {
|
|
typeerr = true;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr);
|
|
}
|
|
}
|
|
if (pat == NULL || *pat == NUL) {
|
|
pat = "[\\x01- ]\\+";
|
|
}
|
|
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
|
|
if (typeerr) {
|
|
goto theend;
|
|
}
|
|
|
|
regmatch_T regmatch = {
|
|
.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING),
|
|
.startp = { NULL },
|
|
.endp = { NULL },
|
|
.rm_ic = false,
|
|
};
|
|
if (regmatch.regprog != NULL) {
|
|
while (*str != NUL || keepempty) {
|
|
bool match;
|
|
if (*str == NUL) {
|
|
match = false; // Empty item at the end.
|
|
} else {
|
|
match = vim_regexec_nl(®match, (char_u *)str, col);
|
|
}
|
|
const char *end;
|
|
if (match) {
|
|
end = (const char *)regmatch.startp[0];
|
|
} else {
|
|
end = str + strlen(str);
|
|
}
|
|
if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
|
|
&& *str != NUL
|
|
&& match
|
|
&& end < (const char *)regmatch.endp[0])) {
|
|
tv_list_append_string(rettv->vval.v_list, str, end - str);
|
|
}
|
|
if (!match) {
|
|
break;
|
|
}
|
|
// Advance to just after the match.
|
|
if (regmatch.endp[0] > (char_u *)str) {
|
|
col = 0;
|
|
} else {
|
|
// Don't get stuck at the same match.
|
|
col = utfc_ptr2len((char *)regmatch.endp[0]);
|
|
}
|
|
str = (const char *)regmatch.endp[0];
|
|
}
|
|
|
|
vim_regfree(regmatch.regprog);
|
|
}
|
|
|
|
theend:
|
|
p_cpo = save_cpo;
|
|
}
|
|
|
|
/// "stdpath(type)" function
|
|
static void f_stdpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
|
|
const char *const p = tv_get_string_chk(&argvars[0]);
|
|
if (p == NULL) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
|
|
if (strequal(p, "config")) {
|
|
rettv->vval.v_string = get_xdg_home(kXDGConfigHome);
|
|
} else if (strequal(p, "data")) {
|
|
rettv->vval.v_string = get_xdg_home(kXDGDataHome);
|
|
} else if (strequal(p, "cache")) {
|
|
rettv->vval.v_string = get_xdg_home(kXDGCacheHome);
|
|
} else if (strequal(p, "state")) {
|
|
rettv->vval.v_string = get_xdg_home(kXDGStateHome);
|
|
} else if (strequal(p, "log")) {
|
|
rettv->vval.v_string = get_xdg_home(kXDGStateHome);
|
|
} else if (strequal(p, "run")) {
|
|
rettv->vval.v_string = stdpaths_get_xdg_var(kXDGRuntimeDir);
|
|
} else if (strequal(p, "config_dirs")) {
|
|
get_xdg_var_list(kXDGConfigDirs, rettv);
|
|
} else if (strequal(p, "data_dirs")) {
|
|
get_xdg_var_list(kXDGDataDirs, rettv);
|
|
} else {
|
|
semsg(_("E6100: \"%s\" is not a valid stdpath"), p);
|
|
}
|
|
}
|
|
|
|
/// "str2float()" function
|
|
static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char *p = skipwhite(tv_get_string(&argvars[0]));
|
|
bool isneg = (*p == '-');
|
|
|
|
if (*p == '+' || *p == '-') {
|
|
p = skipwhite(p + 1);
|
|
}
|
|
(void)string2float(p, &rettv->vval.v_float);
|
|
if (isneg) {
|
|
rettv->vval.v_float *= -1;
|
|
}
|
|
rettv->v_type = VAR_FLOAT;
|
|
}
|
|
|
|
/// "str2list()" function
|
|
static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenUnknown);
|
|
const char_u *p = (const char_u *)tv_get_string(&argvars[0]);
|
|
|
|
for (; *p != NUL; p += utf_ptr2len((char *)p)) {
|
|
tv_list_append_number(rettv->vval.v_list, utf_ptr2char((char *)p));
|
|
}
|
|
}
|
|
|
|
/// "str2nr()" function
|
|
static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int base = 10;
|
|
int what = 0;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
base = (int)tv_get_number(&argvars[1]);
|
|
if (base != 2 && base != 8 && base != 10 && base != 16) {
|
|
emsg(_(e_invarg));
|
|
return;
|
|
}
|
|
if (argvars[2].v_type != VAR_UNKNOWN && tv_get_number(&argvars[2])) {
|
|
what |= STR2NR_QUOTE;
|
|
}
|
|
}
|
|
|
|
char_u *p = (char_u *)skipwhite(tv_get_string(&argvars[0]));
|
|
bool isneg = (*p == '-');
|
|
if (*p == '+' || *p == '-') {
|
|
p = (char_u *)skipwhite((char *)p + 1);
|
|
}
|
|
switch (base) {
|
|
case 2:
|
|
what |= STR2NR_BIN | STR2NR_FORCE;
|
|
break;
|
|
case 8:
|
|
what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
|
|
break;
|
|
case 16:
|
|
what |= STR2NR_HEX | STR2NR_FORCE;
|
|
break;
|
|
}
|
|
varnumber_T n;
|
|
vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false);
|
|
// Text after the number is silently ignored.
|
|
if (isneg) {
|
|
rettv->vval.v_number = -n;
|
|
} else {
|
|
rettv->vval.v_number = n;
|
|
}
|
|
}
|
|
|
|
/// "strftime({format}[, {time}])" function
|
|
static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
time_t seconds;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
|
|
char *p = (char *)tv_get_string(&argvars[0]);
|
|
if (argvars[1].v_type == VAR_UNKNOWN) {
|
|
seconds = time(NULL);
|
|
} else {
|
|
seconds = (time_t)tv_get_number(&argvars[1]);
|
|
}
|
|
|
|
struct tm curtime;
|
|
struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime);
|
|
// MSVC returns NULL for an invalid value of seconds.
|
|
if (curtime_ptr == NULL) {
|
|
rettv->vval.v_string = xstrdup(_("(Invalid)"));
|
|
} else {
|
|
vimconv_T conv;
|
|
char_u *enc;
|
|
|
|
conv.vc_type = CONV_NONE;
|
|
enc = enc_locale();
|
|
convert_setup(&conv, (char_u *)p_enc, enc);
|
|
if (conv.vc_type != CONV_NONE) {
|
|
p = (char *)string_convert(&conv, (char_u *)p, NULL);
|
|
}
|
|
char result_buf[256];
|
|
if (p != NULL) {
|
|
(void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr);
|
|
} else {
|
|
result_buf[0] = NUL;
|
|
}
|
|
|
|
if (conv.vc_type != CONV_NONE) {
|
|
xfree(p);
|
|
}
|
|
convert_setup(&conv, enc, (char_u *)p_enc);
|
|
if (conv.vc_type != CONV_NONE) {
|
|
rettv->vval.v_string = (char *)string_convert(&conv, (char_u *)result_buf, NULL);
|
|
} else {
|
|
rettv->vval.v_string = xstrdup(result_buf);
|
|
}
|
|
|
|
// Release conversion descriptors.
|
|
convert_setup(&conv, NULL, NULL);
|
|
xfree(enc);
|
|
}
|
|
}
|
|
|
|
/// "strgetchar()" function
|
|
static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
const char *const str = tv_get_string_chk(&argvars[0]);
|
|
if (str == NULL) {
|
|
return;
|
|
}
|
|
bool error = false;
|
|
varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
const size_t len = STRLEN(str);
|
|
size_t byteidx = 0;
|
|
|
|
while (charidx >= 0 && byteidx < len) {
|
|
if (charidx == 0) {
|
|
rettv->vval.v_number = utf_ptr2char(str + byteidx);
|
|
break;
|
|
}
|
|
charidx--;
|
|
byteidx += (size_t)utf_ptr2len(str + byteidx);
|
|
}
|
|
}
|
|
|
|
/// "stridx()" function
|
|
static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
char buf[NUMBUFLEN];
|
|
const char *const needle = tv_get_string_chk(&argvars[1]);
|
|
const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
|
|
const char *const haystack_start = haystack;
|
|
if (needle == NULL || haystack == NULL) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
bool error = false;
|
|
|
|
const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
|
|
&error);
|
|
if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
|
|
return;
|
|
}
|
|
if (start_idx >= 0) {
|
|
haystack += start_idx;
|
|
}
|
|
}
|
|
|
|
const char *pos = strstr(haystack, needle);
|
|
if (pos != NULL) {
|
|
rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
|
|
}
|
|
}
|
|
|
|
/// "string()" function
|
|
static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
|
|
}
|
|
|
|
/// "strlen()" function
|
|
static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/// "strchars()" function
|
|
static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *s = tv_get_string(&argvars[0]);
|
|
int skipcc = 0;
|
|
varnumber_T len = 0;
|
|
int (*func_mb_ptr2char_adv)(const char_u **pp);
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
skipcc = (int)tv_get_number_chk(&argvars[1], NULL);
|
|
}
|
|
if (skipcc < 0 || skipcc > 1) {
|
|
emsg(_(e_invarg));
|
|
} else {
|
|
func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
|
|
while (*s != NUL) {
|
|
func_mb_ptr2char_adv((const char_u **)&s);
|
|
len++;
|
|
}
|
|
rettv->vval.v_number = len;
|
|
}
|
|
}
|
|
|
|
/// "strdisplaywidth()" function
|
|
static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const s = tv_get_string(&argvars[0]);
|
|
int col = 0;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
col = (int)tv_get_number(&argvars[1]);
|
|
}
|
|
|
|
rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
|
|
}
|
|
|
|
/// "strwidth()" function
|
|
static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const s = tv_get_string(&argvars[0]);
|
|
|
|
rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
|
|
}
|
|
|
|
/// "strcharpart()" function
|
|
static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const p = tv_get_string(&argvars[0]);
|
|
const size_t slen = STRLEN(p);
|
|
|
|
int nbyte = 0;
|
|
bool error = false;
|
|
varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
|
|
if (!error) {
|
|
if (nchar > 0) {
|
|
while (nchar > 0 && (size_t)nbyte < slen) {
|
|
nbyte += utf_ptr2len(p + nbyte);
|
|
nchar--;
|
|
}
|
|
} else {
|
|
nbyte = (int)nchar;
|
|
}
|
|
}
|
|
int len = 0;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
int charlen = (int)tv_get_number(&argvars[2]);
|
|
while (charlen > 0 && nbyte + len < (int)slen) {
|
|
int off = nbyte + len;
|
|
|
|
if (off < 0) {
|
|
len += 1;
|
|
} else {
|
|
len += utf_ptr2len(p + off);
|
|
}
|
|
charlen--;
|
|
}
|
|
} else {
|
|
len = (int)slen - nbyte; // default: all bytes that are available.
|
|
}
|
|
|
|
// Only return the overlap between the specified part and the actual
|
|
// string.
|
|
if (nbyte < 0) {
|
|
len += nbyte;
|
|
nbyte = 0;
|
|
} else if ((size_t)nbyte > slen) {
|
|
nbyte = (int)slen;
|
|
}
|
|
if (len < 0) {
|
|
len = 0;
|
|
} else if (nbyte + len > (int)slen) {
|
|
len = (int)slen - nbyte;
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len);
|
|
}
|
|
|
|
/// "strpart()" function
|
|
static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
bool error = false;
|
|
|
|
const char *const p = tv_get_string(&argvars[0]);
|
|
const size_t slen = strlen(p);
|
|
|
|
varnumber_T n = tv_get_number_chk(&argvars[1], &error);
|
|
varnumber_T len;
|
|
if (error) {
|
|
len = 0;
|
|
} else if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
len = tv_get_number(&argvars[2]);
|
|
} else {
|
|
len = (varnumber_T)slen - n; // Default len: all bytes that are available.
|
|
}
|
|
|
|
// Only return the overlap between the specified part and the actual
|
|
// string.
|
|
if (n < 0) {
|
|
len += n;
|
|
n = 0;
|
|
} else if (n > (varnumber_T)slen) {
|
|
n = (varnumber_T)slen;
|
|
}
|
|
if (len < 0) {
|
|
len = 0;
|
|
} else if (n + len > (varnumber_T)slen) {
|
|
len = (varnumber_T)slen - n;
|
|
}
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
|
|
int off;
|
|
|
|
// length in characters
|
|
for (off = (int)n; off < (int)slen && len > 0; len--) {
|
|
off += utfc_ptr2len(p + off);
|
|
}
|
|
len = off - n;
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
|
|
}
|
|
|
|
/// "strptime({format}, {timestring})" function
|
|
static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char fmt_buf[NUMBUFLEN];
|
|
char str_buf[NUMBUFLEN];
|
|
|
|
struct tm tmval = {
|
|
.tm_isdst = -1,
|
|
};
|
|
char *fmt = (char *)tv_get_string_buf(&argvars[0], fmt_buf);
|
|
char *str = (char *)tv_get_string_buf(&argvars[1], str_buf);
|
|
|
|
vimconv_T conv = {
|
|
.vc_type = CONV_NONE,
|
|
};
|
|
char_u *enc = enc_locale();
|
|
convert_setup(&conv, (char_u *)p_enc, enc);
|
|
if (conv.vc_type != CONV_NONE) {
|
|
fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL);
|
|
}
|
|
if (fmt == NULL
|
|
|| os_strptime(str, fmt, &tmval) == NULL
|
|
|| (rettv->vval.v_number = mktime(&tmval)) == -1) {
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
if (conv.vc_type != CONV_NONE) {
|
|
xfree(fmt);
|
|
}
|
|
convert_setup(&conv, NULL, NULL);
|
|
xfree(enc);
|
|
}
|
|
|
|
/// "strridx()" function
|
|
static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char buf[NUMBUFLEN];
|
|
const char *const needle = tv_get_string_chk(&argvars[1]);
|
|
const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
|
|
|
|
rettv->vval.v_number = -1;
|
|
if (needle == NULL || haystack == NULL) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
|
|
const size_t haystack_len = STRLEN(haystack);
|
|
ptrdiff_t end_idx;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
// Third argument: upper limit for index.
|
|
end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
|
|
if (end_idx < 0) {
|
|
return; // Can never find a match.
|
|
}
|
|
} else {
|
|
end_idx = (ptrdiff_t)haystack_len;
|
|
}
|
|
|
|
const char *lastmatch = NULL;
|
|
if (*needle == NUL) {
|
|
// Empty string matches past the end.
|
|
lastmatch = haystack + end_idx;
|
|
} else {
|
|
for (const char *rest = haystack; *rest != NUL; rest++) {
|
|
rest = strstr(rest, needle);
|
|
if (rest == NULL || rest > haystack + end_idx) {
|
|
break;
|
|
}
|
|
lastmatch = rest;
|
|
}
|
|
}
|
|
|
|
if (lastmatch != NULL) {
|
|
rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
|
|
}
|
|
}
|
|
|
|
/// "strtrans()" function
|
|
static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
|
|
}
|
|
|
|
/// "submatch()" function
|
|
static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
bool error = false;
|
|
int no = (int)tv_get_number_chk(&argvars[0], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
if (no < 0 || no >= NSUBEXP) {
|
|
semsg(_("E935: invalid submatch number: %d"), no);
|
|
return;
|
|
}
|
|
int retList = 0;
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
retList = (int)tv_get_number_chk(&argvars[1], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (retList == 0) {
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (char *)reg_submatch(no);
|
|
} else {
|
|
rettv->v_type = VAR_LIST;
|
|
rettv->vval.v_list = reg_submatch_list(no);
|
|
}
|
|
}
|
|
|
|
/// "substitute()" function
|
|
static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char patbuf[NUMBUFLEN];
|
|
char subbuf[NUMBUFLEN];
|
|
char flagsbuf[NUMBUFLEN];
|
|
|
|
const char *const str = tv_get_string_chk(&argvars[0]);
|
|
const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
|
|
const char *sub = NULL;
|
|
const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);
|
|
|
|
typval_T *expr = NULL;
|
|
if (tv_is_func(argvars[2])) {
|
|
expr = &argvars[2];
|
|
} else {
|
|
sub = tv_get_string_buf_chk(&argvars[2], subbuf);
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
|
|
|| flg == NULL) {
|
|
rettv->vval.v_string = NULL;
|
|
} else {
|
|
rettv->vval.v_string = do_string_sub((char *)str, (char *)pat,
|
|
(char *)sub, expr, (char *)flg);
|
|
}
|
|
}
|
|
|
|
/// "swapinfo(swap_filename)" function
|
|
static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
|
|
}
|
|
|
|
/// "swapname(expr)" function
|
|
static void f_swapname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
buf_T *buf = tv_get_buf(&argvars[0], false);
|
|
if (buf == NULL
|
|
|| buf->b_ml.ml_mfp == NULL
|
|
|| buf->b_ml.ml_mfp->mf_fname == NULL) {
|
|
rettv->vval.v_string = NULL;
|
|
} else {
|
|
rettv->vval.v_string = (char *)vim_strsave(buf->b_ml.ml_mfp->mf_fname);
|
|
}
|
|
}
|
|
|
|
/// "synID(lnum, col, trans)" function
|
|
static void f_synID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
// -1 on type error (both)
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
|
|
|
|
bool transerr = false;
|
|
const int trans = (int)tv_get_number_chk(&argvars[2], &transerr);
|
|
|
|
int id = 0;
|
|
if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
|
|
&& col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) {
|
|
id = syn_get_id(curwin, lnum, col, trans, NULL, false);
|
|
}
|
|
|
|
rettv->vval.v_number = id;
|
|
}
|
|
|
|
/// "synIDattr(id, what [, mode])" function
|
|
static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const int id = (int)tv_get_number(&argvars[0]);
|
|
const char *const what = tv_get_string(&argvars[1]);
|
|
int modec;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
char modebuf[NUMBUFLEN];
|
|
const char *const mode = tv_get_string_buf(&argvars[2], modebuf);
|
|
modec = TOLOWER_ASC(mode[0]);
|
|
if (modec != 'c' && modec != 'g') {
|
|
modec = 0; // Replace invalid with current.
|
|
}
|
|
} else if (ui_rgb_attached()) {
|
|
modec = 'g';
|
|
} else {
|
|
modec = 'c';
|
|
}
|
|
|
|
const char *p = NULL;
|
|
switch (TOLOWER_ASC(what[0])) {
|
|
case 'b':
|
|
if (TOLOWER_ASC(what[1]) == 'g') { // bg[#]
|
|
p = highlight_color(id, what, modec);
|
|
} else { // bold
|
|
p = highlight_has_attr(id, HL_BOLD, modec);
|
|
}
|
|
break;
|
|
case 'f': // fg[#] or font
|
|
p = highlight_color(id, what, modec);
|
|
break;
|
|
case 'i':
|
|
if (TOLOWER_ASC(what[1]) == 'n') { // inverse
|
|
p = highlight_has_attr(id, HL_INVERSE, modec);
|
|
} else { // italic
|
|
p = highlight_has_attr(id, HL_ITALIC, modec);
|
|
}
|
|
break;
|
|
case 'n':
|
|
if (TOLOWER_ASC(what[1]) == 'o') { // nocombine
|
|
p = highlight_has_attr(id, HL_NOCOMBINE, modec);
|
|
} else { // name
|
|
p = get_highlight_name_ext(NULL, id - 1, false);
|
|
}
|
|
break;
|
|
case 'r': // reverse
|
|
p = highlight_has_attr(id, HL_INVERSE, modec);
|
|
break;
|
|
case 's':
|
|
if (TOLOWER_ASC(what[1]) == 'p') { // sp[#]
|
|
p = highlight_color(id, what, modec);
|
|
} else if (TOLOWER_ASC(what[1]) == 't'
|
|
&& TOLOWER_ASC(what[2]) == 'r') { // strikethrough
|
|
p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
|
|
} else { // standout
|
|
p = highlight_has_attr(id, HL_STANDOUT, modec);
|
|
}
|
|
break;
|
|
case 'u':
|
|
if (STRLEN(what) >= 9) {
|
|
if (TOLOWER_ASC(what[5]) == 'l') {
|
|
// underline
|
|
p = highlight_has_attr(id, HL_UNDERLINE, modec);
|
|
} else if (TOLOWER_ASC(what[5]) != 'd') {
|
|
// undercurl
|
|
p = highlight_has_attr(id, HL_UNDERCURL, modec);
|
|
} else if (TOLOWER_ASC(what[6]) != 'o') {
|
|
// underdashed
|
|
p = highlight_has_attr(id, HL_UNDERDASHED, modec);
|
|
} else if (TOLOWER_ASC(what[7]) == 'u') {
|
|
// underdouble
|
|
p = highlight_has_attr(id, HL_UNDERDOUBLE, modec);
|
|
} else {
|
|
// underdotted
|
|
p = highlight_has_attr(id, HL_UNDERDOTTED, modec);
|
|
}
|
|
} else {
|
|
// ul
|
|
p = highlight_color(id, what, modec);
|
|
}
|
|
break;
|
|
}
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (char *)(p == NULL ? p : xstrdup(p));
|
|
}
|
|
|
|
/// "synIDtrans(id)" function
|
|
static void f_synIDtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int id = (int)tv_get_number(&argvars[0]);
|
|
|
|
if (id > 0) {
|
|
id = syn_get_final_id(id);
|
|
} else {
|
|
id = 0;
|
|
}
|
|
|
|
rettv->vval.v_number = id;
|
|
}
|
|
|
|
/// "synconcealed(lnum, col)" function
|
|
static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int syntax_flags = 0;
|
|
int cchar;
|
|
int matchid = 0;
|
|
char_u str[NUMBUFLEN];
|
|
|
|
tv_list_set_ret(rettv, NULL);
|
|
|
|
// -1 on type error (both)
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
|
|
|
|
CLEAR_FIELD(str);
|
|
|
|
if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0
|
|
&& (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {
|
|
(void)syn_get_id(curwin, lnum, col, false, NULL, false);
|
|
syntax_flags = get_syntax_info(&matchid);
|
|
|
|
// get the conceal character
|
|
if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) {
|
|
cchar = syn_get_sub_char();
|
|
if (cchar == NUL && curwin->w_p_cole == 1) {
|
|
cchar = (curwin->w_p_lcs_chars.conceal == NUL)
|
|
? ' '
|
|
: curwin->w_p_lcs_chars.conceal;
|
|
}
|
|
if (cchar != NUL) {
|
|
utf_char2bytes(cchar, (char *)str);
|
|
}
|
|
}
|
|
}
|
|
|
|
tv_list_alloc_ret(rettv, 3);
|
|
tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0);
|
|
// -1 to auto-determine strlen
|
|
tv_list_append_string(rettv->vval.v_list, (const char *)str, -1);
|
|
tv_list_append_number(rettv->vval.v_list, matchid);
|
|
}
|
|
|
|
/// "synstack(lnum, col)" function
|
|
static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_set_ret(rettv, NULL);
|
|
|
|
// -1 on type error (both)
|
|
const linenr_T lnum = tv_get_lnum(argvars);
|
|
const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
|
|
|
|
if (lnum >= 1
|
|
&& lnum <= curbuf->b_ml.ml_line_count
|
|
&& col >= 0
|
|
&& (size_t)col <= STRLEN(ml_get(lnum))) {
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
(void)syn_get_id(curwin, lnum, col, false, NULL, true);
|
|
|
|
int id;
|
|
int i = 0;
|
|
while ((id = syn_get_stack_item(i++)) >= 0) {
|
|
tv_list_append_number(rettv->vval.v_list, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// f_system - the VimL system() function
|
|
static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
get_system_output_as_rettv(argvars, rettv, false);
|
|
}
|
|
|
|
static void f_systemlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
get_system_output_as_rettv(argvars, rettv, true);
|
|
}
|
|
|
|
/// "tabpagebuflist()" function
|
|
static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = NULL;
|
|
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
wp = firstwin;
|
|
} else {
|
|
tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
|
|
if (tp != NULL) {
|
|
wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
|
|
}
|
|
}
|
|
if (wp != NULL) {
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
while (wp != NULL) {
|
|
tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum);
|
|
wp = wp->w_next;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "tabpagenr()" function
|
|
static void f_tabpagenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int nr = 1;
|
|
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
const char *const arg = tv_get_string_chk(&argvars[0]);
|
|
nr = 0;
|
|
if (arg != NULL) {
|
|
if (strcmp(arg, "$") == 0) {
|
|
nr = tabpage_index(NULL) - 1;
|
|
} else if (strcmp(arg, "#") == 0) {
|
|
nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0;
|
|
} else {
|
|
semsg(_(e_invexpr2), arg);
|
|
}
|
|
}
|
|
} else {
|
|
nr = tabpage_index(curtab);
|
|
}
|
|
rettv->vval.v_number = nr;
|
|
}
|
|
|
|
/// Common code for tabpagewinnr() and winnr().
|
|
static int get_winnr(tabpage_T *tp, typval_T *argvar)
|
|
{
|
|
int nr = 1;
|
|
|
|
win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin;
|
|
if (argvar->v_type != VAR_UNKNOWN) {
|
|
bool invalid_arg = false;
|
|
const char *const arg = tv_get_string_chk(argvar);
|
|
if (arg == NULL) {
|
|
nr = 0; // Type error; errmsg already given.
|
|
} else if (strcmp(arg, "$") == 0) {
|
|
twin = (tp == curtab) ? lastwin : tp->tp_lastwin;
|
|
} else if (strcmp(arg, "#") == 0) {
|
|
twin = (tp == curtab) ? prevwin : tp->tp_prevwin;
|
|
if (twin == NULL) {
|
|
nr = 0;
|
|
}
|
|
} else {
|
|
// Extract the window count (if specified). e.g. winnr('3j')
|
|
char *endp;
|
|
long count = strtol((char *)arg, &endp, 10);
|
|
if (count <= 0) {
|
|
// if count is not specified, default to 1
|
|
count = 1;
|
|
}
|
|
if (endp != NULL && *endp != '\0') {
|
|
if (strequal(endp, "j")) {
|
|
twin = win_vert_neighbor(tp, twin, false, count);
|
|
} else if (strequal(endp, "k")) {
|
|
twin = win_vert_neighbor(tp, twin, true, count);
|
|
} else if (strequal(endp, "h")) {
|
|
twin = win_horz_neighbor(tp, twin, true, count);
|
|
} else if (strequal(endp, "l")) {
|
|
twin = win_horz_neighbor(tp, twin, false, count);
|
|
} else {
|
|
invalid_arg = true;
|
|
}
|
|
} else {
|
|
invalid_arg = true;
|
|
}
|
|
}
|
|
|
|
if (invalid_arg) {
|
|
semsg(_(e_invexpr2), arg);
|
|
nr = 0;
|
|
}
|
|
}
|
|
|
|
if (nr > 0) {
|
|
for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
|
|
wp != twin; wp = wp->w_next) {
|
|
if (wp == NULL) {
|
|
// didn't find it in this tabpage
|
|
nr = 0;
|
|
break;
|
|
}
|
|
nr++;
|
|
}
|
|
}
|
|
return nr;
|
|
}
|
|
|
|
/// "tabpagewinnr()" function
|
|
static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int nr = 1;
|
|
tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
|
|
if (tp == NULL) {
|
|
nr = 0;
|
|
} else {
|
|
nr = get_winnr(tp, &argvars[1]);
|
|
}
|
|
rettv->vval.v_number = nr;
|
|
}
|
|
|
|
/// "tagfiles()" function
|
|
static void f_tagfiles(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenUnknown);
|
|
char *fname = xmalloc(MAXPATHL);
|
|
|
|
bool first = true;
|
|
tagname_T tn;
|
|
while (get_tagfname(&tn, first, (char_u *)fname) == OK) {
|
|
tv_list_append_string(rettv->vval.v_list, fname, -1);
|
|
first = false;
|
|
}
|
|
|
|
tagname_free(&tn);
|
|
xfree(fname);
|
|
}
|
|
|
|
/// "taglist()" function
|
|
static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const tag_pattern = tv_get_string(&argvars[0]);
|
|
|
|
rettv->vval.v_number = false;
|
|
if (*tag_pattern == NUL) {
|
|
return;
|
|
}
|
|
|
|
const char *fname = NULL;
|
|
if (argvars[1].v_type != VAR_UNKNOWN) {
|
|
fname = tv_get_string(&argvars[1]);
|
|
}
|
|
(void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown),
|
|
(char_u *)tag_pattern, (char_u *)fname);
|
|
}
|
|
|
|
/// "tempname()" function
|
|
static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = (char *)vim_tempname();
|
|
}
|
|
|
|
/// "termopen(cmd[, cwd])" function
|
|
static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (curbuf->b_changed) {
|
|
emsg(_("Can only call this function in an unmodified buffer"));
|
|
return;
|
|
}
|
|
|
|
const char *cmd;
|
|
bool executable = true;
|
|
char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
|
|
if (!argv) {
|
|
rettv->vval.v_number = executable ? 0 : -1;
|
|
return; // Did error message in tv_to_argv.
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
|
|
// Wrong argument type
|
|
semsg(_(e_invarg2), "expected dictionary");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
|
|
CallbackReader on_stdout = CALLBACK_READER_INIT,
|
|
on_stderr = CALLBACK_READER_INIT;
|
|
Callback on_exit = CALLBACK_NONE;
|
|
dict_T *job_opts = NULL;
|
|
const char *cwd = ".";
|
|
dict_T *env = NULL;
|
|
const bool pty = true;
|
|
bool clear_env = false;
|
|
dictitem_T *job_env = NULL;
|
|
|
|
if (argvars[1].v_type == VAR_DICT) {
|
|
job_opts = argvars[1].vval.v_dict;
|
|
|
|
const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false);
|
|
if (new_cwd && *new_cwd != NUL) {
|
|
cwd = new_cwd;
|
|
// The new cwd must be a directory.
|
|
if (!os_isdir((const char_u *)cwd)) {
|
|
semsg(_(e_invarg2), "expected valid directory");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
job_env = tv_dict_find(job_opts, S_LEN("env"));
|
|
if (job_env && job_env->di_tv.v_type != VAR_DICT) {
|
|
semsg(_(e_invarg2), "env");
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
|
|
clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
|
|
|
|
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
|
|
shell_free_argv(argv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
env = create_environment(job_env, clear_env, pty, "xterm-256color");
|
|
|
|
const bool rpc = false;
|
|
const bool overlapped = false;
|
|
const bool detach = false;
|
|
ChannelStdinMode stdin_mode = kChannelStdinPipe;
|
|
uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin));
|
|
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
|
|
pty, rpc, overlapped, detach, stdin_mode,
|
|
cwd, term_width, (uint16_t)curwin->w_height_inner,
|
|
env, &rettv->vval.v_number);
|
|
if (rettv->vval.v_number <= 0) {
|
|
return;
|
|
}
|
|
|
|
int pid = chan->stream.pty.process.pid;
|
|
|
|
// "./…" => "/home/foo/…"
|
|
vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false);
|
|
// "/home/foo/…" => "~/…"
|
|
size_t len = home_replace(NULL, (char *)NameBuff, (char *)IObuff, sizeof(IObuff), true);
|
|
// Trim slash.
|
|
if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) {
|
|
IObuff[len - 1] = '\0';
|
|
}
|
|
|
|
if (len == 1 && IObuff[0] == '/') {
|
|
// Avoid ambiguity in the URI when CWD is root directory.
|
|
IObuff[1] = '.';
|
|
IObuff[2] = '\0';
|
|
}
|
|
|
|
// Terminal URI: "term://$CWD//$PID:$CMD"
|
|
snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s",
|
|
(char *)IObuff, pid, cmd);
|
|
// at this point the buffer has no terminal instance associated yet, so unset
|
|
// the 'swapfile' option to ensure no swap file will be created
|
|
curbuf->b_p_swf = false;
|
|
|
|
apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
|
|
(void)setfname(curbuf, (char *)NameBuff, NULL, true);
|
|
apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
|
|
|
|
// Save the job id and pid in b:terminal_job_{id,pid}
|
|
Error err = ERROR_INIT;
|
|
// deprecated: use 'channel' buffer option
|
|
dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
|
|
INTEGER_OBJ((Integer)chan->id), false, false, &err);
|
|
api_clear_error(&err);
|
|
dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
|
|
INTEGER_OBJ(pid), false, false, &err);
|
|
api_clear_error(&err);
|
|
|
|
channel_terminal_open(curbuf, chan);
|
|
channel_create_event(chan, NULL);
|
|
}
|
|
|
|
/// "timer_info([timer])" function
|
|
static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
emsg(_(e_number_exp));
|
|
return;
|
|
}
|
|
tv_list_alloc_ret(rettv, 1);
|
|
timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
|
|
if (timer != NULL && !timer->stopped) {
|
|
add_timer_info(rettv, timer);
|
|
}
|
|
} else {
|
|
add_timer_info_all(rettv);
|
|
}
|
|
}
|
|
|
|
/// "timer_pause(timer, paused)" function
|
|
static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
emsg(_(e_number_exp));
|
|
return;
|
|
}
|
|
int paused = (bool)tv_get_number(&argvars[1]);
|
|
timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
|
|
if (timer != NULL) {
|
|
if (!timer->paused && paused) {
|
|
time_watcher_stop(&timer->tw);
|
|
} else if (timer->paused && !paused) {
|
|
time_watcher_start(&timer->tw, timer_due_cb, (uint64_t)timer->timeout,
|
|
(uint64_t)timer->timeout);
|
|
}
|
|
timer->paused = paused;
|
|
}
|
|
}
|
|
|
|
/// "timer_start(timeout, callback, opts)" function
|
|
static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int repeat = 1;
|
|
|
|
rettv->vval.v_number = -1;
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
dict_T *dict = argvars[2].vval.v_dict;
|
|
if (argvars[2].v_type != VAR_DICT || dict == NULL) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[2]));
|
|
return;
|
|
}
|
|
dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
|
|
if (di != NULL) {
|
|
repeat = (int)tv_get_number(&di->di_tv);
|
|
if (repeat == 0) {
|
|
repeat = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Callback callback;
|
|
if (!callback_from_typval(&callback, &argvars[1])) {
|
|
return;
|
|
}
|
|
rettv->vval.v_number = (varnumber_T)timer_start(tv_get_number(&argvars[0]), repeat, &callback);
|
|
}
|
|
|
|
/// "timer_stop(timerid)" function
|
|
static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (argvars[0].v_type != VAR_NUMBER) {
|
|
emsg(_(e_number_exp));
|
|
return;
|
|
}
|
|
|
|
timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
|
|
if (timer == NULL) {
|
|
return;
|
|
}
|
|
|
|
timer_stop(timer);
|
|
}
|
|
|
|
static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fptr)
|
|
{
|
|
timer_stop_all();
|
|
}
|
|
|
|
/// "tolower(string)" function
|
|
static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
|
|
}
|
|
|
|
/// "toupper(string)" function
|
|
static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
|
|
}
|
|
|
|
/// "tr(string, fromstr, tostr)" function
|
|
static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char buf[NUMBUFLEN];
|
|
char buf2[NUMBUFLEN];
|
|
|
|
const char *in_str = tv_get_string(&argvars[0]);
|
|
const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
|
|
const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
|
|
|
|
// Default return value: empty string.
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (fromstr == NULL || tostr == NULL) {
|
|
return; // Type error; errmsg already given.
|
|
}
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char), 80);
|
|
|
|
// fromstr and tostr have to contain the same number of chars.
|
|
bool first = true;
|
|
while (*in_str != NUL) {
|
|
const char *cpstr = in_str;
|
|
const int inlen = utfc_ptr2len(in_str);
|
|
int cplen = inlen;
|
|
int idx = 0;
|
|
int fromlen;
|
|
for (const char *p = fromstr; *p != NUL; p += fromlen) {
|
|
fromlen = utfc_ptr2len(p);
|
|
if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) {
|
|
int tolen;
|
|
for (p = tostr; *p != NUL; p += tolen) {
|
|
tolen = utfc_ptr2len(p);
|
|
if (idx-- == 0) {
|
|
cplen = tolen;
|
|
cpstr = (char *)p;
|
|
break;
|
|
}
|
|
}
|
|
if (*p == NUL) { // tostr is shorter than fromstr.
|
|
goto error;
|
|
}
|
|
break;
|
|
}
|
|
idx++;
|
|
}
|
|
|
|
if (first && cpstr == in_str) {
|
|
// Check that fromstr and tostr have the same number of
|
|
// (multi-byte) characters. Done only once when a character
|
|
// of in_str doesn't appear in fromstr.
|
|
first = false;
|
|
int tolen;
|
|
for (const char *p = tostr; *p != NUL; p += tolen) {
|
|
tolen = utfc_ptr2len(p);
|
|
idx--;
|
|
}
|
|
if (idx != 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ga_grow(&ga, cplen);
|
|
memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
|
|
ga.ga_len += cplen;
|
|
|
|
in_str += inlen;
|
|
}
|
|
|
|
// add a terminating NUL
|
|
ga_append(&ga, NUL);
|
|
|
|
rettv->vval.v_string = ga.ga_data;
|
|
return;
|
|
error:
|
|
semsg(_(e_invarg2), fromstr);
|
|
ga_clear(&ga);
|
|
}
|
|
|
|
/// "trim({expr})" function
|
|
static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char buf1[NUMBUFLEN];
|
|
char buf2[NUMBUFLEN];
|
|
const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
|
|
const char_u *mask = NULL;
|
|
const char_u *prev;
|
|
const char_u *p;
|
|
int dir = 0;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (head == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[1]));
|
|
return;
|
|
}
|
|
|
|
if (argvars[1].v_type == VAR_STRING) {
|
|
mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
bool error = false;
|
|
// leading or trailing characters to trim
|
|
dir = (int)tv_get_number_chk(&argvars[2], &error);
|
|
if (error) {
|
|
return;
|
|
}
|
|
if (dir < 0 || dir > 2) {
|
|
semsg(_(e_invarg2), tv_get_string(&argvars[2]));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int c1;
|
|
if (dir == 0 || dir == 1) {
|
|
// Trim leading characters
|
|
while (*head != NUL) {
|
|
c1 = utf_ptr2char((char *)head);
|
|
if (mask == NULL) {
|
|
if (c1 > ' ' && c1 != 0xa0) {
|
|
break;
|
|
}
|
|
} else {
|
|
for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
|
|
if (c1 == utf_ptr2char((char *)p)) {
|
|
break;
|
|
}
|
|
}
|
|
if (*p == NUL) {
|
|
break;
|
|
}
|
|
}
|
|
MB_PTR_ADV(head);
|
|
}
|
|
}
|
|
|
|
const char_u *tail = head + STRLEN(head);
|
|
if (dir == 0 || dir == 2) {
|
|
// Trim trailing characters
|
|
for (; tail > head; tail = prev) {
|
|
prev = tail;
|
|
MB_PTR_BACK(head, prev);
|
|
c1 = utf_ptr2char((char *)prev);
|
|
if (mask == NULL) {
|
|
if (c1 > ' ' && c1 != 0xa0) {
|
|
break;
|
|
}
|
|
} else {
|
|
for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
|
|
if (c1 == utf_ptr2char((char *)p)) {
|
|
break;
|
|
}
|
|
}
|
|
if (*p == NUL) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rettv->vval.v_string = (char *)vim_strnsave(head, (size_t)(tail - head));
|
|
}
|
|
|
|
/// "type(expr)" function
|
|
static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
int n = -1;
|
|
|
|
switch (argvars[0].v_type) {
|
|
case VAR_NUMBER:
|
|
n = VAR_TYPE_NUMBER; break;
|
|
case VAR_STRING:
|
|
n = VAR_TYPE_STRING; break;
|
|
case VAR_PARTIAL:
|
|
case VAR_FUNC:
|
|
n = VAR_TYPE_FUNC; break;
|
|
case VAR_LIST:
|
|
n = VAR_TYPE_LIST; break;
|
|
case VAR_DICT:
|
|
n = VAR_TYPE_DICT; break;
|
|
case VAR_FLOAT:
|
|
n = VAR_TYPE_FLOAT; break;
|
|
case VAR_BOOL:
|
|
n = VAR_TYPE_BOOL; break;
|
|
case VAR_SPECIAL:
|
|
n = VAR_TYPE_SPECIAL; break;
|
|
case VAR_BLOB:
|
|
n = VAR_TYPE_BLOB; break;
|
|
case VAR_UNKNOWN:
|
|
internal_error("f_type(UNKNOWN)");
|
|
break;
|
|
}
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
/// "undofile(name)" function
|
|
static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
const char *const fname = tv_get_string(&argvars[0]);
|
|
|
|
if (*fname == NUL) {
|
|
// If there is no file name there will be no undo file.
|
|
rettv->vval.v_string = NULL;
|
|
} else {
|
|
char *ffname = FullName_save(fname, true);
|
|
|
|
if (ffname != NULL) {
|
|
rettv->vval.v_string = u_get_undo_file_name(ffname, false);
|
|
}
|
|
xfree(ffname);
|
|
}
|
|
}
|
|
|
|
/// "undotree()" function
|
|
static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
|
|
dict_T *dict = rettv->vval.v_dict;
|
|
|
|
tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
|
|
tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
|
|
tv_dict_add_nr(dict, S_LEN("save_last"),
|
|
(varnumber_T)curbuf->b_u_save_nr_last);
|
|
tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
|
|
tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
|
|
tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
|
|
|
|
tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
|
|
}
|
|
|
|
/// "virtcol(string)" function
|
|
static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
colnr_T vcol = 0;
|
|
int fnum = curbuf->b_fnum;
|
|
|
|
pos_T *fp = var2fpos(&argvars[0], false, &fnum, false);
|
|
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
|
|
&& fnum == curbuf->b_fnum) {
|
|
// Limit the column to a valid value, getvvcol() doesn't check.
|
|
if (fp->col < 0) {
|
|
fp->col = 0;
|
|
} else {
|
|
const size_t len = STRLEN(ml_get(fp->lnum));
|
|
if (fp->col > (colnr_T)len) {
|
|
fp->col = (colnr_T)len;
|
|
}
|
|
}
|
|
getvvcol(curwin, fp, NULL, NULL, &vcol);
|
|
vcol++;
|
|
}
|
|
|
|
rettv->vval.v_number = vcol;
|
|
}
|
|
|
|
/// "visualmode()" function
|
|
static void f_visualmode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char_u str[2];
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
str[0] = (char_u)curbuf->b_visual_mode_eval;
|
|
str[1] = NUL;
|
|
rettv->vval.v_string = (char *)vim_strsave(str);
|
|
|
|
// A non-zero number or non-empty string argument: reset mode.
|
|
if (non_zero_arg(&argvars[0])) {
|
|
curbuf->b_visual_mode_eval = NUL;
|
|
}
|
|
}
|
|
|
|
/// "wildmenumode()" function
|
|
static void f_wildmenumode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
if (wild_menu_showing || ((State & MODE_CMDLINE) && cmdline_pum_active())) {
|
|
rettv->vval.v_number = 1;
|
|
}
|
|
}
|
|
|
|
/// "win_findbuf()" function
|
|
static void f_win_findbuf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
|
win_findbuf(argvars, rettv->vval.v_list);
|
|
}
|
|
|
|
/// "win_getid()" function
|
|
static void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = win_getid(argvars);
|
|
}
|
|
|
|
/// "win_gettype(nr)" function
|
|
static void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = curwin;
|
|
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
if (argvars[0].v_type != VAR_UNKNOWN) {
|
|
wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
rettv->vval.v_string = (char *)vim_strsave((char_u *)"unknown");
|
|
return;
|
|
}
|
|
}
|
|
if (wp == aucmd_win) {
|
|
rettv->vval.v_string = xstrdup("autocmd");
|
|
} else if (wp->w_p_pvw) {
|
|
rettv->vval.v_string = xstrdup("preview");
|
|
} else if (wp->w_floating) {
|
|
rettv->vval.v_string = xstrdup("popup");
|
|
} else if (wp == curwin && cmdwin_type != 0) {
|
|
rettv->vval.v_string = xstrdup("command");
|
|
} else if (bt_quickfix(wp->w_buffer)) {
|
|
rettv->vval.v_string = xstrdup((wp->w_llist_ref != NULL ? "loclist" : "quickfix"));
|
|
}
|
|
}
|
|
|
|
/// "win_gotoid()" function
|
|
static void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = win_gotoid(argvars);
|
|
}
|
|
|
|
/// "win_id2tabwin()" function
|
|
static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_id2tabwin(argvars, rettv);
|
|
}
|
|
|
|
/// "win_id2win()" function
|
|
static void f_win_id2win(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = win_id2win(argvars);
|
|
}
|
|
|
|
/// "win_move_separator()" function
|
|
static void f_win_move_separator(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = false;
|
|
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL || wp->w_floating) {
|
|
return;
|
|
}
|
|
|
|
int offset = (int)tv_get_number(&argvars[1]);
|
|
win_drag_vsep_line(wp, offset);
|
|
rettv->vval.v_number = true;
|
|
}
|
|
|
|
/// "win_move_statusline()" function
|
|
static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp;
|
|
int offset;
|
|
|
|
rettv->vval.v_number = false;
|
|
|
|
wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL || wp->w_floating) {
|
|
return;
|
|
}
|
|
|
|
offset = (int)tv_get_number(&argvars[1]);
|
|
win_drag_status_line(wp, offset);
|
|
rettv->vval.v_number = true;
|
|
}
|
|
|
|
/// "winbufnr(nr)" function
|
|
static void f_winbufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
rettv->vval.v_number = wp->w_buffer->b_fnum;
|
|
}
|
|
}
|
|
|
|
/// "wincol()" function
|
|
static void f_wincol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
validate_cursor();
|
|
rettv->vval.v_number = curwin->w_wcol + 1;
|
|
}
|
|
|
|
/// "winheight(nr)" function
|
|
static void f_winheight(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
rettv->vval.v_number = wp->w_height_inner;
|
|
}
|
|
}
|
|
|
|
/// "winlayout()" function
|
|
static void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tabpage_T *tp;
|
|
|
|
tv_list_alloc_ret(rettv, 2);
|
|
|
|
if (argvars[0].v_type == VAR_UNKNOWN) {
|
|
tp = curtab;
|
|
} else {
|
|
tp = find_tabpage((int)tv_get_number(&argvars[0]));
|
|
if (tp == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
get_framelayout(tp->tp_topframe, rettv->vval.v_list, true);
|
|
}
|
|
|
|
/// "winline()" function
|
|
static void f_winline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
validate_cursor();
|
|
rettv->vval.v_number = curwin->w_wrow + 1;
|
|
}
|
|
|
|
/// "winnr()" function
|
|
static void f_winnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = get_winnr(curtab, &argvars[0]);
|
|
}
|
|
|
|
/// "winrestcmd()" function
|
|
static void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
char_u buf[50];
|
|
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char), 70);
|
|
|
|
// Do this twice to handle some window layouts properly.
|
|
for (int i = 0; i < 2; i++) {
|
|
int winnr = 1;
|
|
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
|
snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr,
|
|
wp->w_height);
|
|
ga_concat(&ga, (char *)buf);
|
|
snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr,
|
|
wp->w_width);
|
|
ga_concat(&ga, (char *)buf);
|
|
winnr++;
|
|
}
|
|
}
|
|
ga_append(&ga, NUL);
|
|
|
|
rettv->vval.v_string = ga.ga_data;
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "winrestview()" function
|
|
static void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
dict_T *dict = argvars[0].vval.v_dict;
|
|
|
|
if (argvars[0].v_type != VAR_DICT || dict == NULL) {
|
|
emsg(_(e_invarg));
|
|
} else {
|
|
dictitem_T *di;
|
|
if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
|
|
curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
|
|
curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
|
|
curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
|
|
curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
|
|
curwin->w_set_curswant = false;
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
|
|
set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
|
|
curwin->w_topfill = (int)tv_get_number(&di->di_tv);
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
|
|
curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
|
|
}
|
|
if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
|
|
curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
|
|
}
|
|
|
|
check_cursor();
|
|
win_new_height(curwin, curwin->w_height);
|
|
win_new_width(curwin, curwin->w_width);
|
|
changed_window_setting();
|
|
|
|
if (curwin->w_topline <= 0) {
|
|
curwin->w_topline = 1;
|
|
}
|
|
if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
|
|
curwin->w_topline = curbuf->b_ml.ml_line_count;
|
|
}
|
|
check_topfill(curwin, true);
|
|
}
|
|
}
|
|
|
|
/// "winsaveview()" function
|
|
static void f_winsaveview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
dict_T *dict = rettv->vval.v_dict;
|
|
|
|
tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum);
|
|
tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col);
|
|
tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd);
|
|
update_curswant();
|
|
tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant);
|
|
|
|
tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline);
|
|
tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill);
|
|
tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol);
|
|
tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);
|
|
}
|
|
|
|
/// "winwidth(nr)" function
|
|
static void f_winwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
win_T *wp = find_win_by_nr_or_id(&argvars[0]);
|
|
if (wp == NULL) {
|
|
rettv->vval.v_number = -1;
|
|
} else {
|
|
rettv->vval.v_number = wp->w_width_inner;
|
|
}
|
|
}
|
|
|
|
/// "windowsversion()" function
|
|
static void f_windowsversion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xstrdup(windowsVersion);
|
|
}
|
|
|
|
/// "wordcount()" function
|
|
static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
tv_dict_alloc_ret(rettv);
|
|
cursor_pos_info(rettv->vval.v_dict);
|
|
}
|
|
|
|
/// "writefile()" function
|
|
static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
if (argvars[0].v_type == VAR_LIST) {
|
|
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
|
|
if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
|
|
return;
|
|
}
|
|
});
|
|
} else if (argvars[0].v_type != VAR_BLOB) {
|
|
semsg(_(e_invarg2),
|
|
_("writefile() first argument must be a List or a Blob"));
|
|
return;
|
|
}
|
|
|
|
bool binary = false;
|
|
bool append = false;
|
|
bool do_fsync = !!p_fs;
|
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
|
const char *const flags = tv_get_string_chk(&argvars[2]);
|
|
if (flags == NULL) {
|
|
return;
|
|
}
|
|
for (const char *p = flags; *p; p++) {
|
|
switch (*p) {
|
|
case 'b':
|
|
binary = true; break;
|
|
case 'a':
|
|
append = true; break;
|
|
case 's':
|
|
do_fsync = true; break;
|
|
case 'S':
|
|
do_fsync = false; break;
|
|
default:
|
|
// Using %s, p and not %c, *p to preserve multibyte characters
|
|
semsg(_("E5060: Unknown flag: %s"), p);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
char buf[NUMBUFLEN];
|
|
const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
|
|
if (fname == NULL) {
|
|
return;
|
|
}
|
|
FileDescriptor fp;
|
|
int error;
|
|
if (*fname == NUL) {
|
|
emsg(_("E482: Can't open file with an empty name"));
|
|
} else if ((error = file_open(&fp, fname,
|
|
((append ? kFileAppend : kFileTruncate)
|
|
| kFileCreate), 0666)) != 0) {
|
|
semsg(_("E482: Can't open file %s for writing: %s"),
|
|
fname, os_strerror(error));
|
|
} else {
|
|
bool write_ok;
|
|
if (argvars[0].v_type == VAR_BLOB) {
|
|
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
|
|
} else {
|
|
write_ok = write_list(&fp, argvars[0].vval.v_list, binary);
|
|
}
|
|
if (write_ok) {
|
|
rettv->vval.v_number = 0;
|
|
}
|
|
if ((error = file_close(&fp, do_fsync)) != 0) {
|
|
semsg(_("E80: Error when closing file %s: %s"),
|
|
fname, os_strerror(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// "xor(expr, expr)" function
|
|
static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
|
|
^ tv_get_number_chk(&argvars[1], NULL);
|
|
}
|