1 // This is an open source non-commercial project. Dear PVS-Studio, please check
2 // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3 
4 // Context: snapshot of the entire editor state as one big object/map
5 
6 #include "nvim/api/private/converter.h"
7 #include "nvim/api/private/helpers.h"
8 #include "nvim/api/vim.h"
9 #include "nvim/api/vimscript.h"
10 #include "nvim/context.h"
11 #include "nvim/eval/encode.h"
12 #include "nvim/ex_docmd.h"
13 #include "nvim/option.h"
14 #include "nvim/shada.h"
15 
16 #ifdef INCLUDE_GENERATED_DECLARATIONS
17 # include "context.c.generated.h"
18 #endif
19 
20 int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBufs | kCtxGVars | kCtxSFuncs
21                | kCtxFuncs);
22 
23 static ContextVec ctx_stack = KV_INITIAL_VALUE;
24 
25 /// Clears and frees the context stack
ctx_free_all(void)26 void ctx_free_all(void)
27 {
28   for (size_t i = 0; i < kv_size(ctx_stack); i++) {
29     ctx_free(&kv_A(ctx_stack, i));
30   }
31   kv_destroy(ctx_stack);
32 }
33 
34 /// Returns the size of the context stack.
ctx_size(void)35 size_t ctx_size(void)
36 {
37   return kv_size(ctx_stack);
38 }
39 
40 /// Returns pointer to Context object with given zero-based index from the top
41 /// of context stack or NULL if index is out of bounds.
ctx_get(size_t index)42 Context *ctx_get(size_t index)
43 {
44   if (index < kv_size(ctx_stack)) {
45     return &kv_Z(ctx_stack, index);
46   }
47   return NULL;
48 }
49 
50 /// Free resources used by Context object.
51 ///
52 /// param[in]  ctx  pointer to Context object to free.
ctx_free(Context * ctx)53 void ctx_free(Context *ctx)
54   FUNC_ATTR_NONNULL_ALL
55 {
56   if (ctx->regs.data) {
57     msgpack_sbuffer_destroy(&ctx->regs);
58   }
59   if (ctx->jumps.data) {
60     msgpack_sbuffer_destroy(&ctx->jumps);
61   }
62   if (ctx->bufs.data) {
63     msgpack_sbuffer_destroy(&ctx->bufs);
64   }
65   if (ctx->gvars.data) {
66     msgpack_sbuffer_destroy(&ctx->gvars);
67   }
68   if (ctx->funcs.items) {
69     api_free_array(ctx->funcs);
70   }
71 }
72 
73 /// Saves the editor state to a context.
74 ///
75 /// If "context" is NULL, pushes context on context stack.
76 /// Use "flags" to select particular types of context.
77 ///
78 /// @param  ctx    Save to this context, or push on context stack if NULL.
79 /// @param  flags  Flags, see ContextTypeFlags enum.
ctx_save(Context * ctx,const int flags)80 void ctx_save(Context *ctx, const int flags)
81 {
82   if (ctx == NULL) {
83     kv_push(ctx_stack, CONTEXT_INIT);
84     ctx = &kv_last(ctx_stack);
85   }
86 
87   if (flags & kCtxRegs) {
88     ctx_save_regs(ctx);
89   }
90 
91   if (flags & kCtxJumps) {
92     ctx_save_jumps(ctx);
93   }
94 
95   if (flags & kCtxBufs) {
96     ctx_save_bufs(ctx);
97   }
98 
99   if (flags & kCtxGVars) {
100     ctx_save_gvars(ctx);
101   }
102 
103   if (flags & kCtxFuncs) {
104     ctx_save_funcs(ctx, false);
105   } else if (flags & kCtxSFuncs) {
106     ctx_save_funcs(ctx, true);
107   }
108 }
109 
110 /// Restores the editor state from a context.
111 ///
112 /// If "context" is NULL, pops context from context stack.
113 /// Use "flags" to select particular types of context.
114 ///
115 /// @param  ctx    Restore from this context. Pop from context stack if NULL.
116 /// @param  flags  Flags, see ContextTypeFlags enum.
117 ///
118 /// @return true on success, false otherwise (i.e.: empty context stack).
ctx_restore(Context * ctx,const int flags)119 bool ctx_restore(Context *ctx, const int flags)
120 {
121   bool free_ctx = false;
122   if (ctx == NULL) {
123     if (ctx_stack.size == 0) {
124       return false;
125     }
126     ctx = &kv_pop(ctx_stack);
127     free_ctx = true;
128   }
129 
130   char_u *op_shada;
131   get_option_value("shada", NULL, &op_shada, OPT_GLOBAL);
132   set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
133 
134   if (flags & kCtxRegs) {
135     ctx_restore_regs(ctx);
136   }
137 
138   if (flags & kCtxJumps) {
139     ctx_restore_jumps(ctx);
140   }
141 
142   if (flags & kCtxBufs) {
143     ctx_restore_bufs(ctx);
144   }
145 
146   if (flags & kCtxGVars) {
147     ctx_restore_gvars(ctx);
148   }
149 
150   if (flags & kCtxFuncs) {
151     ctx_restore_funcs(ctx);
152   }
153 
154   if (free_ctx) {
155     ctx_free(ctx);
156   }
157 
158   set_option_value("shada", 0L, (char *)op_shada, OPT_GLOBAL);
159   xfree(op_shada);
160 
161   return true;
162 }
163 
164 /// Saves the global registers to a context.
165 ///
166 /// @param  ctx    Save to this context.
ctx_save_regs(Context * ctx)167 static inline void ctx_save_regs(Context *ctx)
168   FUNC_ATTR_NONNULL_ALL
169 {
170   msgpack_sbuffer_init(&ctx->regs);
171   shada_encode_regs(&ctx->regs);
172 }
173 
174 /// Restores the global registers from a context.
175 ///
176 /// @param  ctx   Restore from this context.
ctx_restore_regs(Context * ctx)177 static inline void ctx_restore_regs(Context *ctx)
178   FUNC_ATTR_NONNULL_ALL
179 {
180   shada_read_sbuf(&ctx->regs, kShaDaWantInfo | kShaDaForceit);
181 }
182 
183 /// Saves the jumplist to a context.
184 ///
185 /// @param  ctx  Save to this context.
ctx_save_jumps(Context * ctx)186 static inline void ctx_save_jumps(Context *ctx)
187   FUNC_ATTR_NONNULL_ALL
188 {
189   msgpack_sbuffer_init(&ctx->jumps);
190   shada_encode_jumps(&ctx->jumps);
191 }
192 
193 /// Restores the jumplist from a context.
194 ///
195 /// @param  ctx  Restore from this context.
ctx_restore_jumps(Context * ctx)196 static inline void ctx_restore_jumps(Context *ctx)
197   FUNC_ATTR_NONNULL_ALL
198 {
199   shada_read_sbuf(&ctx->jumps, kShaDaWantInfo | kShaDaForceit);
200 }
201 
202 /// Saves the buffer list to a context.
203 ///
204 /// @param  ctx  Save to this context.
ctx_save_bufs(Context * ctx)205 static inline void ctx_save_bufs(Context *ctx)
206   FUNC_ATTR_NONNULL_ALL
207 {
208   msgpack_sbuffer_init(&ctx->bufs);
209   shada_encode_buflist(&ctx->bufs);
210 }
211 
212 /// Restores the buffer list from a context.
213 ///
214 /// @param  ctx  Restore from this context.
ctx_restore_bufs(Context * ctx)215 static inline void ctx_restore_bufs(Context *ctx)
216   FUNC_ATTR_NONNULL_ALL
217 {
218   shada_read_sbuf(&ctx->bufs, kShaDaWantInfo | kShaDaForceit);
219 }
220 
221 /// Saves global variables to a context.
222 ///
223 /// @param  ctx  Save to this context.
ctx_save_gvars(Context * ctx)224 static inline void ctx_save_gvars(Context *ctx)
225   FUNC_ATTR_NONNULL_ALL
226 {
227   msgpack_sbuffer_init(&ctx->gvars);
228   shada_encode_gvars(&ctx->gvars);
229 }
230 
231 /// Restores global variables from a context.
232 ///
233 /// @param  ctx  Restore from this context.
ctx_restore_gvars(Context * ctx)234 static inline void ctx_restore_gvars(Context *ctx)
235   FUNC_ATTR_NONNULL_ALL
236 {
237   shada_read_sbuf(&ctx->gvars, kShaDaWantInfo | kShaDaForceit);
238 }
239 
240 /// Saves functions to a context.
241 ///
242 /// @param  ctx         Save to this context.
243 /// @param  scriptonly  Save script-local (s:) functions only.
ctx_save_funcs(Context * ctx,bool scriptonly)244 static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
245   FUNC_ATTR_NONNULL_ALL
246 {
247   ctx->funcs = (Array)ARRAY_DICT_INIT;
248   Error err = ERROR_INIT;
249 
250   HASHTAB_ITER(&func_hashtab, hi, {
251     const char_u *const name = hi->hi_key;
252     bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
253     bool isscript = (name[0] == K_SPECIAL);
254 
255     if (!islambda && (!scriptonly || isscript)) {
256       size_t cmd_len = sizeof("func! ") + STRLEN(name);
257       char *cmd = xmalloc(cmd_len);
258       snprintf(cmd, cmd_len, "func! %s", name);
259       String func_body = nvim_exec(cstr_as_string(cmd), true, &err);
260       xfree(cmd);
261       if (!ERROR_SET(&err)) {
262         ADD(ctx->funcs, STRING_OBJ(func_body));
263       }
264       api_clear_error(&err);
265     }
266   });
267 }
268 
269 /// Restores functions from a context.
270 ///
271 /// @param  ctx  Restore from this context.
ctx_restore_funcs(Context * ctx)272 static inline void ctx_restore_funcs(Context *ctx)
273   FUNC_ATTR_NONNULL_ALL
274 {
275   for (size_t i = 0; i < ctx->funcs.size; i++) {
276     do_cmdline_cmd(ctx->funcs.items[i].data.string.data);
277   }
278 }
279 
280 /// Convert msgpack_sbuffer to readfile()-style array.
281 ///
282 /// @param[in]  sbuf  msgpack_sbuffer to convert.
283 ///
284 /// @return readfile()-style array representation of "sbuf".
sbuf_to_array(msgpack_sbuffer sbuf)285 static inline Array sbuf_to_array(msgpack_sbuffer sbuf)
286 {
287   list_T *const list = tv_list_alloc(kListLenMayKnow);
288   tv_list_append_string(list, "", 0);
289   if (sbuf.size > 0) {
290     encode_list_write(list, sbuf.data, sbuf.size);
291   }
292 
293   typval_T list_tv = (typval_T) {
294     .v_lock = VAR_UNLOCKED,
295     .v_type = VAR_LIST,
296     .vval.v_list = list
297   };
298 
299   Array array = vim_to_object(&list_tv).data.array;
300   tv_clear(&list_tv);
301   return array;
302 }
303 
304 /// Convert readfile()-style array to msgpack_sbuffer.
305 ///
306 /// @param[in]  array  readfile()-style array to convert.
307 ///
308 /// @return msgpack_sbuffer with conversion result.
array_to_sbuf(Array array)309 static inline msgpack_sbuffer array_to_sbuf(Array array)
310 {
311   msgpack_sbuffer sbuf;
312   msgpack_sbuffer_init(&sbuf);
313 
314   typval_T list_tv;
315   Error err = ERROR_INIT;
316   object_to_vim(ARRAY_OBJ(array), &list_tv, &err);
317 
318   if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
319     emsg(_("E474: Failed to convert list to msgpack string buffer"));
320   }
321   sbuf.alloc = sbuf.size;
322 
323   tv_clear(&list_tv);
324   api_clear_error(&err);
325   return sbuf;
326 }
327 
328 /// Converts Context to Dictionary representation.
329 ///
330 /// @param[in]  ctx  Context to convert.
331 ///
332 /// @return Dictionary representing "ctx".
ctx_to_dict(Context * ctx)333 Dictionary ctx_to_dict(Context *ctx)
334   FUNC_ATTR_NONNULL_ALL
335 {
336   assert(ctx != NULL);
337 
338   Dictionary rv = ARRAY_DICT_INIT;
339 
340   PUT(rv, "regs", ARRAY_OBJ(sbuf_to_array(ctx->regs)));
341   PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps)));
342   PUT(rv, "bufs", ARRAY_OBJ(sbuf_to_array(ctx->bufs)));
343   PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars)));
344   PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs)));
345 
346   return rv;
347 }
348 
349 /// Converts Dictionary representation of Context back to Context object.
350 ///
351 /// @param[in]   dict  Context Dictionary representation.
352 /// @param[out]  ctx   Context object to store conversion result into.
353 ///
354 /// @return types of included context items.
ctx_from_dict(Dictionary dict,Context * ctx)355 int ctx_from_dict(Dictionary dict, Context *ctx)
356   FUNC_ATTR_NONNULL_ALL
357 {
358   assert(ctx != NULL);
359 
360   int types = 0;
361   for (size_t i = 0; i < dict.size; i++) {
362     KeyValuePair item = dict.items[i];
363     if (item.value.type != kObjectTypeArray) {
364       continue;
365     }
366     if (strequal(item.key.data, "regs")) {
367       types |= kCtxRegs;
368       ctx->regs = array_to_sbuf(item.value.data.array);
369     } else if (strequal(item.key.data, "jumps")) {
370       types |= kCtxJumps;
371       ctx->jumps = array_to_sbuf(item.value.data.array);
372     } else if (strequal(item.key.data, "bufs")) {
373       types |= kCtxBufs;
374       ctx->bufs = array_to_sbuf(item.value.data.array);
375     } else if (strequal(item.key.data, "gvars")) {
376       types |= kCtxGVars;
377       ctx->gvars = array_to_sbuf(item.value.data.array);
378     } else if (strequal(item.key.data, "funcs")) {
379       types |= kCtxFuncs;
380       ctx->funcs = copy_object(item.value).data.array;
381     }
382   }
383 
384   return types;
385 }
386