API: Context

This commit is contained in:
Justin M. Keyes 2019-07-01 03:42:03 +02:00
parent 997601d966
commit 411a06c8b6
9 changed files with 717 additions and 101 deletions

View File

@ -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.

317
src/nvim/context.c Normal file
View File

@ -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;
}

42
src/nvim/context.h Normal file
View File

@ -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

View File

@ -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, &reg, &is_unnamed);
reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &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;
}
}

View File

@ -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,

View File

@ -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) {

View File

@ -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);

View File

@ -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, &reg, &is_unnamed);
reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &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);
}

View File

@ -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