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 | |
18 | int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBuflist | kCtxGVars | kCtxSFuncs |
19 | | kCtxFuncs); |
20 | |
21 | static ContextVec ctx_stack = KV_INITIAL_VALUE; |
22 | |
23 | /// Clears and frees the context stack |
24 | void 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. |
33 | size_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. |
40 | Context *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. |
51 | void 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. |
78 | void 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). |
117 | bool 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. |
165 | static 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. |
175 | static 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. |
184 | static 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. |
194 | static 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. |
203 | static 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. |
213 | static 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. |
222 | static 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. |
232 | static 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. |
242 | static 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. |
270 | static 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". |
283 | static 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. |
307 | static 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". |
331 | Dictionary 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. |
353 | int 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 | |