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