diff options
author | Justin M. Keyes <justinkz@gmail.com> | 2019-07-01 03:42:03 +0200 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2019-07-27 16:36:57 +0200 |
commit | 411a06c8b6e92ead6a14d407d7ca61a44fba5bb6 (patch) | |
tree | 68d5923d5ae5ff0e69107ab5f841ecfa76b8bbc8 | |
parent | 997601d966dcc7b10c11eaae9c31bce2441c86da (diff) | |
download | rneovim-411a06c8b6e92ead6a14d407d7ca61a44fba5bb6.tar.gz rneovim-411a06c8b6e92ead6a14d407d7ca61a44fba5bb6.tar.bz2 rneovim-411a06c8b6e92ead6a14d407d7ca61a44fba5bb6.zip |
API: Context
-rw-r--r-- | src/nvim/api/vim.c | 58 | ||||
-rw-r--r-- | src/nvim/context.c | 317 | ||||
-rw-r--r-- | src/nvim/context.h | 42 | ||||
-rw-r--r-- | src/nvim/eval.c | 19 | ||||
-rw-r--r-- | src/nvim/eval.h | 7 | ||||
-rw-r--r-- | src/nvim/mark.c | 4 | ||||
-rw-r--r-- | src/nvim/ops.c | 58 | ||||
-rw-r--r-- | src/nvim/shada.c | 302 | ||||
-rw-r--r-- | src/nvim/shada.h | 11 |
9 files changed, 717 insertions, 101 deletions
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index dbe3b66fd5..3fd50ffaf8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -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,63 @@ 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; + } + } + } + } + + 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. diff --git a/src/nvim/context.c b/src/nvim/context.c new file mode 100644 index 0000000000..09f2d7bd76 --- /dev/null +++ b/src/nvim/context.c @@ -0,0 +1,317 @@ +// 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/option.h" +#include "nvim/shada.h" +#include "nvim/api/private/helpers.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "context.c.generated.h" +#endif + +int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBuflist | kCtxGVars); + +static ContextVec ctx_stack = KV_INITIAL_VALUE; + +/// Clears and frees the context stack +void free_ctx_stack(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); + } +} + +/// 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); + } +} + +/// 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 (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); +} + +/// 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))); + + 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); + } + } + + return types; +} diff --git a/src/nvim/context.h b/src/nvim/context.h new file mode 100644 index 0000000000..4e641adeda --- /dev/null +++ b/src/nvim/context.h @@ -0,0 +1,42 @@ +#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. +} 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, \ +} + +typedef enum { + kCtxRegs = 1, ///< Registers + kCtxJumps = 2, ///< Jumplist + kCtxBuflist = 4, ///< Buffer list + kCtxGVars = 8, ///< Global variables +} ContextTypeFlags; + +extern int kCtxAll; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "context.h.generated.h" +#endif + +#endif // NVIM_CONTEXT_H diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 0b943cedc7..cd89a22db3 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -303,15 +303,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 +5252,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); } @@ -15618,7 +15609,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)) { } } } @@ -23278,7 +23269,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 +23280,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 +23292,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; } } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 149dae688e..abe032a96e 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -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, diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 3736004527..9f357575d0 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -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) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 40c96eb333..a1f2a2f1b6 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -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); diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 4aafc669dc..d23985ca86 100644 --- a/src/nvim/shada.c +++ b/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); +} diff --git a/src/nvim/shada.h b/src/nvim/shada.h index 49986ac1c1..2a945a06bc 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -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 |