mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 20:55:18 -07:00
Merge #10619 'API: context'
This commit is contained in:
commit
8e6b0a73c9
@ -9110,9 +9110,9 @@ functions.
|
||||
*:fu* *:function* *E128* *E129* *E123*
|
||||
:fu[nction] List all functions and their arguments.
|
||||
|
||||
:fu[nction] {name} List function {name}.
|
||||
{name} can also be a |Dictionary| entry that is a
|
||||
|Funcref|: >
|
||||
:fu[nction][!] {name} List function {name}, annotated with line numbers
|
||||
unless "!" is given.
|
||||
{name} may be a |Dictionary| |Funcref| entry: >
|
||||
:function dict.init
|
||||
|
||||
:fu[nction] /{pattern} List functions with a name matching {pattern}.
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "nvim/lua/executor.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/context.h"
|
||||
#include "nvim/file_search.h"
|
||||
#include "nvim/highlight.h"
|
||||
#include "nvim/window.h"
|
||||
@ -1268,6 +1269,67 @@ Dictionary nvim_get_color_map(void)
|
||||
return colors;
|
||||
}
|
||||
|
||||
/// Gets a map of the current editor state.
|
||||
///
|
||||
/// @param types Context types ("regs", "jumps", "buflist", "gvars", ...)
|
||||
/// to gather, or NIL for all.
|
||||
///
|
||||
/// @return map of global context
|
||||
Dictionary nvim_get_context(Array types)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
int int_types = 0;
|
||||
if (types.size == 1 && types.items[0].type == kObjectTypeNil) {
|
||||
int_types = kCtxAll;
|
||||
} else {
|
||||
for (size_t i = 0; i < types.size; i++) {
|
||||
if (types.items[i].type == kObjectTypeString) {
|
||||
const char *const current = types.items[i].data.string.data;
|
||||
if (strequal(current, "regs")) {
|
||||
int_types |= kCtxRegs;
|
||||
} else if (strequal(current, "jumps")) {
|
||||
int_types |= kCtxJumps;
|
||||
} else if (strequal(current, "buflist")) {
|
||||
int_types |= kCtxBuflist;
|
||||
} else if (strequal(current, "gvars")) {
|
||||
int_types |= kCtxGVars;
|
||||
} else if (strequal(current, "sfuncs")) {
|
||||
int_types |= kCtxSFuncs;
|
||||
} else if (strequal(current, "funcs")) {
|
||||
int_types |= kCtxFuncs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Context ctx = CONTEXT_INIT;
|
||||
ctx_save(&ctx, int_types);
|
||||
Dictionary dict = ctx_to_dict(&ctx);
|
||||
ctx_free(&ctx);
|
||||
return dict;
|
||||
}
|
||||
|
||||
/// Sets the current editor state to that in given context dictionary.
|
||||
///
|
||||
/// @param ctx_dict Context dictionary.
|
||||
Object nvim_load_context(Dictionary dict)
|
||||
FUNC_API_SINCE(6)
|
||||
{
|
||||
Context ctx = CONTEXT_INIT;
|
||||
|
||||
int save_did_emsg = did_emsg;
|
||||
did_emsg = false;
|
||||
|
||||
ctx_from_dict(dict, &ctx);
|
||||
if (!did_emsg) {
|
||||
ctx_restore(&ctx, kCtxAll);
|
||||
}
|
||||
|
||||
ctx_free(&ctx);
|
||||
|
||||
did_emsg = save_did_emsg;
|
||||
return (Object)OBJECT_INIT;
|
||||
}
|
||||
|
||||
/// Gets the current mode. |mode()|
|
||||
/// "blocking" is true if Nvim is waiting for input.
|
||||
|
383
src/nvim/context.c
Normal file
383
src/nvim/context.c
Normal file
@ -0,0 +1,383 @@
|
||||
// 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
|
||||
|
||||
// Context: snapshot of the entire editor state as one big object/map
|
||||
|
||||
#include "nvim/context.h"
|
||||
#include "nvim/eval/encode.h"
|
||||
#include "nvim/ex_docmd.h"
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/shada.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "context.c.generated.h"
|
||||
#endif
|
||||
|
||||
int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBuflist | kCtxGVars | kCtxSFuncs
|
||||
| kCtxFuncs);
|
||||
|
||||
static ContextVec ctx_stack = KV_INITIAL_VALUE;
|
||||
|
||||
/// Clears and frees the context stack
|
||||
void ctx_free_all(void)
|
||||
{
|
||||
for (size_t i = 0; i < kv_size(ctx_stack); i++) {
|
||||
ctx_free(&kv_A(ctx_stack, i));
|
||||
}
|
||||
kv_destroy(ctx_stack);
|
||||
}
|
||||
|
||||
/// Returns the size of the context stack.
|
||||
size_t ctx_size(void)
|
||||
{
|
||||
return kv_size(ctx_stack);
|
||||
}
|
||||
|
||||
/// Returns pointer to Context object with given zero-based index from the top
|
||||
/// of context stack or NULL if index is out of bounds.
|
||||
Context *ctx_get(size_t index)
|
||||
{
|
||||
if (index < kv_size(ctx_stack)) {
|
||||
return &kv_Z(ctx_stack, index);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/// Free resources used by Context object.
|
||||
///
|
||||
/// param[in] ctx pointer to Context object to free.
|
||||
void ctx_free(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (ctx->regs.data) {
|
||||
msgpack_sbuffer_destroy(&ctx->regs);
|
||||
}
|
||||
if (ctx->jumps.data) {
|
||||
msgpack_sbuffer_destroy(&ctx->jumps);
|
||||
}
|
||||
if (ctx->buflist.data) {
|
||||
msgpack_sbuffer_destroy(&ctx->buflist);
|
||||
}
|
||||
if (ctx->gvars.data) {
|
||||
msgpack_sbuffer_destroy(&ctx->gvars);
|
||||
}
|
||||
if (ctx->funcs.items) {
|
||||
api_free_array(ctx->funcs);
|
||||
}
|
||||
}
|
||||
|
||||
/// Saves the editor state to a context.
|
||||
///
|
||||
/// If "context" is NULL, pushes context on context stack.
|
||||
/// Use "flags" to select particular types of context.
|
||||
///
|
||||
/// @param ctx Save to this context, or push on context stack if NULL.
|
||||
/// @param flags Flags, see ContextTypeFlags enum.
|
||||
void ctx_save(Context *ctx, const int flags)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
kv_push(ctx_stack, CONTEXT_INIT);
|
||||
ctx = &kv_last(ctx_stack);
|
||||
}
|
||||
|
||||
if (flags & kCtxRegs) {
|
||||
ctx_save_regs(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxJumps) {
|
||||
ctx_save_jumps(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxBuflist) {
|
||||
ctx_save_buflist(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxGVars) {
|
||||
ctx_save_gvars(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxFuncs) {
|
||||
ctx_save_funcs(ctx, false);
|
||||
} else if (flags & kCtxSFuncs) {
|
||||
ctx_save_funcs(ctx, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Restores the editor state from a context.
|
||||
///
|
||||
/// If "context" is NULL, pops context from context stack.
|
||||
/// Use "flags" to select particular types of context.
|
||||
///
|
||||
/// @param ctx Restore from this context. Pop from context stack if NULL.
|
||||
/// @param flags Flags, see ContextTypeFlags enum.
|
||||
///
|
||||
/// @return true on success, false otherwise (i.e.: empty context stack).
|
||||
bool ctx_restore(Context *ctx, const int flags)
|
||||
{
|
||||
bool free_ctx = false;
|
||||
if (ctx == NULL) {
|
||||
if (ctx_stack.size == 0) {
|
||||
return false;
|
||||
}
|
||||
ctx = &kv_pop(ctx_stack);
|
||||
free_ctx = true;
|
||||
}
|
||||
|
||||
char_u *op_shada;
|
||||
get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL);
|
||||
set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
|
||||
|
||||
if (flags & kCtxRegs) {
|
||||
ctx_restore_regs(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxJumps) {
|
||||
ctx_restore_jumps(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxBuflist) {
|
||||
ctx_restore_buflist(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxGVars) {
|
||||
ctx_restore_gvars(ctx);
|
||||
}
|
||||
|
||||
if (flags & kCtxFuncs) {
|
||||
ctx_restore_funcs(ctx);
|
||||
}
|
||||
|
||||
if (free_ctx) {
|
||||
ctx_free(ctx);
|
||||
}
|
||||
|
||||
set_option_value("shada", 0L, (char *)op_shada, OPT_GLOBAL);
|
||||
xfree(op_shada);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Saves the global registers to a context.
|
||||
///
|
||||
/// @param ctx Save to this context.
|
||||
static inline void ctx_save_regs(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
msgpack_sbuffer_init(&ctx->regs);
|
||||
shada_encode_regs(&ctx->regs);
|
||||
}
|
||||
|
||||
/// Restores the global registers from a context.
|
||||
///
|
||||
/// @param ctx Restore from this context.
|
||||
static inline void ctx_restore_regs(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
shada_read_sbuf(&ctx->regs, kShaDaWantInfo | kShaDaForceit);
|
||||
}
|
||||
|
||||
/// Saves the jumplist to a context.
|
||||
///
|
||||
/// @param ctx Save to this context.
|
||||
static inline void ctx_save_jumps(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
msgpack_sbuffer_init(&ctx->jumps);
|
||||
shada_encode_jumps(&ctx->jumps);
|
||||
}
|
||||
|
||||
/// Restores the jumplist from a context.
|
||||
///
|
||||
/// @param ctx Restore from this context.
|
||||
static inline void ctx_restore_jumps(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
shada_read_sbuf(&ctx->jumps, kShaDaWantInfo | kShaDaForceit);
|
||||
}
|
||||
|
||||
/// Saves the buffer list to a context.
|
||||
///
|
||||
/// @param ctx Save to this context.
|
||||
static inline void ctx_save_buflist(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
msgpack_sbuffer_init(&ctx->buflist);
|
||||
shada_encode_buflist(&ctx->buflist);
|
||||
}
|
||||
|
||||
/// Restores the buffer list from a context.
|
||||
///
|
||||
/// @param ctx Restore from this context.
|
||||
static inline void ctx_restore_buflist(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
shada_read_sbuf(&ctx->buflist, kShaDaWantInfo | kShaDaForceit);
|
||||
}
|
||||
|
||||
/// Saves global variables to a context.
|
||||
///
|
||||
/// @param ctx Save to this context.
|
||||
static inline void ctx_save_gvars(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
msgpack_sbuffer_init(&ctx->gvars);
|
||||
shada_encode_gvars(&ctx->gvars);
|
||||
}
|
||||
|
||||
/// Restores global variables from a context.
|
||||
///
|
||||
/// @param ctx Restore from this context.
|
||||
static inline void ctx_restore_gvars(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
shada_read_sbuf(&ctx->gvars, kShaDaWantInfo | kShaDaForceit);
|
||||
}
|
||||
|
||||
/// Saves functions to a context.
|
||||
///
|
||||
/// @param ctx Save to this context.
|
||||
/// @param scriptonly Save script-local (s:) functions only.
|
||||
static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
ctx->funcs = (Array)ARRAY_DICT_INIT;
|
||||
Error err = ERROR_INIT;
|
||||
|
||||
HASHTAB_ITER(&func_hashtab, hi, {
|
||||
const char_u *const name = hi->hi_key;
|
||||
bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
|
||||
bool isscript = (name[0] == K_SPECIAL);
|
||||
|
||||
if (!islambda && (!scriptonly || isscript)) {
|
||||
size_t cmd_len = sizeof("func! ") + STRLEN(name);
|
||||
char *cmd = xmalloc(cmd_len);
|
||||
snprintf(cmd, cmd_len, "func! %s", name);
|
||||
String func_body = nvim_command_output(cstr_as_string(cmd), &err);
|
||||
xfree(cmd);
|
||||
if (!ERROR_SET(&err)) {
|
||||
ADD(ctx->funcs, STRING_OBJ(func_body));
|
||||
}
|
||||
api_clear_error(&err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Restores functions from a context.
|
||||
///
|
||||
/// @param ctx Restore from this context.
|
||||
static inline void ctx_restore_funcs(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
for (size_t i = 0; i < ctx->funcs.size; i++) {
|
||||
do_cmdline_cmd(ctx->funcs.items[i].data.string.data);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert msgpack_sbuffer to readfile()-style array.
|
||||
///
|
||||
/// @param[in] sbuf msgpack_sbuffer to convert.
|
||||
///
|
||||
/// @return readfile()-style array representation of "sbuf".
|
||||
static inline Array sbuf_to_array(msgpack_sbuffer sbuf)
|
||||
{
|
||||
list_T *const list = tv_list_alloc(kListLenMayKnow);
|
||||
tv_list_append_string(list, "", 0);
|
||||
if (sbuf.size > 0) {
|
||||
encode_list_write(list, sbuf.data, sbuf.size);
|
||||
}
|
||||
|
||||
typval_T list_tv = (typval_T) {
|
||||
.v_lock = VAR_UNLOCKED,
|
||||
.v_type = VAR_LIST,
|
||||
.vval.v_list = list
|
||||
};
|
||||
|
||||
Array array = vim_to_object(&list_tv).data.array;
|
||||
tv_clear(&list_tv);
|
||||
return array;
|
||||
}
|
||||
|
||||
/// Convert readfile()-style array to msgpack_sbuffer.
|
||||
///
|
||||
/// @param[in] array readfile()-style array to convert.
|
||||
///
|
||||
/// @return msgpack_sbuffer with conversion result.
|
||||
static inline msgpack_sbuffer array_to_sbuf(Array array)
|
||||
{
|
||||
msgpack_sbuffer sbuf;
|
||||
msgpack_sbuffer_init(&sbuf);
|
||||
|
||||
typval_T list_tv;
|
||||
Error err = ERROR_INIT;
|
||||
object_to_vim(ARRAY_OBJ(array), &list_tv, &err);
|
||||
|
||||
if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
|
||||
EMSG(_("E474: Failed to convert list to msgpack string buffer"));
|
||||
}
|
||||
sbuf.alloc = sbuf.size;
|
||||
|
||||
tv_clear(&list_tv);
|
||||
api_clear_error(&err);
|
||||
return sbuf;
|
||||
}
|
||||
|
||||
/// Converts Context to Dictionary representation.
|
||||
///
|
||||
/// @param[in] ctx Context to convert.
|
||||
///
|
||||
/// @return Dictionary representing "ctx".
|
||||
Dictionary ctx_to_dict(Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
|
||||
Dictionary rv = ARRAY_DICT_INIT;
|
||||
|
||||
PUT(rv, "regs", ARRAY_OBJ(sbuf_to_array(ctx->regs)));
|
||||
PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps)));
|
||||
PUT(rv, "buflist", ARRAY_OBJ(sbuf_to_array(ctx->buflist)));
|
||||
PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars)));
|
||||
PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs)));
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/// Converts Dictionary representation of Context back to Context object.
|
||||
///
|
||||
/// @param[in] dict Context Dictionary representation.
|
||||
/// @param[out] ctx Context object to store conversion result into.
|
||||
///
|
||||
/// @return types of included context items.
|
||||
int ctx_from_dict(Dictionary dict, Context *ctx)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
assert(ctx != NULL);
|
||||
|
||||
int types = 0;
|
||||
for (size_t i = 0; i < dict.size; i++) {
|
||||
KeyValuePair item = dict.items[i];
|
||||
if (item.value.type != kObjectTypeArray) {
|
||||
continue;
|
||||
}
|
||||
if (strequal(item.key.data, "regs")) {
|
||||
types |= kCtxRegs;
|
||||
ctx->regs = array_to_sbuf(item.value.data.array);
|
||||
} else if (strequal(item.key.data, "jumps")) {
|
||||
types |= kCtxJumps;
|
||||
ctx->jumps = array_to_sbuf(item.value.data.array);
|
||||
} else if (strequal(item.key.data, "buflist")) {
|
||||
types |= kCtxBuflist;
|
||||
ctx->buflist = array_to_sbuf(item.value.data.array);
|
||||
} else if (strequal(item.key.data, "gvars")) {
|
||||
types |= kCtxGVars;
|
||||
ctx->gvars = array_to_sbuf(item.value.data.array);
|
||||
} else if (strequal(item.key.data, "funcs")) {
|
||||
types |= kCtxFuncs;
|
||||
ctx->funcs = copy_object(item.value).data.array;
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
46
src/nvim/context.h
Normal file
46
src/nvim/context.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef NVIM_CONTEXT_H
|
||||
#define NVIM_CONTEXT_H
|
||||
|
||||
#include <msgpack.h>
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/lib/kvec.h"
|
||||
|
||||
typedef struct {
|
||||
msgpack_sbuffer regs; ///< Registers.
|
||||
msgpack_sbuffer jumps; ///< Jumplist.
|
||||
msgpack_sbuffer buflist; ///< Buffer list.
|
||||
msgpack_sbuffer gvars; ///< Global variables.
|
||||
Array funcs; ///< Functions.
|
||||
} Context;
|
||||
typedef kvec_t(Context) ContextVec;
|
||||
|
||||
#define MSGPACK_SBUFFER_INIT (msgpack_sbuffer) { \
|
||||
.size = 0, \
|
||||
.data = NULL, \
|
||||
.alloc = 0, \
|
||||
}
|
||||
|
||||
#define CONTEXT_INIT (Context) { \
|
||||
.regs = MSGPACK_SBUFFER_INIT, \
|
||||
.jumps = MSGPACK_SBUFFER_INIT, \
|
||||
.buflist = MSGPACK_SBUFFER_INIT, \
|
||||
.gvars = MSGPACK_SBUFFER_INIT, \
|
||||
.funcs = ARRAY_DICT_INIT, \
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
kCtxRegs = 1, ///< Registers
|
||||
kCtxJumps = 2, ///< Jumplist
|
||||
kCtxBuflist = 4, ///< Buffer list
|
||||
kCtxGVars = 8, ///< Global variables
|
||||
kCtxSFuncs = 16, ///< Script functions
|
||||
kCtxFuncs = 32, ///< Functions
|
||||
} ContextTypeFlags;
|
||||
|
||||
extern int kCtxAll;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "context.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_CONTEXT_H
|
178
src/nvim/eval.c
178
src/nvim/eval.c
@ -26,6 +26,7 @@
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/channel.h"
|
||||
#include "nvim/charset.h"
|
||||
#include "nvim/context.h"
|
||||
#include "nvim/cursor.h"
|
||||
#include "nvim/diff.h"
|
||||
#include "nvim/edit.h"
|
||||
@ -303,15 +304,6 @@ typedef struct {
|
||||
list_T *fi_list; /* list being used */
|
||||
} forinfo_T;
|
||||
|
||||
/*
|
||||
* enum used by var_flavour()
|
||||
*/
|
||||
typedef enum {
|
||||
VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */
|
||||
VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */
|
||||
VAR_FLAVOUR_SHADA /* all uppercase */
|
||||
} var_flavour_T;
|
||||
|
||||
/* values for vv_flags: */
|
||||
#define VV_COMPAT 1 /* compatible, also used without "v:" */
|
||||
#define VV_RO 2 /* read-only */
|
||||
@ -5261,7 +5253,7 @@ bool garbage_collect(bool testing)
|
||||
yankreg_T reg;
|
||||
char name = NUL;
|
||||
bool is_unnamed = false;
|
||||
reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed);
|
||||
reg_iter = op_global_reg_iter(reg_iter, &name, ®, &is_unnamed);
|
||||
if (name != NUL) {
|
||||
ABORTING(set_ref_dict)(reg.additional_data, copyID);
|
||||
}
|
||||
@ -8023,6 +8015,116 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
(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 = argvars[0].vval.v_number;
|
||||
} else if (argvars[0].v_type != VAR_UNKNOWN) {
|
||||
EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
|
||||
return;
|
||||
}
|
||||
|
||||
Context *ctx = ctx_get(index);
|
||||
if (ctx == NULL) {
|
||||
EMSG3(_(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, "buflist")) {
|
||||
types |= kCtxBuflist;
|
||||
} 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) {
|
||||
EMSG2(_(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) {
|
||||
EMSG2(_(e_invarg2), "expected dictionary as first argument");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
if (argvars[1].v_type == VAR_NUMBER) {
|
||||
index = argvars[1].vval.v_number;
|
||||
} else if (argvars[1].v_type != VAR_UNKNOWN) {
|
||||
EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
|
||||
return;
|
||||
}
|
||||
|
||||
Context *ctx = ctx_get(index);
|
||||
if (ctx == NULL) {
|
||||
EMSG3(_(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 = ctx_size();
|
||||
}
|
||||
|
||||
/// "cursor(lnum, col)" function, or
|
||||
/// "cursor(list)"
|
||||
///
|
||||
@ -15618,7 +15720,7 @@ free_lstval:
|
||||
|
||||
if (set_unnamed) {
|
||||
// Discard the result. We already handle the error case.
|
||||
if (op_register_set_previous(regname)) { }
|
||||
if (op_reg_set_previous(regname)) { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -20883,7 +20985,7 @@ void ex_function(exarg_T *eap)
|
||||
continue;
|
||||
}
|
||||
if (!func_name_refcount(fp->uf_name)) {
|
||||
list_func_head(fp, false);
|
||||
list_func_head(fp, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20914,7 +21016,7 @@ void ex_function(exarg_T *eap)
|
||||
fp = HI2UF(hi);
|
||||
if (!isdigit(*fp->uf_name)
|
||||
&& vim_regexec(®match, fp->uf_name, 0))
|
||||
list_func_head(fp, FALSE);
|
||||
list_func_head(fp, false, false);
|
||||
}
|
||||
}
|
||||
vim_regfree(regmatch.regprog);
|
||||
@ -20964,9 +21066,12 @@ void ex_function(exarg_T *eap)
|
||||
saved_did_emsg = did_emsg;
|
||||
did_emsg = FALSE;
|
||||
|
||||
/*
|
||||
* ":function func" with only function name: list function.
|
||||
*/
|
||||
//
|
||||
// ":function func" with only function name: list function.
|
||||
// If bang is given:
|
||||
// - include "!" in function head
|
||||
// - exclude line numbers from function body
|
||||
//
|
||||
if (!paren) {
|
||||
if (!ends_excmd(*skipwhite(p))) {
|
||||
EMSG(_(e_trailing));
|
||||
@ -20978,17 +21083,20 @@ void ex_function(exarg_T *eap)
|
||||
if (!eap->skip && !got_int) {
|
||||
fp = find_func(name);
|
||||
if (fp != NULL) {
|
||||
list_func_head(fp, TRUE);
|
||||
for (int j = 0; j < fp->uf_lines.ga_len && !got_int; ++j) {
|
||||
if (FUNCLINE(fp, j) == NULL)
|
||||
list_func_head(fp, !eap->forceit, eap->forceit);
|
||||
for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
|
||||
if (FUNCLINE(fp, j) == NULL) {
|
||||
continue;
|
||||
msg_putchar('\n');
|
||||
msg_outnum((long)j + 1);
|
||||
if (j < 9) {
|
||||
msg_putchar(' ');
|
||||
}
|
||||
if (j < 99) {
|
||||
msg_putchar(' ');
|
||||
msg_putchar('\n');
|
||||
if (!eap->forceit) {
|
||||
msg_outnum((long)j + 1);
|
||||
if (j < 9) {
|
||||
msg_putchar(' ');
|
||||
}
|
||||
if (j < 99) {
|
||||
msg_putchar(' ');
|
||||
}
|
||||
}
|
||||
msg_prt_line(FUNCLINE(fp, j), false);
|
||||
ui_flush(); // show a line at a time
|
||||
@ -20996,7 +21104,7 @@ void ex_function(exarg_T *eap)
|
||||
}
|
||||
if (!got_int) {
|
||||
msg_putchar('\n');
|
||||
msg_puts(" endfunction");
|
||||
msg_puts(eap->forceit ? "endfunction" : " endfunction");
|
||||
}
|
||||
} else
|
||||
emsg_funcname(N_("E123: Undefined function: %s"), name);
|
||||
@ -21686,15 +21794,17 @@ static inline bool eval_fname_sid(const char *const name)
|
||||
return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
|
||||
}
|
||||
|
||||
/*
|
||||
* List the head of the function: "name(arg1, arg2)".
|
||||
*/
|
||||
static void list_func_head(ufunc_T *fp, int indent)
|
||||
/// List the head of the function: "name(arg1, arg2)".
|
||||
///
|
||||
/// @param[in] fp Function pointer.
|
||||
/// @param[in] indent Indent line.
|
||||
/// @param[in] force Include bang "!" (i.e.: "function!").
|
||||
static void list_func_head(ufunc_T *fp, int indent, bool force)
|
||||
{
|
||||
msg_start();
|
||||
if (indent)
|
||||
MSG_PUTS(" ");
|
||||
MSG_PUTS("function ");
|
||||
MSG_PUTS(force ? "function! " : "function ");
|
||||
if (fp->uf_name[0] == K_SPECIAL) {
|
||||
MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));
|
||||
msg_puts((const char *)fp->uf_name + 3);
|
||||
@ -23278,7 +23388,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
|
||||
/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
|
||||
/// or NULL to indicate that iteration is over.
|
||||
const void *var_shada_iter(const void *const iter, const char **const name,
|
||||
typval_T *rettv)
|
||||
typval_T *rettv, var_flavour_T flavour)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
|
||||
{
|
||||
const hashitem_T *hi;
|
||||
@ -23289,7 +23399,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,
|
||||
hi = globvarht.ht_array;
|
||||
while ((size_t) (hi - hifirst) < hinum
|
||||
&& (HASHITEM_EMPTY(hi)
|
||||
|| var_flavour(hi->hi_key) != VAR_FLAVOUR_SHADA)) {
|
||||
|| !(var_flavour(hi->hi_key) & flavour))) {
|
||||
hi++;
|
||||
}
|
||||
if ((size_t) (hi - hifirst) == hinum) {
|
||||
@ -23301,7 +23411,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,
|
||||
*name = (char *)TV_DICT_HI2DI(hi)->di_key;
|
||||
tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);
|
||||
while ((size_t)(++hi - hifirst) < hinum) {
|
||||
if (!HASHITEM_EMPTY(hi) && var_flavour(hi->hi_key) == VAR_FLAVOUR_SHADA) {
|
||||
if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {
|
||||
return hi;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,13 @@ EXTERN ufunc_T dumuf;
|
||||
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
|
||||
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
|
||||
|
||||
/// enum used by var_flavour()
|
||||
typedef enum {
|
||||
VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase
|
||||
VAR_FLAVOUR_SESSION = 2, // starts with uppercase, some lower
|
||||
VAR_FLAVOUR_SHADA = 4 // all uppercase
|
||||
} var_flavour_T;
|
||||
|
||||
/// Defines for Vim variables
|
||||
typedef enum {
|
||||
VV_COUNT,
|
||||
|
@ -74,6 +74,11 @@ return {
|
||||
cosh={args=1, func="float_op_wrapper", data="&cosh"},
|
||||
count={args={2, 4}},
|
||||
cscope_connection={args={0, 3}},
|
||||
ctxget={args={0, 1}},
|
||||
ctxpop={},
|
||||
ctxpush={args={0, 1}},
|
||||
ctxset={args={1, 2}},
|
||||
ctxsize={},
|
||||
cursor={args={1, 3}},
|
||||
deepcopy={args={1, 2}},
|
||||
delete={args={1,2}},
|
||||
|
@ -1213,8 +1213,8 @@ void cleanup_jumplist(win_T *wp, bool loadfiles)
|
||||
|
||||
// When pointer is below last jump, remove the jump if it matches the current
|
||||
// line. This avoids useless/phantom jumps. #9805
|
||||
if (wp->w_jumplistlen
|
||||
&& wp->w_jumplistidx == wp->w_jumplistlen) {
|
||||
if (loadfiles // otherwise (i.e.: Shada), last entry should be kept
|
||||
&& wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) {
|
||||
const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1];
|
||||
if (fm_last->fmark.fnum == curbuf->b_fnum
|
||||
&& fm_last->fmark.mark.lnum == wp->w_cursor.lnum) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/context.h"
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/highlight.h"
|
||||
#include "nvim/memfile.h"
|
||||
@ -671,6 +672,7 @@ void free_all_mem(void)
|
||||
|
||||
eval_clear();
|
||||
api_vim_free_all_mem();
|
||||
ctx_free_all();
|
||||
|
||||
// Free all buffers. Reset 'autochdir' to avoid accessing things that
|
||||
// were freed already.
|
||||
|
@ -3516,8 +3516,8 @@ void ex_display(exarg_T *eap)
|
||||
* display a string for do_dis()
|
||||
* truncate at end of screen line
|
||||
*/
|
||||
static void
|
||||
dis_msg (
|
||||
static void
|
||||
dis_msg(
|
||||
char_u *p,
|
||||
int skip_esc /* if TRUE, ignore trailing ESC */
|
||||
)
|
||||
@ -3857,8 +3857,8 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in
|
||||
/*
|
||||
* Implementation of the format operator 'gq'.
|
||||
*/
|
||||
void
|
||||
op_format (
|
||||
void
|
||||
op_format(
|
||||
oparg_T *oap,
|
||||
int keep_cursor /* keep cursor on same text char */
|
||||
)
|
||||
@ -3937,8 +3937,8 @@ void op_formatexpr(oparg_T *oap)
|
||||
op_format(oap, FALSE);
|
||||
}
|
||||
|
||||
int
|
||||
fex_format (
|
||||
int
|
||||
fex_format(
|
||||
linenr_T lnum,
|
||||
long count,
|
||||
int c /* character to be inserted */
|
||||
@ -3980,8 +3980,8 @@ fex_format (
|
||||
* Lines after the cursor line are saved for undo, caller must have saved the
|
||||
* first line.
|
||||
*/
|
||||
void
|
||||
format_lines (
|
||||
void
|
||||
format_lines(
|
||||
linenr_T line_count,
|
||||
int avoid_fex /* don't use 'formatexpr' */
|
||||
)
|
||||
@ -5892,33 +5892,45 @@ static inline bool reg_empty(const yankreg_T *const reg)
|
||||
&& *(reg->y_array[0]) == NUL));
|
||||
}
|
||||
|
||||
/// Iterate over registerrs
|
||||
/// Iterate over global registers.
|
||||
///
|
||||
/// @see op_register_iter
|
||||
const void *op_global_reg_iter(const void *const iter, char *const name,
|
||||
yankreg_T *const reg, bool *is_unnamed)
|
||||
FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
return op_reg_iter(iter, y_regs, name, reg, is_unnamed);
|
||||
}
|
||||
|
||||
/// Iterate over registers `regs`.
|
||||
///
|
||||
/// @param[in] iter Iterator. Pass NULL to start iteration.
|
||||
/// @param[in] regs Registers list to be iterated.
|
||||
/// @param[out] name Register name.
|
||||
/// @param[out] reg Register contents.
|
||||
///
|
||||
/// @return Pointer that needs to be passed to next `op_register_iter` call or
|
||||
/// @return Pointer that must be passed to next `op_register_iter` call or
|
||||
/// NULL if iteration is over.
|
||||
const void *op_register_iter(const void *const iter, char *const name,
|
||||
yankreg_T *const reg, bool *is_unnamed)
|
||||
FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
const void *op_reg_iter(const void *const iter, const yankreg_T *const regs,
|
||||
char *const name, yankreg_T *const reg,
|
||||
bool *is_unnamed)
|
||||
FUNC_ATTR_NONNULL_ARG(3, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
*name = NUL;
|
||||
const yankreg_T *iter_reg = (iter == NULL
|
||||
? &(y_regs[0])
|
||||
: (const yankreg_T *const) iter);
|
||||
while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
|
||||
? &(regs[0])
|
||||
: (const yankreg_T *const)iter);
|
||||
while (iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
|
||||
iter_reg++;
|
||||
}
|
||||
if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
|
||||
if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
|
||||
return NULL;
|
||||
}
|
||||
int iter_off = (int)(iter_reg - &(y_regs[0]));
|
||||
int iter_off = (int)(iter_reg - &(regs[0]));
|
||||
*name = (char)get_register_name(iter_off);
|
||||
*reg = *iter_reg;
|
||||
*is_unnamed = (iter_reg == y_previous);
|
||||
while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
|
||||
while (++iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS) {
|
||||
if (!reg_empty(iter_reg)) {
|
||||
return (void *) iter_reg;
|
||||
}
|
||||
@ -5927,7 +5939,7 @@ const void *op_register_iter(const void *const iter, char *const name,
|
||||
}
|
||||
|
||||
/// Get a number of non-empty registers
|
||||
size_t op_register_amount(void)
|
||||
size_t op_reg_amount(void)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
size_t ret = 0;
|
||||
@ -5946,7 +5958,7 @@ size_t op_register_amount(void)
|
||||
/// @param[in] is_unnamed Whether to set the unnamed regiseter to reg
|
||||
///
|
||||
/// @return true on success, false on failure.
|
||||
bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed)
|
||||
bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
|
||||
{
|
||||
int i = op_reg_index(name);
|
||||
if (i == -1) {
|
||||
@ -5966,7 +5978,7 @@ bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed)
|
||||
/// @param[in] name Register name.
|
||||
///
|
||||
/// @return Pointer to the register contents or NULL.
|
||||
const yankreg_T *op_register_get(const char name)
|
||||
const yankreg_T *op_reg_get(const char name)
|
||||
{
|
||||
int i = op_reg_index(name);
|
||||
if (i == -1) {
|
||||
@ -5980,7 +5992,7 @@ const yankreg_T *op_register_get(const char name)
|
||||
/// @param[in] name Register name.
|
||||
///
|
||||
/// @return true on success, false on failure.
|
||||
bool op_register_set_previous(const char name)
|
||||
bool op_reg_set_previous(const char name)
|
||||
FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
int i = op_reg_index(name);
|
||||
|
302
src/nvim/shada.c
302
src/nvim/shada.c
@ -151,15 +151,6 @@ KHASH_SET_INIT_STR(strset)
|
||||
/// Callback function for add_search_pattern
|
||||
typedef void (*SearchPatternGetter)(SearchPattern *);
|
||||
|
||||
/// Flags for shada_read_file and children
|
||||
typedef enum {
|
||||
kShaDaWantInfo = 1, ///< Load non-mark information
|
||||
kShaDaWantMarks = 2, ///< Load local file marks and change list
|
||||
kShaDaForceit = 4, ///< Overwrite info already read
|
||||
kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
|
||||
kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
|
||||
} ShaDaReadFileFlags;
|
||||
|
||||
/// Possible ShaDa entry types
|
||||
///
|
||||
/// @warning Enum values are part of the API and must not be altered.
|
||||
@ -1328,13 +1319,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
|
||||
break;
|
||||
}
|
||||
if (!force) {
|
||||
const yankreg_T *const reg = op_register_get(cur_entry.data.reg.name);
|
||||
const yankreg_T *const reg = op_reg_get(cur_entry.data.reg.name);
|
||||
if (reg == NULL || reg->timestamp >= cur_entry.timestamp) {
|
||||
shada_free_shada_entry(&cur_entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!op_register_set(cur_entry.data.reg.name, (yankreg_T) {
|
||||
if (!op_reg_set(cur_entry.data.reg.name, (yankreg_T) {
|
||||
.y_array = (char_u **)cur_entry.data.reg.contents,
|
||||
.y_size = cur_entry.data.reg.contents_size,
|
||||
.y_type = cur_entry.data.reg.type,
|
||||
@ -2496,7 +2487,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms,
|
||||
yankreg_T reg;
|
||||
char name = NUL;
|
||||
bool is_unnamed = false;
|
||||
reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed);
|
||||
reg_iter = op_global_reg_iter(reg_iter, &name, ®, &is_unnamed);
|
||||
if (name == NUL) {
|
||||
break;
|
||||
}
|
||||
@ -2552,6 +2543,19 @@ static inline void replace_numbered_mark(WriteMergerState *const wms,
|
||||
wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx);
|
||||
}
|
||||
|
||||
/// Find buffers ignored due to their location.
|
||||
///
|
||||
/// @param[out] removable_bufs Cache of buffers ignored due to their location.
|
||||
static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
|
||||
{
|
||||
FOR_ALL_BUFFERS(buf) {
|
||||
if (buf->b_ffname != NULL && shada_removable((char *)buf->b_ffname)) {
|
||||
int kh_ret;
|
||||
(void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write ShaDa file
|
||||
///
|
||||
/// @param[in] sd_writer Structure containing file writer definition.
|
||||
@ -2621,12 +2625,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
|
||||
set_last_cursor(wp);
|
||||
}
|
||||
|
||||
FOR_ALL_BUFFERS(buf) {
|
||||
if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) {
|
||||
int kh_ret;
|
||||
(void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret);
|
||||
}
|
||||
}
|
||||
find_removable_bufs(&removable_bufs);
|
||||
|
||||
// Write header
|
||||
if (shada_pack_entry(packer, (ShadaEntry) {
|
||||
@ -2673,7 +2672,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
|
||||
do {
|
||||
typval_T vartv;
|
||||
const char *name = NULL;
|
||||
var_iter = var_shada_iter(var_iter, &name, &vartv);
|
||||
var_iter = var_shada_iter(var_iter, &name, &vartv, VAR_FLAVOUR_SHADA);
|
||||
if (name == NULL) {
|
||||
break;
|
||||
}
|
||||
@ -2737,49 +2736,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
|
||||
}
|
||||
|
||||
// Initialize jump list
|
||||
const void *jump_iter = NULL;
|
||||
cleanup_jumplist(curwin, false);
|
||||
setpcmark();
|
||||
do {
|
||||
xfmark_T fm;
|
||||
jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
|
||||
|
||||
if (fm.fmark.mark.lnum == 0) {
|
||||
iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
|
||||
(void *)jump_iter, (void *)&curwin->w_jumplist[0],
|
||||
curwin->w_jumplistlen);
|
||||
continue;
|
||||
}
|
||||
const buf_T *const buf = (fm.fmark.fnum == 0
|
||||
? NULL
|
||||
: buflist_findnr(fm.fmark.fnum));
|
||||
if (buf != NULL
|
||||
? in_bufset(&removable_bufs, buf)
|
||||
: fm.fmark.fnum != 0) {
|
||||
continue;
|
||||
}
|
||||
const char *const fname = (char *) (fm.fmark.fnum == 0
|
||||
? (fm.fname == NULL ? NULL : fm.fname)
|
||||
: buf->b_ffname);
|
||||
if (fname == NULL) {
|
||||
continue;
|
||||
}
|
||||
wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) {
|
||||
.can_free_entry = false,
|
||||
.data = {
|
||||
.type = kSDItemJump,
|
||||
.timestamp = fm.fmark.timestamp,
|
||||
.data = {
|
||||
.filemark = {
|
||||
.name = NUL,
|
||||
.mark = fm.fmark.mark,
|
||||
.fname = (char *) fname,
|
||||
.additional_data = fm.fmark.additional_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} while (jump_iter != NULL);
|
||||
wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs);
|
||||
|
||||
// Initialize global marks
|
||||
if (dump_global_marks) {
|
||||
@ -4117,3 +4074,224 @@ static bool shada_removable(const char *name)
|
||||
xfree(new_name);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// Initialize ShaDa jumplist entries.
|
||||
///
|
||||
/// @param[in,out] jumps Array of ShaDa entries to set.
|
||||
/// @param[in] removable_bufs Cache of buffers ignored due to their
|
||||
/// location.
|
||||
///
|
||||
/// @return number of jumplist entries
|
||||
static inline size_t shada_init_jumps(
|
||||
PossiblyFreedShadaEntry *jumps, khash_t(bufset) *const removable_bufs)
|
||||
{
|
||||
// Initialize jump list
|
||||
size_t jumps_size = 0;
|
||||
const void *jump_iter = NULL;
|
||||
setpcmark();
|
||||
cleanup_jumplist(curwin, false);
|
||||
do {
|
||||
xfmark_T fm;
|
||||
jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
|
||||
|
||||
if (fm.fmark.mark.lnum == 0) {
|
||||
iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
|
||||
(void *)jump_iter, (void *)&curwin->w_jumplist[0],
|
||||
curwin->w_jumplistlen);
|
||||
continue;
|
||||
}
|
||||
const buf_T *const buf = (fm.fmark.fnum == 0
|
||||
? NULL
|
||||
: buflist_findnr(fm.fmark.fnum));
|
||||
if (buf != NULL
|
||||
? in_bufset(removable_bufs, buf)
|
||||
: fm.fmark.fnum != 0) {
|
||||
continue;
|
||||
}
|
||||
const char *const fname = (char *) (fm.fmark.fnum == 0
|
||||
? (fm.fname == NULL ? NULL : fm.fname)
|
||||
: buf->b_ffname);
|
||||
if (fname == NULL) {
|
||||
continue;
|
||||
}
|
||||
jumps[jumps_size++] = (PossiblyFreedShadaEntry) {
|
||||
.can_free_entry = false,
|
||||
.data = {
|
||||
.type = kSDItemJump,
|
||||
.timestamp = fm.fmark.timestamp,
|
||||
.data = {
|
||||
.filemark = {
|
||||
.name = NUL,
|
||||
.mark = fm.fmark.mark,
|
||||
.fname = (char *) fname,
|
||||
.additional_data = fm.fmark.additional_data,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} while (jump_iter != NULL);
|
||||
return jumps_size;
|
||||
}
|
||||
|
||||
/// Write registers ShaDa entries in given msgpack_sbuffer.
|
||||
///
|
||||
/// @param[in] sbuf target msgpack_sbuffer to write to.
|
||||
void shada_encode_regs(msgpack_sbuffer *const sbuf)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
WriteMergerState wms;
|
||||
shada_initialize_registers(&wms, -1);
|
||||
msgpack_packer packer;
|
||||
msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
|
||||
for (size_t i = 0; i < ARRAY_SIZE(wms.registers); i++) {
|
||||
if (wms.registers[i].data.type == kSDItemRegister) {
|
||||
shada_pack_pfreed_entry(&packer, wms.registers[i], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write jumplist ShaDa entries in given msgpack_sbuffer.
|
||||
///
|
||||
/// @param[in] sbuf target msgpack_sbuffer to write to.
|
||||
void shada_encode_jumps(msgpack_sbuffer *const sbuf)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
|
||||
find_removable_bufs(&removable_bufs);
|
||||
PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
|
||||
size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
|
||||
msgpack_packer packer;
|
||||
msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
|
||||
for (size_t i = 0; i < jumps_size; i++) {
|
||||
shada_pack_pfreed_entry(&packer, jumps[i], 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write buffer list ShaDa entry in given msgpack_sbuffer.
|
||||
///
|
||||
/// @param[in] sbuf target msgpack_sbuffer to write to.
|
||||
void shada_encode_buflist(msgpack_sbuffer *const sbuf)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
|
||||
find_removable_bufs(&removable_bufs);
|
||||
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
|
||||
msgpack_packer packer;
|
||||
msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
|
||||
shada_pack_entry(&packer, buflist_entry, 0);
|
||||
xfree(buflist_entry.data.buffer_list.buffers);
|
||||
}
|
||||
|
||||
/// Write global variables ShaDa entries in given msgpack_sbuffer.
|
||||
///
|
||||
/// @param[in] sbuf target msgpack_sbuffer to write to.
|
||||
void shada_encode_gvars(msgpack_sbuffer *const sbuf)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
msgpack_packer packer;
|
||||
msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
|
||||
const void *var_iter = NULL;
|
||||
const Timestamp cur_timestamp = os_time();
|
||||
do {
|
||||
typval_T vartv;
|
||||
const char *name = NULL;
|
||||
var_iter = var_shada_iter(
|
||||
var_iter, &name, &vartv,
|
||||
VAR_FLAVOUR_DEFAULT | VAR_FLAVOUR_SESSION | VAR_FLAVOUR_SHADA);
|
||||
if (name == NULL) {
|
||||
break;
|
||||
}
|
||||
if (vartv.v_type != VAR_FUNC && vartv.v_type != VAR_PARTIAL) {
|
||||
typval_T tgttv;
|
||||
tv_copy(&vartv, &tgttv);
|
||||
shada_pack_entry(&packer, (ShadaEntry) {
|
||||
.type = kSDItemVariable,
|
||||
.timestamp = cur_timestamp,
|
||||
.data = {
|
||||
.global_var = {
|
||||
.name = (char *)name,
|
||||
.value = tgttv,
|
||||
.additional_elements = NULL,
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
tv_clear(&tgttv);
|
||||
}
|
||||
tv_clear(&vartv);
|
||||
} while (var_iter != NULL);
|
||||
}
|
||||
|
||||
/// Wrapper for reading from msgpack_sbuffer.
|
||||
///
|
||||
/// @return number of bytes read.
|
||||
static ptrdiff_t read_sbuf(ShaDaReadDef *const sd_reader, void *const dest,
|
||||
const size_t size)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
|
||||
const uintmax_t bytes_read = MIN(size, sbuf->size - sd_reader->fpos);
|
||||
if (bytes_read < size) {
|
||||
sd_reader->eof = true;
|
||||
}
|
||||
memcpy(dest, sbuf->data + sd_reader->fpos, (size_t)bytes_read);
|
||||
sd_reader->fpos += bytes_read;
|
||||
return (ptrdiff_t)bytes_read;
|
||||
}
|
||||
|
||||
/// Wrapper for read that ignores bytes read from msgpack_sbuffer.
|
||||
///
|
||||
/// Used for skipping.
|
||||
///
|
||||
/// @param[in,out] sd_reader ShaDaReadDef with msgpack_sbuffer.
|
||||
/// @param[in] offset Amount of bytes to skip.
|
||||
///
|
||||
/// @return FAIL in case of failure, OK in case of success. May set
|
||||
/// sd_reader->eof.
|
||||
static int sd_sbuf_reader_skip_read(ShaDaReadDef *const sd_reader,
|
||||
const size_t offset)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
|
||||
const uintmax_t bytes_skipped = MIN(offset, sbuf->size - sd_reader->fpos);
|
||||
if (bytes_skipped < offset) {
|
||||
sd_reader->eof = true;
|
||||
return FAIL;
|
||||
}
|
||||
sd_reader->fpos += offset;
|
||||
return OK;
|
||||
}
|
||||
|
||||
/// Prepare ShaDaReadDef with msgpack_sbuffer for reading.
|
||||
///
|
||||
/// @param[in] sbuf msgpack_sbuffer to read from.
|
||||
/// @param[out] sd_reader Location where reader structure will be saved.
|
||||
static void open_shada_sbuf_for_reading(const msgpack_sbuffer *const sbuf,
|
||||
ShaDaReadDef *sd_reader)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
*sd_reader = (ShaDaReadDef) {
|
||||
.read = &read_sbuf,
|
||||
.close = NULL,
|
||||
.skip = &sd_sbuf_reader_skip_read,
|
||||
.error = NULL,
|
||||
.eof = false,
|
||||
.fpos = 0,
|
||||
.cookie = (void *)sbuf,
|
||||
};
|
||||
}
|
||||
|
||||
/// Read ShaDa from msgpack_sbuffer.
|
||||
///
|
||||
/// @param[in] file msgpack_sbuffer to read from.
|
||||
/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
|
||||
void shada_read_sbuf(msgpack_sbuffer *const sbuf, const int flags)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
assert(sbuf != NULL);
|
||||
if (sbuf->data == NULL) {
|
||||
return;
|
||||
}
|
||||
ShaDaReadDef sd_reader;
|
||||
open_shada_sbuf_for_reading(sbuf, &sd_reader);
|
||||
shada_read(&sd_reader, flags);
|
||||
}
|
||||
|
@ -1,6 +1,17 @@
|
||||
#ifndef NVIM_SHADA_H
|
||||
#define NVIM_SHADA_H
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
/// Flags for shada_read_file and children
|
||||
typedef enum {
|
||||
kShaDaWantInfo = 1, ///< Load non-mark information
|
||||
kShaDaWantMarks = 2, ///< Load local file marks and change list
|
||||
kShaDaForceit = 4, ///< Overwrite info already read
|
||||
kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
|
||||
kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
|
||||
} ShaDaReadFileFlags;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "shada.h.generated.h"
|
||||
#endif
|
||||
|
@ -11,6 +11,7 @@ local meth_pcall = helpers.meth_pcall
|
||||
local meths = helpers.meths
|
||||
local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed
|
||||
local os_name = helpers.os_name
|
||||
local parse_context = helpers.parse_context
|
||||
local request = helpers.request
|
||||
local source = helpers.source
|
||||
local next_msg = helpers.next_msg
|
||||
@ -680,6 +681,65 @@ describe('API', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_get_context', function()
|
||||
it('returns context dictionary of current editor state', function()
|
||||
local ctx_items = {'regs', 'jumps', 'buflist', 'gvars'}
|
||||
eq({}, parse_context(nvim('get_context', ctx_items)))
|
||||
|
||||
feed('i1<cr>2<cr>3<c-[>ddddddqahjklquuu')
|
||||
feed('gg')
|
||||
feed('G')
|
||||
command('edit! BUF1')
|
||||
command('edit BUF2')
|
||||
nvim('set_var', 'one', 1)
|
||||
nvim('set_var', 'Two', 2)
|
||||
nvim('set_var', 'THREE', 3)
|
||||
|
||||
local expected_ctx = {
|
||||
['regs'] = {
|
||||
{['rt'] = 1, ['rc'] = {'1'}, ['n'] = 49, ['ru'] = true},
|
||||
{['rt'] = 1, ['rc'] = {'2'}, ['n'] = 50},
|
||||
{['rt'] = 1, ['rc'] = {'3'}, ['n'] = 51},
|
||||
{['rc'] = {'hjkl'}, ['n'] = 97},
|
||||
},
|
||||
|
||||
['jumps'] = eval(([[
|
||||
filter(map(add(
|
||||
getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }),
|
||||
'filter(
|
||||
{ "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum },
|
||||
{ k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)')
|
||||
]]):gsub('\n', '')),
|
||||
|
||||
['buflist'] = eval([[
|
||||
filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)')
|
||||
]]),
|
||||
|
||||
['gvars'] = {{'one', 1}, {'Two', 2}, {'THREE', 3}},
|
||||
}
|
||||
|
||||
eq(expected_ctx, parse_context(nvim('get_context', ctx_items)))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_load_context', function()
|
||||
it('sets current editor state to given context dictionary', function()
|
||||
local ctx_items = {'regs', 'jumps', 'buflist', 'gvars'}
|
||||
eq({}, parse_context(nvim('get_context', ctx_items)))
|
||||
|
||||
nvim('set_var', 'one', 1)
|
||||
nvim('set_var', 'Two', 2)
|
||||
nvim('set_var', 'THREE', 3)
|
||||
local ctx = nvim('get_context', ctx_items)
|
||||
nvim('set_var', 'one', 'a')
|
||||
nvim('set_var', 'Two', 'b')
|
||||
nvim('set_var', 'THREE', 'c')
|
||||
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
|
||||
nvim('load_context', ctx)
|
||||
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('nvim_replace_termcodes', function()
|
||||
it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function()
|
||||
eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true))
|
||||
|
408
test/functional/eval/ctx_functions_spec.lua
Normal file
408
test/functional/eval/ctx_functions_spec.lua
Normal file
@ -0,0 +1,408 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
|
||||
local call = helpers.call
|
||||
local clear = helpers.clear
|
||||
local command = helpers.command
|
||||
local eq = helpers.eq
|
||||
local eval = helpers.eval
|
||||
local expect_err = helpers.expect_err
|
||||
local feed = helpers.feed
|
||||
local map = helpers.map
|
||||
local nvim = helpers.nvim
|
||||
local parse_context = helpers.parse_context
|
||||
local redir_exec = helpers.redir_exec
|
||||
local source = helpers.source
|
||||
local trim = helpers.trim
|
||||
local write_file = helpers.write_file
|
||||
|
||||
describe('context functions', function()
|
||||
local fname1 = 'Xtest-functional-eval-ctx1'
|
||||
local fname2 = 'Xtest-functional-eval-ctx2'
|
||||
local outofbounds =
|
||||
'Vim:E475: Invalid value for argument index: out of bounds'
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
write_file(fname1, "1\n2\n3")
|
||||
write_file(fname2, "a\nb\nc")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.remove(fname1)
|
||||
os.remove(fname2)
|
||||
end)
|
||||
|
||||
describe('ctxpush/ctxpop', function()
|
||||
it('saves and restores registers properly', function()
|
||||
local regs = {'1', '2', '3', 'a'}
|
||||
local vals = {'1', '2', '3', 'hjkl'}
|
||||
feed('i1<cr>2<cr>3<c-[>ddddddqahjklq')
|
||||
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
|
||||
call('ctxpush')
|
||||
call('ctxpush', {'regs'})
|
||||
|
||||
map(function(r) call('setreg', r, {}) end, regs)
|
||||
eq({'', '', '', ''},
|
||||
map(function(r) return trim(call('getreg', r)) end, regs))
|
||||
|
||||
call('ctxpop')
|
||||
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
|
||||
|
||||
map(function(r) call('setreg', r, {}) end, regs)
|
||||
eq({'', '', '', ''},
|
||||
map(function(r) return trim(call('getreg', r)) end, regs))
|
||||
|
||||
call('ctxpop')
|
||||
eq(vals, map(function(r) return trim(call('getreg', r)) end, regs))
|
||||
end)
|
||||
|
||||
it('saves and restores jumplist properly', function()
|
||||
command('edit '..fname1)
|
||||
feed('G')
|
||||
feed('gg')
|
||||
command('edit '..fname2)
|
||||
local jumplist = call('getjumplist')
|
||||
call('ctxpush')
|
||||
call('ctxpush', {'jumps'})
|
||||
|
||||
command('clearjumps')
|
||||
eq({{}, 0}, call('getjumplist'))
|
||||
|
||||
call('ctxpop')
|
||||
eq(jumplist, call('getjumplist'))
|
||||
|
||||
command('clearjumps')
|
||||
eq({{}, 0}, call('getjumplist'))
|
||||
|
||||
call('ctxpop')
|
||||
eq(jumplist, call('getjumplist'))
|
||||
end)
|
||||
|
||||
it('saves and restores buffer list properly', function()
|
||||
command('edit '..fname1)
|
||||
command('edit '..fname2)
|
||||
command('edit TEST')
|
||||
local buflist = call('map', call('getbufinfo'), 'v:val.name')
|
||||
call('ctxpush')
|
||||
call('ctxpush', {'buflist'})
|
||||
|
||||
command('%bwipeout')
|
||||
eq({''}, call('map', call('getbufinfo'), 'v:val.name'))
|
||||
|
||||
call('ctxpop')
|
||||
eq({'', unpack(buflist)}, call('map', call('getbufinfo'), 'v:val.name'))
|
||||
|
||||
command('%bwipeout')
|
||||
eq({''}, call('map', call('getbufinfo'), 'v:val.name'))
|
||||
|
||||
call('ctxpop')
|
||||
eq({'', unpack(buflist)}, call('map', call('getbufinfo'), 'v:val.name'))
|
||||
end)
|
||||
|
||||
it('saves and restores global variables properly', function()
|
||||
nvim('set_var', 'one', 1)
|
||||
nvim('set_var', 'Two', 2)
|
||||
nvim('set_var', 'THREE', 3)
|
||||
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
|
||||
call('ctxpush')
|
||||
call('ctxpush', {'gvars'})
|
||||
|
||||
nvim('del_var', 'one')
|
||||
nvim('del_var', 'Two')
|
||||
nvim('del_var', 'THREE')
|
||||
expect_err('E121: Undefined variable: g:one', eval, 'g:one')
|
||||
expect_err('E121: Undefined variable: g:Two', eval, 'g:Two')
|
||||
expect_err('E121: Undefined variable: g:THREE', eval, 'g:THREE')
|
||||
|
||||
call('ctxpop')
|
||||
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
|
||||
|
||||
nvim('del_var', 'one')
|
||||
nvim('del_var', 'Two')
|
||||
nvim('del_var', 'THREE')
|
||||
expect_err('E121: Undefined variable: g:one', eval, 'g:one')
|
||||
expect_err('E121: Undefined variable: g:Two', eval, 'g:Two')
|
||||
expect_err('E121: Undefined variable: g:THREE', eval, 'g:THREE')
|
||||
|
||||
call('ctxpop')
|
||||
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
|
||||
end)
|
||||
|
||||
it('saves and restores script functions properly', function()
|
||||
source([[
|
||||
function s:greet(name)
|
||||
echom 'Hello, '.a:name.'!'
|
||||
endfunction
|
||||
|
||||
function s:greet_all(name, ...)
|
||||
echom 'Hello, '.a:name.'!'
|
||||
for more in a:000
|
||||
echom 'Hello, '.more.'!'
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function Greet(name)
|
||||
call call('s:greet', [a:name])
|
||||
endfunction
|
||||
|
||||
function GreetAll(name, ...)
|
||||
call call('s:greet_all', extend([a:name], a:000))
|
||||
endfunction
|
||||
|
||||
function SaveSFuncs()
|
||||
call ctxpush(['sfuncs'])
|
||||
endfunction
|
||||
|
||||
function DeleteSFuncs()
|
||||
delfunction s:greet
|
||||
delfunction s:greet_all
|
||||
endfunction
|
||||
|
||||
function RestoreFuncs()
|
||||
call ctxpop()
|
||||
endfunction
|
||||
]])
|
||||
|
||||
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
|
||||
eq('\nHello, World!'..
|
||||
'\nHello, One!'..
|
||||
'\nHello, Two!'..
|
||||
'\nHello, Three!',
|
||||
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
|
||||
|
||||
call('SaveSFuncs')
|
||||
call('DeleteSFuncs')
|
||||
|
||||
eq('\nError detected while processing function Greet:'..
|
||||
'\nline 1:'..
|
||||
'\nE117: Unknown function: s:greet',
|
||||
redir_exec([[call Greet('World')]]))
|
||||
eq('\nError detected while processing function GreetAll:'..
|
||||
'\nline 1:'..
|
||||
'\nE117: Unknown function: s:greet_all',
|
||||
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
|
||||
|
||||
call('RestoreFuncs')
|
||||
|
||||
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
|
||||
eq('\nHello, World!'..
|
||||
'\nHello, One!'..
|
||||
'\nHello, Two!'..
|
||||
'\nHello, Three!',
|
||||
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
|
||||
end)
|
||||
|
||||
it('saves and restores functions properly', function()
|
||||
source([[
|
||||
function Greet(name)
|
||||
echom 'Hello, '.a:name.'!'
|
||||
endfunction
|
||||
|
||||
function GreetAll(name, ...)
|
||||
echom 'Hello, '.a:name.'!'
|
||||
for more in a:000
|
||||
echom 'Hello, '.more.'!'
|
||||
endfor
|
||||
endfunction
|
||||
]])
|
||||
|
||||
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
|
||||
eq('\nHello, World!'..
|
||||
'\nHello, One!'..
|
||||
'\nHello, Two!'..
|
||||
'\nHello, Three!',
|
||||
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
|
||||
|
||||
call('ctxpush', {'funcs'})
|
||||
command('delfunction Greet')
|
||||
command('delfunction GreetAll')
|
||||
|
||||
expect_err('Vim:E117: Unknown function: Greet', call, 'Greet', 'World')
|
||||
expect_err('Vim:E117: Unknown function: Greet', call, 'GreetAll',
|
||||
'World', 'One', 'Two', 'Three')
|
||||
|
||||
call('ctxpop')
|
||||
|
||||
eq('\nHello, World!', redir_exec([[call Greet('World')]]))
|
||||
eq('\nHello, World!'..
|
||||
'\nHello, One!'..
|
||||
'\nHello, Two!'..
|
||||
'\nHello, Three!',
|
||||
redir_exec([[call GreetAll('World', 'One', 'Two', 'Three')]]))
|
||||
end)
|
||||
|
||||
it('errors out when context stack is empty', function()
|
||||
local err = 'Vim:Context stack is empty'
|
||||
expect_err(err, call, 'ctxpop')
|
||||
expect_err(err, call, 'ctxpop')
|
||||
call('ctxpush')
|
||||
call('ctxpush')
|
||||
call('ctxpop')
|
||||
call('ctxpop')
|
||||
expect_err(err, call, 'ctxpop')
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('ctxsize()', function()
|
||||
it('returns context stack size', function()
|
||||
eq(0, call('ctxsize'))
|
||||
call('ctxpush')
|
||||
eq(1, call('ctxsize'))
|
||||
call('ctxpush')
|
||||
eq(2, call('ctxsize'))
|
||||
call('ctxpush')
|
||||
eq(3, call('ctxsize'))
|
||||
call('ctxpop')
|
||||
eq(2, call('ctxsize'))
|
||||
call('ctxpop')
|
||||
eq(1, call('ctxsize'))
|
||||
call('ctxpop')
|
||||
eq(0, call('ctxsize'))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('ctxget()', function()
|
||||
it('errors out when index is out of bounds', function()
|
||||
expect_err(outofbounds, call, 'ctxget')
|
||||
call('ctxpush')
|
||||
expect_err(outofbounds, call, 'ctxget', 1)
|
||||
call('ctxpop')
|
||||
expect_err(outofbounds, call, 'ctxget', 0)
|
||||
end)
|
||||
|
||||
it('returns context dictionary at index in context stack', function()
|
||||
feed('i1<cr>2<cr>3<c-[>ddddddqahjklq')
|
||||
command('edit! '..fname1)
|
||||
feed('G')
|
||||
feed('gg')
|
||||
command('edit '..fname2)
|
||||
nvim('set_var', 'one', 1)
|
||||
nvim('set_var', 'Two', 2)
|
||||
nvim('set_var', 'THREE', 3)
|
||||
|
||||
local with_regs = {
|
||||
['regs'] = {
|
||||
{['rt'] = 1, ['rc'] = {'1'}, ['n'] = 49, ['ru'] = true},
|
||||
{['rt'] = 1, ['rc'] = {'2'}, ['n'] = 50},
|
||||
{['rt'] = 1, ['rc'] = {'3'}, ['n'] = 51},
|
||||
{['rc'] = {'hjkl'}, ['n'] = 97},
|
||||
}
|
||||
}
|
||||
|
||||
local with_jumps = {
|
||||
['jumps'] = eval(([[
|
||||
filter(map(add(
|
||||
getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }),
|
||||
'filter(
|
||||
{ "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum },
|
||||
{ k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)')
|
||||
]]):gsub('\n', ''))
|
||||
}
|
||||
|
||||
local with_buflist = {
|
||||
['buflist'] = eval([[
|
||||
filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)')
|
||||
]])
|
||||
}
|
||||
|
||||
local with_gvars = {
|
||||
['gvars'] = {{'one', 1}, {'Two', 2}, {'THREE', 3}}
|
||||
}
|
||||
|
||||
local with_all = {
|
||||
['regs'] = with_regs['regs'],
|
||||
['jumps'] = with_jumps['jumps'],
|
||||
['buflist'] = with_buflist['buflist'],
|
||||
['gvars'] = with_gvars['gvars'],
|
||||
}
|
||||
|
||||
call('ctxpush')
|
||||
eq(with_all, parse_context(call('ctxget')))
|
||||
eq(with_all, parse_context(call('ctxget', 0)))
|
||||
|
||||
call('ctxpush', {'gvars'})
|
||||
eq(with_gvars, parse_context(call('ctxget')))
|
||||
eq(with_gvars, parse_context(call('ctxget', 0)))
|
||||
eq(with_all, parse_context(call('ctxget', 1)))
|
||||
|
||||
call('ctxpush', {'buflist'})
|
||||
eq(with_buflist, parse_context(call('ctxget')))
|
||||
eq(with_buflist, parse_context(call('ctxget', 0)))
|
||||
eq(with_gvars, parse_context(call('ctxget', 1)))
|
||||
eq(with_all, parse_context(call('ctxget', 2)))
|
||||
|
||||
call('ctxpush', {'jumps'})
|
||||
eq(with_jumps, parse_context(call('ctxget')))
|
||||
eq(with_jumps, parse_context(call('ctxget', 0)))
|
||||
eq(with_buflist, parse_context(call('ctxget', 1)))
|
||||
eq(with_gvars, parse_context(call('ctxget', 2)))
|
||||
eq(with_all, parse_context(call('ctxget', 3)))
|
||||
|
||||
call('ctxpush', {'regs'})
|
||||
eq(with_regs, parse_context(call('ctxget')))
|
||||
eq(with_regs, parse_context(call('ctxget', 0)))
|
||||
eq(with_jumps, parse_context(call('ctxget', 1)))
|
||||
eq(with_buflist, parse_context(call('ctxget', 2)))
|
||||
eq(with_gvars, parse_context(call('ctxget', 3)))
|
||||
eq(with_all, parse_context(call('ctxget', 4)))
|
||||
|
||||
call('ctxpop')
|
||||
eq(with_jumps, parse_context(call('ctxget')))
|
||||
eq(with_jumps, parse_context(call('ctxget', 0)))
|
||||
eq(with_buflist, parse_context(call('ctxget', 1)))
|
||||
eq(with_gvars, parse_context(call('ctxget', 2)))
|
||||
eq(with_all, parse_context(call('ctxget', 3)))
|
||||
|
||||
call('ctxpop')
|
||||
eq(with_buflist, parse_context(call('ctxget')))
|
||||
eq(with_buflist, parse_context(call('ctxget', 0)))
|
||||
eq(with_gvars, parse_context(call('ctxget', 1)))
|
||||
eq(with_all, parse_context(call('ctxget', 2)))
|
||||
|
||||
call('ctxpop')
|
||||
eq(with_gvars, parse_context(call('ctxget')))
|
||||
eq(with_gvars, parse_context(call('ctxget', 0)))
|
||||
eq(with_all, parse_context(call('ctxget', 1)))
|
||||
|
||||
call('ctxpop')
|
||||
eq(with_all, parse_context(call('ctxget')))
|
||||
eq(with_all, parse_context(call('ctxget', 0)))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('ctxset()', function()
|
||||
it('errors out when index is out of bounds', function()
|
||||
expect_err(outofbounds, call, 'ctxset', {dummy = 1})
|
||||
call('ctxpush')
|
||||
expect_err(outofbounds, call, 'ctxset', {dummy = 1}, 1)
|
||||
call('ctxpop')
|
||||
expect_err(outofbounds, call, 'ctxset', {dummy = 1}, 0)
|
||||
end)
|
||||
|
||||
it('sets context dictionary at index in context stack', function()
|
||||
nvim('set_var', 'one', 1)
|
||||
nvim('set_var', 'Two', 2)
|
||||
nvim('set_var', 'THREE', 3)
|
||||
call('ctxpush')
|
||||
local ctx1 = call('ctxget')
|
||||
nvim('set_var', 'one', 'a')
|
||||
nvim('set_var', 'Two', 'b')
|
||||
nvim('set_var', 'THREE', 'c')
|
||||
call('ctxpush')
|
||||
call('ctxpush')
|
||||
local ctx2 = call('ctxget')
|
||||
|
||||
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
|
||||
call('ctxset', ctx1)
|
||||
call('ctxset', ctx2, 2)
|
||||
call('ctxpop')
|
||||
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
|
||||
call('ctxpop')
|
||||
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
|
||||
nvim('set_var', 'one', 1.5)
|
||||
eq({1.5, 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
|
||||
call('ctxpop')
|
||||
eq({'a', 'b' ,'c'}, eval('[g:one, g:Two, g:THREE]'))
|
||||
end)
|
||||
end)
|
||||
end)
|
@ -13,6 +13,8 @@ local check_cores = global_helpers.check_cores
|
||||
local check_logs = global_helpers.check_logs
|
||||
local dedent = global_helpers.dedent
|
||||
local eq = global_helpers.eq
|
||||
local filter = global_helpers.filter
|
||||
local map = global_helpers.map
|
||||
local ok = global_helpers.ok
|
||||
local sleep = global_helpers.sleep
|
||||
local tbl_contains = global_helpers.tbl_contains
|
||||
@ -763,6 +765,22 @@ local function load_adjust(num)
|
||||
return math.ceil(num * load_factor)
|
||||
end
|
||||
|
||||
local function parse_context(ctx)
|
||||
local parsed = {}
|
||||
for _, item in ipairs({'regs', 'jumps', 'buflist', 'gvars'}) do
|
||||
parsed[item] = filter(function(v)
|
||||
return type(v) == 'table'
|
||||
end, nvim_call('msgpackparse', ctx[item]))
|
||||
end
|
||||
parsed['buflist'] = parsed['buflist'][1]
|
||||
return map(function(v)
|
||||
if #v == 0 then
|
||||
return nil
|
||||
end
|
||||
return v
|
||||
end, parsed)
|
||||
end
|
||||
|
||||
local module = {
|
||||
NIL = mpack.NIL,
|
||||
alter_slashes = alter_slashes,
|
||||
@ -810,6 +828,7 @@ local module = {
|
||||
nvim_prog_abs = nvim_prog_abs,
|
||||
nvim_set = nvim_set,
|
||||
os_name = os_name,
|
||||
parse_context = parse_context,
|
||||
pathroot = pathroot,
|
||||
pending_win32 = pending_win32,
|
||||
prepend_argv = prepend_argv,
|
||||
|
Loading…
Reference in New Issue
Block a user