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 }