neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

context.c (8637B)


      1 // Context: snapshot of the entire editor state as one big object/map
      2 
      3 #include <assert.h>
      4 #include <stdbool.h>
      5 #include <stdint.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 
      9 #include "nvim/api/keysets_defs.h"
     10 #include "nvim/api/private/converter.h"
     11 #include "nvim/api/private/defs.h"
     12 #include "nvim/api/private/helpers.h"
     13 #include "nvim/api/vimscript.h"
     14 #include "nvim/context.h"
     15 #include "nvim/eval/encode.h"
     16 #include "nvim/eval/typval.h"
     17 #include "nvim/eval/typval_defs.h"
     18 #include "nvim/eval/userfunc.h"
     19 #include "nvim/ex_docmd.h"
     20 #include "nvim/hashtab.h"
     21 #include "nvim/keycodes.h"
     22 #include "nvim/memory.h"
     23 #include "nvim/memory_defs.h"
     24 #include "nvim/option.h"
     25 #include "nvim/option_defs.h"
     26 #include "nvim/shada.h"
     27 
     28 #include "context.c.generated.h"
     29 
     30 int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBufs | kCtxGVars | kCtxSFuncs
     31               | kCtxFuncs);
     32 
     33 static ContextVec ctx_stack = KV_INITIAL_VALUE;
     34 
     35 /// Clears and frees the context stack
     36 void ctx_free_all(void)
     37 {
     38  for (size_t i = 0; i < kv_size(ctx_stack); i++) {
     39    ctx_free(&kv_A(ctx_stack, i));
     40  }
     41  kv_destroy(ctx_stack);
     42 }
     43 
     44 /// Returns the size of the context stack.
     45 size_t ctx_size(void)
     46  FUNC_ATTR_PURE
     47 {
     48  return kv_size(ctx_stack);
     49 }
     50 
     51 /// Returns pointer to Context object with given zero-based index from the top
     52 /// of context stack or NULL if index is out of bounds.
     53 Context *ctx_get(size_t index)
     54  FUNC_ATTR_PURE
     55 {
     56  if (index < kv_size(ctx_stack)) {
     57    return &kv_Z(ctx_stack, index);
     58  }
     59  return NULL;
     60 }
     61 
     62 /// Free resources used by Context object.
     63 ///
     64 /// param[in]  ctx  pointer to Context object to free.
     65 void ctx_free(Context *ctx)
     66  FUNC_ATTR_NONNULL_ALL
     67 {
     68  api_free_string(ctx->regs);
     69  api_free_string(ctx->jumps);
     70  api_free_string(ctx->bufs);
     71  api_free_string(ctx->gvars);
     72  api_free_array(ctx->funcs);
     73 }
     74 
     75 /// Saves the editor state to a context.
     76 ///
     77 /// If "context" is NULL, pushes context on context stack.
     78 /// Use "flags" to select particular types of context.
     79 ///
     80 /// @param  ctx    Save to this context, or push on context stack if NULL.
     81 /// @param  flags  Flags, see ContextTypeFlags enum.
     82 void ctx_save(Context *ctx, const int flags)
     83 {
     84  if (ctx == NULL) {
     85    kv_push(ctx_stack, CONTEXT_INIT);
     86    ctx = &kv_last(ctx_stack);
     87  }
     88 
     89  if (flags & kCtxRegs) {
     90    ctx->regs = shada_encode_regs();
     91  }
     92 
     93  if (flags & kCtxJumps) {
     94    ctx->jumps = shada_encode_jumps();
     95  }
     96 
     97  if (flags & kCtxBufs) {
     98    ctx->bufs = shada_encode_buflist();
     99  }
    100 
    101  if (flags & kCtxGVars) {
    102    ctx->gvars = shada_encode_gvars();
    103  }
    104 
    105  if (flags & kCtxFuncs) {
    106    ctx_save_funcs(ctx, false);
    107  } else if (flags & kCtxSFuncs) {
    108    ctx_save_funcs(ctx, true);
    109  }
    110 }
    111 
    112 /// Restores the editor state from a context.
    113 ///
    114 /// If "context" is NULL, pops context from context stack.
    115 /// Use "flags" to select particular types of context.
    116 ///
    117 /// @param  ctx    Restore from this context. Pop from context stack if NULL.
    118 /// @param  flags  Flags, see ContextTypeFlags enum.
    119 ///
    120 /// @return true on success, false otherwise (i.e.: empty context stack).
    121 bool ctx_restore(Context *ctx, const int flags)
    122 {
    123  bool free_ctx = false;
    124  if (ctx == NULL) {
    125    if (ctx_stack.size == 0) {
    126      return false;
    127    }
    128    ctx = &kv_pop(ctx_stack);
    129    free_ctx = true;
    130  }
    131 
    132  OptVal op_shada = get_option_value(kOptShada, OPT_GLOBAL);
    133  set_option_value(kOptShada, STATIC_CSTR_AS_OPTVAL("!,'100,%"), OPT_GLOBAL);
    134 
    135  if (flags & kCtxRegs) {
    136    ctx_restore_regs(ctx);
    137  }
    138 
    139  if (flags & kCtxJumps) {
    140    ctx_restore_jumps(ctx);
    141  }
    142 
    143  if (flags & kCtxBufs) {
    144    ctx_restore_bufs(ctx);
    145  }
    146 
    147  if (flags & kCtxGVars) {
    148    ctx_restore_gvars(ctx);
    149  }
    150 
    151  if (flags & kCtxFuncs) {
    152    ctx_restore_funcs(ctx);
    153  }
    154 
    155  if (free_ctx) {
    156    ctx_free(ctx);
    157  }
    158 
    159  set_option_value(kOptShada, op_shada, OPT_GLOBAL);
    160  optval_free(op_shada);
    161 
    162  return true;
    163 }
    164 
    165 /// Restores the global registers from a context.
    166 ///
    167 /// @param  ctx   Restore from this context.
    168 static inline void ctx_restore_regs(Context *ctx)
    169  FUNC_ATTR_NONNULL_ALL
    170 {
    171  shada_read_string(ctx->regs, kShaDaWantInfo | kShaDaForceit);
    172 }
    173 
    174 /// Restores the jumplist from a context.
    175 ///
    176 /// @param  ctx  Restore from this context.
    177 static inline void ctx_restore_jumps(Context *ctx)
    178  FUNC_ATTR_NONNULL_ALL
    179 {
    180  shada_read_string(ctx->jumps, kShaDaWantInfo | kShaDaForceit);
    181 }
    182 
    183 /// Restores the buffer list from a context.
    184 ///
    185 /// @param  ctx  Restore from this context.
    186 static inline void ctx_restore_bufs(Context *ctx)
    187  FUNC_ATTR_NONNULL_ALL
    188 {
    189  shada_read_string(ctx->bufs, kShaDaWantInfo | kShaDaForceit);
    190 }
    191 
    192 /// Restores global variables from a context.
    193 ///
    194 /// @param  ctx  Restore from this context.
    195 static inline void ctx_restore_gvars(Context *ctx)
    196  FUNC_ATTR_NONNULL_ALL
    197 {
    198  shada_read_string(ctx->gvars, kShaDaWantInfo | kShaDaForceit);
    199 }
    200 
    201 /// Saves functions to a context.
    202 ///
    203 /// @param  ctx         Save to this context.
    204 /// @param  scriptonly  Save script-local (s:) functions only.
    205 static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
    206  FUNC_ATTR_NONNULL_ALL
    207 {
    208  ctx->funcs = (Array)ARRAY_DICT_INIT;
    209  Error err = ERROR_INIT;
    210 
    211  HASHTAB_ITER(func_tbl_get(), hi, {
    212    const char *const name = hi->hi_key;
    213    bool islambda = (strncmp(name, "<lambda>", 8) == 0);
    214    bool isscript = ((uint8_t)name[0] == K_SPECIAL);
    215 
    216    if (!islambda && (!scriptonly || isscript)) {
    217      size_t cmd_len = sizeof("func! ") + strlen(name);
    218      char *cmd = xmalloc(cmd_len);
    219      snprintf(cmd, cmd_len, "func! %s", name);
    220      Dict(exec_opts) opts = { .output = true };
    221      String func_body = exec_impl(VIML_INTERNAL_CALL, cstr_as_string(cmd),
    222                                   &opts, &err);
    223      xfree(cmd);
    224      if (!ERROR_SET(&err)) {
    225        ADD(ctx->funcs, STRING_OBJ(func_body));
    226      }
    227      api_clear_error(&err);
    228    }
    229  });
    230 }
    231 
    232 /// Restores functions from a context.
    233 ///
    234 /// @param  ctx  Restore from this context.
    235 static inline void ctx_restore_funcs(Context *ctx)
    236  FUNC_ATTR_NONNULL_ALL
    237 {
    238  for (size_t i = 0; i < ctx->funcs.size; i++) {
    239    do_cmdline_cmd(ctx->funcs.items[i].data.string.data);
    240  }
    241 }
    242 
    243 /// Convert readfile()-style array to String
    244 ///
    245 /// @param[in]  array  readfile()-style array to convert.
    246 /// @param[out]  err   Error object.
    247 ///
    248 /// @return String with conversion result.
    249 static inline String array_to_string(Array array, Error *err)
    250  FUNC_ATTR_NONNULL_ALL
    251 {
    252  String sbuf = STRING_INIT;
    253 
    254  typval_T list_tv;
    255  object_to_vim(ARRAY_OBJ(array), &list_tv, err);
    256 
    257  assert(list_tv.v_type == VAR_LIST);
    258  if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
    259    api_set_error(err, kErrorTypeException, "%s",
    260                  "E474: Failed to convert list to msgpack string buffer");
    261  }
    262 
    263  tv_clear(&list_tv);
    264  return sbuf;
    265 }
    266 
    267 /// Converts Context to Dict representation.
    268 ///
    269 /// @param[in]  ctx  Context to convert.
    270 ///
    271 /// @return Dict representing "ctx".
    272 Dict ctx_to_dict(Context *ctx, Arena *arena)
    273  FUNC_ATTR_NONNULL_ALL
    274 {
    275  assert(ctx != NULL);
    276 
    277  Dict rv = arena_dict(arena, 5);
    278 
    279  PUT_C(rv, "regs", ARRAY_OBJ(string_to_array(ctx->regs, false, arena)));
    280  PUT_C(rv, "jumps", ARRAY_OBJ(string_to_array(ctx->jumps, false, arena)));
    281  PUT_C(rv, "bufs", ARRAY_OBJ(string_to_array(ctx->bufs, false, arena)));
    282  PUT_C(rv, "gvars", ARRAY_OBJ(string_to_array(ctx->gvars, false, arena)));
    283  PUT_C(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs, arena)));
    284 
    285  return rv;
    286 }
    287 
    288 /// Converts Dict representation of Context back to Context object.
    289 ///
    290 /// @param[in]   dict  Context Dict representation.
    291 /// @param[out]  ctx   Context object to store conversion result into.
    292 /// @param[out]  err   Error object.
    293 ///
    294 /// @return types of included context items.
    295 int ctx_from_dict(Dict dict, Context *ctx, Error *err)
    296  FUNC_ATTR_NONNULL_ALL
    297 {
    298  assert(ctx != NULL);
    299 
    300  int types = 0;
    301  for (size_t i = 0; i < dict.size && !ERROR_SET(err); i++) {
    302    KeyValuePair item = dict.items[i];
    303    if (item.value.type != kObjectTypeArray) {
    304      continue;
    305    }
    306    if (strequal(item.key.data, "regs")) {
    307      types |= kCtxRegs;
    308      ctx->regs = array_to_string(item.value.data.array, err);
    309    } else if (strequal(item.key.data, "jumps")) {
    310      types |= kCtxJumps;
    311      ctx->jumps = array_to_string(item.value.data.array, err);
    312    } else if (strequal(item.key.data, "bufs")) {
    313      types |= kCtxBufs;
    314      ctx->bufs = array_to_string(item.value.data.array, err);
    315    } else if (strequal(item.key.data, "gvars")) {
    316      types |= kCtxGVars;
    317      ctx->gvars = array_to_string(item.value.data.array, err);
    318    } else if (strequal(item.key.data, "funcs")) {
    319      types |= kCtxFuncs;
    320      ctx->funcs = copy_object(item.value, NULL).data.array;
    321    }
    322  }
    323 
    324  return types;
    325 }