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

In non-multigrid UI the only change is that the returned height now excludes winbar, and this is compatible with Vim. In multigrid UI this means the return value of these functions now reflect the space available for buffer lines in a window. No change in nvim_win_get_height() and nvim_win_get_width().
10014 lines
269 KiB
C
10014 lines
269 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, FunPtr fptr)
|
|
{
|
|
float_T f;
|
|
float_T (*function)(float_T) = (float_T (*)(float_T)) fptr;
|
|
|
|
rettv->v_type = VAR_FLOAT;
|
|
if (tv_get_float_chk(argvars, &f)) {
|
|
rettv->vval.v_float = function(f);
|
|
} else {
|
|
rettv->vval.v_float = 0.0;
|
|
}
|
|
}
|
|
|
|
static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
|
|
ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;
|
|
|
|
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;
|
|
Object result = fn(VIML_INTERNAL_CALL, args, &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);
|
|
api_free_object(result);
|
|
api_clear_error(&err);
|
|
}
|
|
|
|
/// "abs(expr)" function
|
|
static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
if (argvars[0].v_type == VAR_FLOAT) {
|
|
float_op_wrapper(argvars, rettv, (FunPtr)&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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
Dictionary metadata = api_metadata();
|
|
(void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL);
|
|
api_free_dictionary(metadata);
|
|
}
|
|
|
|
/// "append(lnum, string/list)" function
|
|
static void f_append(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
f_browse(argvars, rettv, NULL);
|
|
}
|
|
|
|
/// 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
|
|
}
|
|
|
|
/// "buflisted(expr)" function
|
|
static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
buf_win_common(argvars, rettv, false);
|
|
}
|
|
|
|
/// "bufwinnr(nr)" function
|
|
static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr 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 = (char *)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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
byteidx(argvars, rettv, false);
|
|
}
|
|
|
|
/// "byteidxcomp()" function
|
|
static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
byteidx(argvars, rettv, true);
|
|
}
|
|
|
|
/// "call(func, arglist [, dict])" function
|
|
static void f_call(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = curbuf->b_u_seq_cur;
|
|
}
|
|
|
|
/// "chanclose(id[, stream])" function
|
|
static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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_u *p = 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((char *)p))] == NUL) {
|
|
col += l;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rettv->vval.v_number = col;
|
|
}
|
|
|
|
/// "charcol()" function
|
|
static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
get_col(argvars, rettv, true);
|
|
}
|
|
|
|
/// "charidx()" function
|
|
static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
get_col(argvars, rettv, false);
|
|
}
|
|
|
|
/// "confirm(message, buttons[, default [, type]])" function
|
|
static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr fptr)
|
|
{
|
|
var_item_copy(NULL, &argvars[0], rettv, false, 0);
|
|
}
|
|
|
|
/// "count()" function
|
|
static void f_count(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
if (!ctx_restore(NULL, kCtxAll)) {
|
|
emsg(_("Context stack is empty"));
|
|
}
|
|
}
|
|
|
|
/// "ctxpush([{types}])" function
|
|
static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
set_cursorpos(argvars, rettv, false);
|
|
}
|
|
|
|
/// "debugbreak()" function
|
|
static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = did_filetype;
|
|
}
|
|
|
|
/// "diff_filler()" function
|
|
static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = vgetc_busy;
|
|
}
|
|
|
|
/// "executable()" function
|
|
static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr fptr, 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, FunPtr fptr)
|
|
{
|
|
execute_common(argvars, rettv, fptr, 0);
|
|
}
|
|
|
|
/// "win_execute(win_id, command)" function
|
|
static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr 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, fptr, 1));
|
|
}
|
|
}
|
|
|
|
/// "exepath()" function
|
|
static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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 : 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 *)""
|
|
: 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, FunPtr fptr)
|
|
{
|
|
filter_map(argvars, rettv, false);
|
|
}
|
|
|
|
/// "finddir({fname}[, {path}[, {count}]])" function
|
|
static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
findfilendir(argvars, rettv, FINDFILE_DIR);
|
|
}
|
|
|
|
/// "findfile({fname}[, {path}[, {count}]])" function
|
|
static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
findfilendir(argvars, rettv, FINDFILE_FILE);
|
|
}
|
|
|
|
/// "float2nr({float})" function
|
|
static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{}
|
|
|
|
static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
common_function(argvars, rettv, true, fptr);
|
|
}
|
|
|
|
static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
common_function(argvars, rettv, false, fptr);
|
|
}
|
|
|
|
/// "garbagecollect()" function
|
|
static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
getpos_both(argvars, rettv, false, true);
|
|
}
|
|
|
|
/// "getcharsearch()" function
|
|
static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = get_cmdline_pos() + 1;
|
|
}
|
|
|
|
/// "getcmdscreenpos()" function
|
|
static void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = get_cmdline_screen_pos() + 1;
|
|
}
|
|
|
|
/// "getcmdtype()" function
|
|
static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = NULL;
|
|
}
|
|
|
|
/// "getfperm({fname})" function
|
|
static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = os_get_pid();
|
|
}
|
|
|
|
/// "getcurpos(string)" function
|
|
static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
getpos_both(argvars, rettv, true, false);
|
|
}
|
|
|
|
static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
getpos_both(argvars, rettv, true, true);
|
|
}
|
|
|
|
/// "getpos(string)" function
|
|
static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
|
|
/// "getwinposy()" function
|
|
static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = -1;
|
|
}
|
|
|
|
/// "glob()" function
|
|
static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0]));
|
|
}
|
|
|
|
/// "hostname()" function
|
|
static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
get_user_input(argvars, rettv, false, inputsecret_flag);
|
|
}
|
|
|
|
/// "inputdialog()" function
|
|
static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
get_user_input(argvars, rettv, true, inputsecret_flag);
|
|
}
|
|
|
|
/// "inputlist()" function
|
|
static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
cmdline_star++;
|
|
inputsecret_flag = true;
|
|
f_input(argvars, rettv, NULL);
|
|
cmdline_star--;
|
|
inputsecret_flag = false;
|
|
}
|
|
|
|
/// "insert()" function
|
|
static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr 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,
|
|
FunPtr fptr FUNC_ATTR_UNUSED)
|
|
{
|
|
got_int = true;
|
|
}
|
|
|
|
/// "invert(expr)" function
|
|
static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
|
|
}
|
|
|
|
/// "isdirectory()" function
|
|
static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
libcall_common(argvars, rettv, VAR_STRING);
|
|
}
|
|
|
|
/// "libcallnr()" function
|
|
static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
libcall_common(argvars, rettv, VAR_NUMBER);
|
|
}
|
|
|
|
/// "line(string, [winid])" function
|
|
static void f_line(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = (varnumber_T)time(NULL);
|
|
}
|
|
|
|
/// luaeval() function implementation
|
|
static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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 = (char *)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, FunPtr fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatch);
|
|
}
|
|
|
|
/// "matchend()" function
|
|
static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchEnd);
|
|
}
|
|
|
|
/// "matchlist()" function
|
|
static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchList);
|
|
}
|
|
|
|
/// "matchstr()" function
|
|
static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
find_some_match(argvars, rettv, kSomeMatchStr);
|
|
}
|
|
|
|
/// "matchstrpos()" function
|
|
static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr fptr)
|
|
{
|
|
max_min(argvars, rettv, true);
|
|
}
|
|
|
|
/// "min()" function
|
|
static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
max_min(argvars, rettv, false);
|
|
}
|
|
|
|
/// "mkdir()" function
|
|
static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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;
|
|
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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
script_host_eval("perl", argvars, rettv);
|
|
}
|
|
|
|
/// "rubyeval()" function
|
|
static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
script_host_eval("ruby", argvars, rettv);
|
|
}
|
|
|
|
/// "range()" function
|
|
static void f_range(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
return_register(reg_executing, rettv);
|
|
}
|
|
|
|
/// "reg_recording()" function
|
|
static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
return_register(reg_recording, rettv);
|
|
}
|
|
|
|
static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
return_register(reg_recorded, rettv);
|
|
}
|
|
|
|
/// list2proftime - convert a List to proftime_T
|
|
///
|
|
/// @param arg The input list, must be of type VAR_LIST and have
|
|
/// 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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 = (char_u *)(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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, NULL);
|
|
api_clear_error(&err);
|
|
}
|
|
|
|
/// "rpcstart()" function (DEPRECATED)
|
|
static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, NULL);
|
|
} 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = ui_current_col() + 1;
|
|
}
|
|
|
|
/// "screenpos({winid}, {lnum}, {col})" function
|
|
static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = ui_current_row() + 1;
|
|
}
|
|
|
|
/// "screenstring()" function
|
|
static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = searchpair_cmn(argvars, NULL);
|
|
}
|
|
|
|
/// "searchpairpos()" function
|
|
static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr 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 = (char *)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 ((char_u *)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((char_u *)save_cpo);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/// "searchpos()" function
|
|
static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
set_position(argvars, rettv, true);
|
|
}
|
|
|
|
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
set_cursorpos(argvars, rettv, true);
|
|
}
|
|
|
|
/// "setenv()" function
|
|
static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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 = (char *)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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, 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, 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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_u *)s) - col);
|
|
}
|
|
|
|
/// "strwidth()" function
|
|
static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
get_system_output_as_rettv(argvars, rettv, false);
|
|
}
|
|
|
|
static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
get_system_output_as_rettv(argvars, rettv, true);
|
|
}
|
|
|
|
/// "tabpagebuflist()" function
|
|
static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
timer_stop_all();
|
|
}
|
|
|
|
/// "tolower(string)" function
|
|
static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = win_getid(argvars);
|
|
}
|
|
|
|
/// "win_gettype(nr)" function
|
|
static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = win_gotoid(argvars);
|
|
}
|
|
|
|
/// "win_id2tabwin()" function
|
|
static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
win_id2tabwin(argvars, rettv);
|
|
}
|
|
|
|
/// "win_id2win()" function
|
|
static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = win_id2win(argvars);
|
|
}
|
|
|
|
/// "win_move_separator()" function
|
|
static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
validate_cursor();
|
|
rettv->vval.v_number = curwin->w_wcol + 1;
|
|
}
|
|
|
|
/// "winheight(nr)" function
|
|
static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
validate_cursor();
|
|
rettv->vval.v_number = curwin->w_wrow + 1;
|
|
}
|
|
|
|
/// "winnr()" function
|
|
static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = get_winnr(curtab, &argvars[0]);
|
|
}
|
|
|
|
/// "winrestcmd()" function
|
|
static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->v_type = VAR_STRING;
|
|
rettv->vval.v_string = xstrdup(windowsVersion);
|
|
}
|
|
|
|
/// "wordcount()" function
|
|
static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr 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, FunPtr 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, FunPtr fptr)
|
|
{
|
|
rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
|
|
^ tv_get_number_chk(&argvars[1], NULL);
|
|
}
|