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 | #include <assert.h> |
5 | #include <inttypes.h> |
6 | #include <stdbool.h> |
7 | #include <stdlib.h> |
8 | #include <string.h> |
9 | #include <limits.h> |
10 | |
11 | #include "nvim/api/vim.h" |
12 | #include "nvim/ascii.h" |
13 | #include "nvim/api/private/helpers.h" |
14 | #include "nvim/api/private/defs.h" |
15 | #include "nvim/api/private/dispatch.h" |
16 | #include "nvim/api/buffer.h" |
17 | #include "nvim/api/window.h" |
18 | #include "nvim/msgpack_rpc/channel.h" |
19 | #include "nvim/msgpack_rpc/helpers.h" |
20 | #include "nvim/lua/executor.h" |
21 | #include "nvim/vim.h" |
22 | #include "nvim/buffer.h" |
23 | #include "nvim/context.h" |
24 | #include "nvim/file_search.h" |
25 | #include "nvim/highlight.h" |
26 | #include "nvim/window.h" |
27 | #include "nvim/types.h" |
28 | #include "nvim/ex_docmd.h" |
29 | #include "nvim/screen.h" |
30 | #include "nvim/memline.h" |
31 | #include "nvim/mark.h" |
32 | #include "nvim/memory.h" |
33 | #include "nvim/message.h" |
34 | #include "nvim/popupmnu.h" |
35 | #include "nvim/edit.h" |
36 | #include "nvim/eval.h" |
37 | #include "nvim/eval/typval.h" |
38 | #include "nvim/fileio.h" |
39 | #include "nvim/ops.h" |
40 | #include "nvim/option.h" |
41 | #include "nvim/state.h" |
42 | #include "nvim/syntax.h" |
43 | #include "nvim/getchar.h" |
44 | #include "nvim/os/input.h" |
45 | #include "nvim/os/process.h" |
46 | #include "nvim/viml/parser/expressions.h" |
47 | #include "nvim/viml/parser/parser.h" |
48 | #include "nvim/ui.h" |
49 | |
50 | #define LINE_BUFFER_SIZE 4096 |
51 | |
52 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
53 | # include "api/vim.c.generated.h" |
54 | #endif |
55 | |
56 | // `msg_list` controls the collection of abort-causing non-exception errors, |
57 | // which would otherwise be ignored. This pattern is from do_cmdline(). |
58 | // |
59 | // TODO(bfredl): prepare error-handling at "top level" (nv_event). |
60 | #define TRY_WRAP(code) \ |
61 | do { \ |
62 | struct msglist **saved_msg_list = msg_list; \ |
63 | struct msglist *private_msg_list; \ |
64 | msg_list = &private_msg_list; \ |
65 | private_msg_list = NULL; \ |
66 | code \ |
67 | msg_list = saved_msg_list; /* Restore the exception context. */ \ |
68 | } while (0) |
69 | |
70 | void api_vim_init(void) |
71 | FUNC_API_NOEXPORT |
72 | { |
73 | namespace_ids = map_new(String, handle_T)(); |
74 | } |
75 | |
76 | void api_vim_free_all_mem(void) |
77 | FUNC_API_NOEXPORT |
78 | { |
79 | String name; |
80 | handle_T id; |
81 | map_foreach(namespace_ids, name, id, { |
82 | (void)id; |
83 | xfree(name.data); |
84 | }) |
85 | map_free(String, handle_T)(namespace_ids); |
86 | } |
87 | |
88 | /// Executes an ex-command. |
89 | /// |
90 | /// On execution error: fails with VimL error, does not update v:errmsg. |
91 | /// |
92 | /// @param command Ex-command string |
93 | /// @param[out] err Error details (Vim error), if any |
94 | void nvim_command(String command, Error *err) |
95 | FUNC_API_SINCE(1) |
96 | { |
97 | try_start(); |
98 | do_cmdline_cmd(command.data); |
99 | try_end(err); |
100 | } |
101 | |
102 | /// Gets a highlight definition by name. |
103 | /// |
104 | /// @param name Highlight group name |
105 | /// @param rgb Export RGB colors |
106 | /// @param[out] err Error details, if any |
107 | /// @return Highlight definition map |
108 | /// @see nvim_get_hl_by_id |
109 | Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) |
110 | FUNC_API_SINCE(3) |
111 | { |
112 | Dictionary result = ARRAY_DICT_INIT; |
113 | int id = syn_name2id((const char_u *)name.data); |
114 | |
115 | if (id == 0) { |
116 | api_set_error(err, kErrorTypeException, "Invalid highlight name: %s" , |
117 | name.data); |
118 | return result; |
119 | } |
120 | result = nvim_get_hl_by_id(id, rgb, err); |
121 | return result; |
122 | } |
123 | |
124 | /// Gets a highlight definition by id. |hlID()| |
125 | /// |
126 | /// @param hl_id Highlight id as returned by |hlID()| |
127 | /// @param rgb Export RGB colors |
128 | /// @param[out] err Error details, if any |
129 | /// @return Highlight definition map |
130 | /// @see nvim_get_hl_by_name |
131 | Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) |
132 | FUNC_API_SINCE(3) |
133 | { |
134 | Dictionary dic = ARRAY_DICT_INIT; |
135 | if (syn_get_final_id((int)hl_id) == 0) { |
136 | api_set_error(err, kErrorTypeException, |
137 | "Invalid highlight id: %" PRId64, hl_id); |
138 | return dic; |
139 | } |
140 | int attrcode = syn_id2attr((int)hl_id); |
141 | return hl_get_attr_by_id(attrcode, rgb, err); |
142 | } |
143 | |
144 | /// Sends input-keys to Nvim, subject to various quirks controlled by `mode` |
145 | /// flags. This is a blocking call, unlike |nvim_input()|. |
146 | /// |
147 | /// On execution error: does not fail, but updates v:errmsg. |
148 | /// |
149 | /// @param keys to be typed |
150 | /// @param mode behavior flags, see |feedkeys()| |
151 | /// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` |
152 | /// @see feedkeys() |
153 | /// @see vim_strsave_escape_csi |
154 | void nvim_feedkeys(String keys, String mode, Boolean escape_csi) |
155 | FUNC_API_SINCE(1) |
156 | { |
157 | bool remap = true; |
158 | bool insert = false; |
159 | bool typed = false; |
160 | bool execute = false; |
161 | bool dangerous = false; |
162 | |
163 | for (size_t i = 0; i < mode.size; ++i) { |
164 | switch (mode.data[i]) { |
165 | case 'n': remap = false; break; |
166 | case 'm': remap = true; break; |
167 | case 't': typed = true; break; |
168 | case 'i': insert = true; break; |
169 | case 'x': execute = true; break; |
170 | case '!': dangerous = true; break; |
171 | } |
172 | } |
173 | |
174 | if (keys.size == 0 && !execute) { |
175 | return; |
176 | } |
177 | |
178 | char *keys_esc; |
179 | if (escape_csi) { |
180 | // Need to escape K_SPECIAL and CSI before putting the string in the |
181 | // typeahead buffer. |
182 | keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data); |
183 | } else { |
184 | keys_esc = keys.data; |
185 | } |
186 | ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE), |
187 | insert ? 0 : typebuf.tb_len, !typed, false); |
188 | |
189 | if (escape_csi) { |
190 | xfree(keys_esc); |
191 | } |
192 | |
193 | if (vgetc_busy) { |
194 | typebuf_was_filled = true; |
195 | } |
196 | if (execute) { |
197 | int save_msg_scroll = msg_scroll; |
198 | |
199 | /* Avoid a 1 second delay when the keys start Insert mode. */ |
200 | msg_scroll = false; |
201 | if (!dangerous) { |
202 | ex_normal_busy++; |
203 | } |
204 | exec_normal(true); |
205 | if (!dangerous) { |
206 | ex_normal_busy--; |
207 | } |
208 | msg_scroll |= save_msg_scroll; |
209 | } |
210 | } |
211 | |
212 | /// Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a low-level |
213 | /// input buffer and the call is non-blocking (input is processed |
214 | /// asynchronously by the eventloop). |
215 | /// |
216 | /// On execution error: does not fail, but updates v:errmsg. |
217 | /// |
218 | /// @note |keycodes| like <CR> are translated, so "<" is special. |
219 | /// To input a literal "<", send <LT>. |
220 | /// |
221 | /// @note For mouse events use |nvim_input_mouse()|. The pseudokey form |
222 | /// "<LeftMouse><col,row>" is deprecated since |api-level| 6. |
223 | /// |
224 | /// @param keys to be typed |
225 | /// @return Number of bytes actually written (can be fewer than |
226 | /// requested if the buffer becomes full). |
227 | Integer nvim_input(String keys) |
228 | FUNC_API_SINCE(1) FUNC_API_FAST |
229 | { |
230 | return (Integer)input_enqueue(keys); |
231 | } |
232 | |
233 | /// Send mouse event from GUI. |
234 | /// |
235 | /// Non-blocking: does not wait on any result, but queues the event to be |
236 | /// processed soon by the event loop. |
237 | /// |
238 | /// @note Currently this doesn't support "scripting" multiple mouse events |
239 | /// by calling it multiple times in a loop: the intermediate mouse |
240 | /// positions will be ignored. It should be used to implement real-time |
241 | /// mouse input in a GUI. The deprecated pseudokey form |
242 | /// ("<LeftMouse><col,row>") of |nvim_input()| has the same limitiation. |
243 | /// |
244 | /// @param button Mouse button: one of "left", "right", "middle", "wheel". |
245 | /// @param action For ordinary buttons, one of "press", "drag", "release". |
246 | /// For the wheel, one of "up", "down", "left", "right". |
247 | /// @param modifier String of modifiers each represented by a single char. |
248 | /// The same specifiers are used as for a key press, except |
249 | /// that the "-" separator is optional, so "C-A-", "c-a" |
250 | /// and "CA" can all be used to specify Ctrl+Alt+click. |
251 | /// @param grid Grid number if the client uses |ui-multigrid|, else 0. |
252 | /// @param row Mouse row-position (zero-based, like redraw events) |
253 | /// @param col Mouse column-position (zero-based, like redraw events) |
254 | /// @param[out] err Error details, if any |
255 | void nvim_input_mouse(String button, String action, String modifier, |
256 | Integer grid, Integer row, Integer col, Error *err) |
257 | FUNC_API_SINCE(6) FUNC_API_FAST |
258 | { |
259 | if (button.data == NULL || action.data == NULL) { |
260 | goto error; |
261 | } |
262 | |
263 | int code = 0; |
264 | |
265 | if (strequal(button.data, "left" )) { |
266 | code = KE_LEFTMOUSE; |
267 | } else if (strequal(button.data, "middle" )) { |
268 | code = KE_MIDDLEMOUSE; |
269 | } else if (strequal(button.data, "right" )) { |
270 | code = KE_RIGHTMOUSE; |
271 | } else if (strequal(button.data, "wheel" )) { |
272 | code = KE_MOUSEDOWN; |
273 | } else { |
274 | goto error; |
275 | } |
276 | |
277 | if (code == KE_MOUSEDOWN) { |
278 | if (strequal(action.data, "down" )) { |
279 | code = KE_MOUSEUP; |
280 | } else if (strequal(action.data, "up" )) { |
281 | code = KE_MOUSEDOWN; |
282 | } else if (strequal(action.data, "left" )) { |
283 | code = KE_MOUSERIGHT; |
284 | } else if (strequal(action.data, "right" )) { |
285 | code = KE_MOUSELEFT; |
286 | } else { |
287 | goto error; |
288 | } |
289 | } else { |
290 | if (strequal(action.data, "press" )) { |
291 | // pass |
292 | } else if (strequal(action.data, "drag" )) { |
293 | code += KE_LEFTDRAG - KE_LEFTMOUSE; |
294 | } else if (strequal(action.data, "release" )) { |
295 | code += KE_LEFTRELEASE - KE_LEFTMOUSE; |
296 | } else { |
297 | goto error; |
298 | } |
299 | } |
300 | |
301 | int modmask = 0; |
302 | for (size_t i = 0; i < modifier.size; i++) { |
303 | char byte = modifier.data[i]; |
304 | if (byte == '-') { |
305 | continue; |
306 | } |
307 | int mod = name_to_mod_mask(byte); |
308 | if (mod == 0) { |
309 | api_set_error(err, kErrorTypeValidation, |
310 | "invalid modifier %c" , byte); |
311 | return; |
312 | } |
313 | modmask |= mod; |
314 | } |
315 | |
316 | input_enqueue_mouse(code, (uint8_t)modmask, (int)grid, (int)row, (int)col); |
317 | return; |
318 | |
319 | error: |
320 | api_set_error(err, kErrorTypeValidation, |
321 | "invalid button or action" ); |
322 | } |
323 | |
324 | /// Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a string with |
325 | /// the internal representation. |
326 | /// |
327 | /// @param str String to be converted. |
328 | /// @param from_part Legacy Vim parameter. Usually true. |
329 | /// @param do_lt Also translate <lt>. Ignored if `special` is false. |
330 | /// @param special Replace |keycodes|, e.g. <CR> becomes a "\n" char. |
331 | /// @see replace_termcodes |
332 | /// @see cpoptions |
333 | String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, |
334 | Boolean special) |
335 | FUNC_API_SINCE(1) |
336 | { |
337 | if (str.size == 0) { |
338 | // Empty string |
339 | return (String) { .data = NULL, .size = 0 }; |
340 | } |
341 | |
342 | char *ptr = NULL; |
343 | replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr, |
344 | from_part, do_lt, special, CPO_TO_CPO_FLAGS); |
345 | return cstr_as_string(ptr); |
346 | } |
347 | |
348 | /// Executes an ex-command and returns its (non-error) output. |
349 | /// Shell |:!| output is not captured. |
350 | /// |
351 | /// On execution error: fails with VimL error, does not update v:errmsg. |
352 | /// |
353 | /// @param command Ex-command string |
354 | /// @param[out] err Error details (Vim error), if any |
355 | String nvim_command_output(String command, Error *err) |
356 | FUNC_API_SINCE(1) |
357 | { |
358 | const int save_msg_silent = msg_silent; |
359 | garray_T *const save_capture_ga = capture_ga; |
360 | garray_T capture_local; |
361 | ga_init(&capture_local, 1, 80); |
362 | |
363 | try_start(); |
364 | msg_silent++; |
365 | capture_ga = &capture_local; |
366 | do_cmdline_cmd(command.data); |
367 | capture_ga = save_capture_ga; |
368 | msg_silent = save_msg_silent; |
369 | try_end(err); |
370 | |
371 | if (ERROR_SET(err)) { |
372 | goto theend; |
373 | } |
374 | |
375 | if (capture_local.ga_len > 1) { |
376 | String s = (String){ |
377 | .data = capture_local.ga_data, |
378 | .size = (size_t)capture_local.ga_len, |
379 | }; |
380 | // redir usually (except :echon) prepends a newline. |
381 | if (s.data[0] == '\n') { |
382 | memmove(s.data, s.data + 1, s.size - 1); |
383 | s.data[s.size - 1] = '\0'; |
384 | s.size = s.size - 1; |
385 | } |
386 | return s; // Caller will free the memory. |
387 | } |
388 | |
389 | theend: |
390 | ga_clear(&capture_local); |
391 | return (String)STRING_INIT; |
392 | } |
393 | |
394 | /// Evaluates a VimL expression (:help expression). |
395 | /// Dictionaries and Lists are recursively expanded. |
396 | /// |
397 | /// On execution error: fails with VimL error, does not update v:errmsg. |
398 | /// |
399 | /// @param expr VimL expression string |
400 | /// @param[out] err Error details, if any |
401 | /// @return Evaluation result or expanded object |
402 | Object nvim_eval(String expr, Error *err) |
403 | FUNC_API_SINCE(1) |
404 | { |
405 | static int recursive = 0; // recursion depth |
406 | Object rv = OBJECT_INIT; |
407 | |
408 | TRY_WRAP({ |
409 | // Initialize `force_abort` and `suppress_errthrow` at the top level. |
410 | if (!recursive) { |
411 | force_abort = false; |
412 | suppress_errthrow = false; |
413 | current_exception = NULL; |
414 | // `did_emsg` is set by emsg(), which cancels execution. |
415 | did_emsg = false; |
416 | } |
417 | recursive++; |
418 | try_start(); |
419 | |
420 | typval_T rettv; |
421 | int ok = eval0((char_u *)expr.data, &rettv, NULL, true); |
422 | |
423 | if (!try_end(err)) { |
424 | if (ok == FAIL) { |
425 | // Should never happen, try_end() should get the error. #8371 |
426 | api_set_error(err, kErrorTypeException, "Failed to evaluate expression" ); |
427 | } else { |
428 | rv = vim_to_object(&rettv); |
429 | } |
430 | } |
431 | |
432 | tv_clear(&rettv); |
433 | recursive--; |
434 | }); |
435 | |
436 | return rv; |
437 | } |
438 | |
439 | /// Execute Lua code. Parameters (if any) are available as `...` inside the |
440 | /// chunk. The chunk can return a value. |
441 | /// |
442 | /// Only statements are executed. To evaluate an expression, prefix it |
443 | /// with `return`: return my_function(...) |
444 | /// |
445 | /// @param code Lua code to execute |
446 | /// @param args Arguments to the code |
447 | /// @param[out] err Details of an error encountered while parsing |
448 | /// or executing the Lua code. |
449 | /// |
450 | /// @return Return value of Lua code if present or NIL. |
451 | Object nvim_execute_lua(String code, Array args, Error *err) |
452 | FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY |
453 | { |
454 | return executor_exec_lua_api(code, args, err); |
455 | } |
456 | |
457 | /// Calls a VimL function. |
458 | /// |
459 | /// @param fn Function name |
460 | /// @param args Function arguments |
461 | /// @param self `self` dict, or NULL for non-dict functions |
462 | /// @param[out] err Error details, if any |
463 | /// @return Result of the function call |
464 | static Object _call_function(String fn, Array args, dict_T *self, Error *err) |
465 | { |
466 | static int recursive = 0; // recursion depth |
467 | Object rv = OBJECT_INIT; |
468 | |
469 | if (args.size > MAX_FUNC_ARGS) { |
470 | api_set_error(err, kErrorTypeValidation, |
471 | "Function called with too many arguments" ); |
472 | return rv; |
473 | } |
474 | |
475 | // Convert the arguments in args from Object to typval_T values |
476 | typval_T vim_args[MAX_FUNC_ARGS + 1]; |
477 | size_t i = 0; // also used for freeing the variables |
478 | for (; i < args.size; i++) { |
479 | if (!object_to_vim(args.items[i], &vim_args[i], err)) { |
480 | goto free_vim_args; |
481 | } |
482 | } |
483 | |
484 | TRY_WRAP({ |
485 | // Initialize `force_abort` and `suppress_errthrow` at the top level. |
486 | if (!recursive) { |
487 | force_abort = false; |
488 | suppress_errthrow = false; |
489 | current_exception = NULL; |
490 | // `did_emsg` is set by emsg(), which cancels execution. |
491 | did_emsg = false; |
492 | } |
493 | recursive++; |
494 | try_start(); |
495 | typval_T rettv; |
496 | int dummy; |
497 | // call_func() retval is deceptive, ignore it. Instead we set `msg_list` |
498 | // (see above) to capture abort-causing non-exception errors. |
499 | (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, |
500 | vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, |
501 | &dummy, true, NULL, self); |
502 | if (!try_end(err)) { |
503 | rv = vim_to_object(&rettv); |
504 | } |
505 | tv_clear(&rettv); |
506 | recursive--; |
507 | }); |
508 | |
509 | free_vim_args: |
510 | while (i > 0) { |
511 | tv_clear(&vim_args[--i]); |
512 | } |
513 | |
514 | return rv; |
515 | } |
516 | |
517 | /// Calls a VimL function with the given arguments. |
518 | /// |
519 | /// On execution error: fails with VimL error, does not update v:errmsg. |
520 | /// |
521 | /// @param fn Function to call |
522 | /// @param args Function arguments packed in an Array |
523 | /// @param[out] err Error details, if any |
524 | /// @return Result of the function call |
525 | Object nvim_call_function(String fn, Array args, Error *err) |
526 | FUNC_API_SINCE(1) |
527 | { |
528 | return _call_function(fn, args, NULL, err); |
529 | } |
530 | |
531 | /// Calls a VimL |Dictionary-function| with the given arguments. |
532 | /// |
533 | /// On execution error: fails with VimL error, does not update v:errmsg. |
534 | /// |
535 | /// @param dict Dictionary, or String evaluating to a VimL |self| dict |
536 | /// @param fn Name of the function defined on the VimL dict |
537 | /// @param args Function arguments packed in an Array |
538 | /// @param[out] err Error details, if any |
539 | /// @return Result of the function call |
540 | Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) |
541 | FUNC_API_SINCE(4) |
542 | { |
543 | Object rv = OBJECT_INIT; |
544 | |
545 | typval_T rettv; |
546 | bool mustfree = false; |
547 | switch (dict.type) { |
548 | case kObjectTypeString: { |
549 | try_start(); |
550 | if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { |
551 | api_set_error(err, kErrorTypeException, |
552 | "Failed to evaluate dict expression" ); |
553 | } |
554 | if (try_end(err)) { |
555 | return rv; |
556 | } |
557 | // Evaluation of the string arg created a new dict or increased the |
558 | // refcount of a dict. Not necessary for a RPC dict. |
559 | mustfree = true; |
560 | break; |
561 | } |
562 | case kObjectTypeDictionary: { |
563 | if (!object_to_vim(dict, &rettv, err)) { |
564 | goto end; |
565 | } |
566 | break; |
567 | } |
568 | default: { |
569 | api_set_error(err, kErrorTypeValidation, |
570 | "dict argument type must be String or Dictionary" ); |
571 | return rv; |
572 | } |
573 | } |
574 | dict_T *self_dict = rettv.vval.v_dict; |
575 | if (rettv.v_type != VAR_DICT || !self_dict) { |
576 | api_set_error(err, kErrorTypeValidation, "dict not found" ); |
577 | goto end; |
578 | } |
579 | |
580 | if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { |
581 | dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); |
582 | if (di == NULL) { |
583 | api_set_error(err, kErrorTypeValidation, "Not found: %s" , fn.data); |
584 | goto end; |
585 | } |
586 | if (di->di_tv.v_type == VAR_PARTIAL) { |
587 | api_set_error(err, kErrorTypeValidation, |
588 | "partial function not supported" ); |
589 | goto end; |
590 | } |
591 | if (di->di_tv.v_type != VAR_FUNC) { |
592 | api_set_error(err, kErrorTypeValidation, "Not a function: %s" , fn.data); |
593 | goto end; |
594 | } |
595 | fn = (String) { |
596 | .data = (char *)di->di_tv.vval.v_string, |
597 | .size = strlen((char *)di->di_tv.vval.v_string), |
598 | }; |
599 | } |
600 | |
601 | if (!fn.data || fn.size < 1) { |
602 | api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name" ); |
603 | goto end; |
604 | } |
605 | |
606 | rv = _call_function(fn, args, self_dict, err); |
607 | end: |
608 | if (mustfree) { |
609 | tv_clear(&rettv); |
610 | } |
611 | |
612 | return rv; |
613 | } |
614 | |
615 | /// Calculates the number of display cells occupied by `text`. |
616 | /// <Tab> counts as one cell. |
617 | /// |
618 | /// @param text Some text |
619 | /// @param[out] err Error details, if any |
620 | /// @return Number of cells |
621 | Integer nvim_strwidth(String text, Error *err) |
622 | FUNC_API_SINCE(1) |
623 | { |
624 | if (text.size > INT_MAX) { |
625 | api_set_error(err, kErrorTypeValidation, "String is too long" ); |
626 | return 0; |
627 | } |
628 | |
629 | return (Integer)mb_string2cells((char_u *)text.data); |
630 | } |
631 | |
632 | /// Gets the paths contained in 'runtimepath'. |
633 | /// |
634 | /// @return List of paths |
635 | ArrayOf(String) nvim_list_runtime_paths(void) |
636 | FUNC_API_SINCE(1) |
637 | { |
638 | Array rv = ARRAY_DICT_INIT; |
639 | char_u *rtp = p_rtp; |
640 | |
641 | if (*rtp == NUL) { |
642 | // No paths |
643 | return rv; |
644 | } |
645 | |
646 | // Count the number of paths in rtp |
647 | while (*rtp != NUL) { |
648 | if (*rtp == ',') { |
649 | rv.size++; |
650 | } |
651 | rtp++; |
652 | } |
653 | rv.size++; |
654 | |
655 | // Allocate memory for the copies |
656 | rv.items = xmalloc(sizeof(*rv.items) * rv.size); |
657 | // Reset the position |
658 | rtp = p_rtp; |
659 | // Start copying |
660 | for (size_t i = 0; i < rv.size; i++) { |
661 | rv.items[i].type = kObjectTypeString; |
662 | rv.items[i].data.string.data = xmalloc(MAXPATHL); |
663 | // Copy the path from 'runtimepath' to rv.items[i] |
664 | size_t length = copy_option_part(&rtp, |
665 | (char_u *)rv.items[i].data.string.data, |
666 | MAXPATHL, |
667 | "," ); |
668 | rv.items[i].data.string.size = length; |
669 | } |
670 | |
671 | return rv; |
672 | } |
673 | |
674 | /// Changes the global working directory. |
675 | /// |
676 | /// @param dir Directory path |
677 | /// @param[out] err Error details, if any |
678 | void nvim_set_current_dir(String dir, Error *err) |
679 | FUNC_API_SINCE(1) |
680 | { |
681 | if (dir.size >= MAXPATHL) { |
682 | api_set_error(err, kErrorTypeValidation, "Directory name is too long" ); |
683 | return; |
684 | } |
685 | |
686 | char string[MAXPATHL]; |
687 | memcpy(string, dir.data, dir.size); |
688 | string[dir.size] = NUL; |
689 | |
690 | try_start(); |
691 | |
692 | if (vim_chdir((char_u *)string)) { |
693 | if (!try_end(err)) { |
694 | api_set_error(err, kErrorTypeException, "Failed to change directory" ); |
695 | } |
696 | return; |
697 | } |
698 | |
699 | post_chdir(kCdScopeGlobal, true); |
700 | try_end(err); |
701 | } |
702 | |
703 | /// Gets the current line. |
704 | /// |
705 | /// @param[out] err Error details, if any |
706 | /// @return Current line string |
707 | String nvim_get_current_line(Error *err) |
708 | FUNC_API_SINCE(1) |
709 | { |
710 | return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); |
711 | } |
712 | |
713 | /// Sets the current line. |
714 | /// |
715 | /// @param line Line contents |
716 | /// @param[out] err Error details, if any |
717 | void nvim_set_current_line(String line, Error *err) |
718 | FUNC_API_SINCE(1) |
719 | { |
720 | buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); |
721 | } |
722 | |
723 | /// Deletes the current line. |
724 | /// |
725 | /// @param[out] err Error details, if any |
726 | void nvim_del_current_line(Error *err) |
727 | FUNC_API_SINCE(1) |
728 | { |
729 | buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); |
730 | } |
731 | |
732 | /// Gets a global (g:) variable. |
733 | /// |
734 | /// @param name Variable name |
735 | /// @param[out] err Error details, if any |
736 | /// @return Variable value |
737 | Object nvim_get_var(String name, Error *err) |
738 | FUNC_API_SINCE(1) |
739 | { |
740 | return dict_get_value(&globvardict, name, err); |
741 | } |
742 | |
743 | /// Sets a global (g:) variable. |
744 | /// |
745 | /// @param name Variable name |
746 | /// @param value Variable value |
747 | /// @param[out] err Error details, if any |
748 | void nvim_set_var(String name, Object value, Error *err) |
749 | FUNC_API_SINCE(1) |
750 | { |
751 | dict_set_var(&globvardict, name, value, false, false, err); |
752 | } |
753 | |
754 | /// Removes a global (g:) variable. |
755 | /// |
756 | /// @param name Variable name |
757 | /// @param[out] err Error details, if any |
758 | void nvim_del_var(String name, Error *err) |
759 | FUNC_API_SINCE(1) |
760 | { |
761 | dict_set_var(&globvardict, name, NIL, true, false, err); |
762 | } |
763 | |
764 | /// @deprecated |
765 | /// @see nvim_set_var |
766 | /// @warning May return nil if there was no previous value |
767 | /// OR if previous value was `v:null`. |
768 | /// @return Old value or nil if there was no previous value. |
769 | Object vim_set_var(String name, Object value, Error *err) |
770 | { |
771 | return dict_set_var(&globvardict, name, value, false, true, err); |
772 | } |
773 | |
774 | /// @deprecated |
775 | /// @see nvim_del_var |
776 | Object vim_del_var(String name, Error *err) |
777 | { |
778 | return dict_set_var(&globvardict, name, NIL, true, true, err); |
779 | } |
780 | |
781 | /// Gets a v: variable. |
782 | /// |
783 | /// @param name Variable name |
784 | /// @param[out] err Error details, if any |
785 | /// @return Variable value |
786 | Object nvim_get_vvar(String name, Error *err) |
787 | FUNC_API_SINCE(1) |
788 | { |
789 | return dict_get_value(&vimvardict, name, err); |
790 | } |
791 | |
792 | /// Sets a v: variable, if it is not readonly. |
793 | /// |
794 | /// @param name Variable name |
795 | /// @param value Variable value |
796 | /// @param[out] err Error details, if any |
797 | void nvim_set_vvar(String name, Object value, Error *err) |
798 | FUNC_API_SINCE(6) |
799 | { |
800 | dict_set_var(&vimvardict, name, value, false, false, err); |
801 | } |
802 | |
803 | /// Gets an option value string. |
804 | /// |
805 | /// @param name Option name |
806 | /// @param[out] err Error details, if any |
807 | /// @return Option value (global) |
808 | Object nvim_get_option(String name, Error *err) |
809 | FUNC_API_SINCE(1) |
810 | { |
811 | return get_option_from(NULL, SREQ_GLOBAL, name, err); |
812 | } |
813 | |
814 | /// Sets an option value. |
815 | /// |
816 | /// @param channel_id |
817 | /// @param name Option name |
818 | /// @param value New option value |
819 | /// @param[out] err Error details, if any |
820 | void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) |
821 | FUNC_API_SINCE(1) |
822 | { |
823 | set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err); |
824 | } |
825 | |
826 | /// Writes a message to the Vim output buffer. Does not append "\n", the |
827 | /// message is buffered (won't display) until a linefeed is written. |
828 | /// |
829 | /// @param str Message |
830 | void nvim_out_write(String str) |
831 | FUNC_API_SINCE(1) |
832 | { |
833 | write_msg(str, false); |
834 | } |
835 | |
836 | /// Writes a message to the Vim error buffer. Does not append "\n", the |
837 | /// message is buffered (won't display) until a linefeed is written. |
838 | /// |
839 | /// @param str Message |
840 | void nvim_err_write(String str) |
841 | FUNC_API_SINCE(1) |
842 | { |
843 | write_msg(str, true); |
844 | } |
845 | |
846 | /// Writes a message to the Vim error buffer. Appends "\n", so the buffer is |
847 | /// flushed (and displayed). |
848 | /// |
849 | /// @param str Message |
850 | /// @see nvim_err_write() |
851 | void nvim_err_writeln(String str) |
852 | FUNC_API_SINCE(1) |
853 | { |
854 | nvim_err_write(str); |
855 | nvim_err_write((String) { .data = "\n" , .size = 1 }); |
856 | } |
857 | |
858 | /// Gets the current list of buffer handles |
859 | /// |
860 | /// Includes unlisted (unloaded/deleted) buffers, like `:ls!`. |
861 | /// Use |nvim_buf_is_loaded()| to check if a buffer is loaded. |
862 | /// |
863 | /// @return List of buffer handles |
864 | ArrayOf(Buffer) nvim_list_bufs(void) |
865 | FUNC_API_SINCE(1) |
866 | { |
867 | Array rv = ARRAY_DICT_INIT; |
868 | |
869 | FOR_ALL_BUFFERS(b) { |
870 | rv.size++; |
871 | } |
872 | |
873 | rv.items = xmalloc(sizeof(Object) * rv.size); |
874 | size_t i = 0; |
875 | |
876 | FOR_ALL_BUFFERS(b) { |
877 | rv.items[i++] = BUFFER_OBJ(b->handle); |
878 | } |
879 | |
880 | return rv; |
881 | } |
882 | |
883 | /// Gets the current buffer. |
884 | /// |
885 | /// @return Buffer handle |
886 | Buffer nvim_get_current_buf(void) |
887 | FUNC_API_SINCE(1) |
888 | { |
889 | return curbuf->handle; |
890 | } |
891 | |
892 | /// Sets the current buffer. |
893 | /// |
894 | /// @param buffer Buffer handle |
895 | /// @param[out] err Error details, if any |
896 | void nvim_set_current_buf(Buffer buffer, Error *err) |
897 | FUNC_API_SINCE(1) |
898 | { |
899 | buf_T *buf = find_buffer_by_handle(buffer, err); |
900 | |
901 | if (!buf) { |
902 | return; |
903 | } |
904 | |
905 | try_start(); |
906 | int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); |
907 | if (!try_end(err) && result == FAIL) { |
908 | api_set_error(err, |
909 | kErrorTypeException, |
910 | "Failed to switch to buffer %d" , |
911 | buffer); |
912 | } |
913 | } |
914 | |
915 | /// Gets the current list of window handles. |
916 | /// |
917 | /// @return List of window handles |
918 | ArrayOf(Window) nvim_list_wins(void) |
919 | FUNC_API_SINCE(1) |
920 | { |
921 | Array rv = ARRAY_DICT_INIT; |
922 | |
923 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
924 | rv.size++; |
925 | } |
926 | |
927 | rv.items = xmalloc(sizeof(Object) * rv.size); |
928 | size_t i = 0; |
929 | |
930 | FOR_ALL_TAB_WINDOWS(tp, wp) { |
931 | rv.items[i++] = WINDOW_OBJ(wp->handle); |
932 | } |
933 | |
934 | return rv; |
935 | } |
936 | |
937 | /// Gets the current window. |
938 | /// |
939 | /// @return Window handle |
940 | Window nvim_get_current_win(void) |
941 | FUNC_API_SINCE(1) |
942 | { |
943 | return curwin->handle; |
944 | } |
945 | |
946 | /// Sets the current window. |
947 | /// |
948 | /// @param window Window handle |
949 | /// @param[out] err Error details, if any |
950 | void nvim_set_current_win(Window window, Error *err) |
951 | FUNC_API_SINCE(1) |
952 | { |
953 | win_T *win = find_window_by_handle(window, err); |
954 | |
955 | if (!win) { |
956 | return; |
957 | } |
958 | |
959 | try_start(); |
960 | goto_tabpage_win(win_find_tabpage(win), win); |
961 | if (!try_end(err) && win != curwin) { |
962 | api_set_error(err, |
963 | kErrorTypeException, |
964 | "Failed to switch to window %d" , |
965 | window); |
966 | } |
967 | } |
968 | |
969 | /// Creates a new, empty, unnamed buffer. |
970 | /// |
971 | /// @param listed Sets 'buflisted' |
972 | /// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work |
973 | /// (always 'nomodified') |
974 | /// @param[out] err Error details, if any |
975 | /// @return Buffer handle, or 0 on error |
976 | /// |
977 | /// @see buf_open_scratch |
978 | Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) |
979 | FUNC_API_SINCE(6) |
980 | { |
981 | try_start(); |
982 | buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0, |
983 | BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0)); |
984 | try_end(err); |
985 | if (buf == NULL) { |
986 | goto fail; |
987 | } |
988 | |
989 | // Open the memline for the buffer. This will avoid spurious autocmds when |
990 | // a later nvim_buf_set_lines call would have needed to "open" the buffer. |
991 | try_start(); |
992 | block_autocmds(); |
993 | int status = ml_open(buf); |
994 | unblock_autocmds(); |
995 | try_end(err); |
996 | if (status == FAIL) { |
997 | goto fail; |
998 | } |
999 | |
1000 | if (scratch) { |
1001 | aco_save_T aco; |
1002 | aucmd_prepbuf(&aco, buf); |
1003 | set_option_value("bh" , 0L, "hide" , OPT_LOCAL); |
1004 | set_option_value("bt" , 0L, "nofile" , OPT_LOCAL); |
1005 | set_option_value("swf" , 0L, NULL, OPT_LOCAL); |
1006 | aucmd_restbuf(&aco); |
1007 | } |
1008 | return buf->b_fnum; |
1009 | |
1010 | fail: |
1011 | if (!ERROR_SET(err)) { |
1012 | api_set_error(err, kErrorTypeException, "Failed to create buffer" ); |
1013 | } |
1014 | return 0; |
1015 | } |
1016 | |
1017 | /// Open a new window. |
1018 | /// |
1019 | /// Currently this is used to open floating and external windows. |
1020 | /// Floats are windows that are drawn above the split layout, at some anchor |
1021 | /// position in some other window. Floats can be drawn internally or by external |
1022 | /// GUI with the |ui-multigrid| extension. External windows are only supported |
1023 | /// with multigrid GUIs, and are displayed as separate top-level windows. |
1024 | /// |
1025 | /// For a general overview of floats, see |api-floatwin|. |
1026 | /// |
1027 | /// Exactly one of `external` and `relative` must be specified. The `width` and |
1028 | /// `height` of the new window must be specified. |
1029 | /// |
1030 | /// With relative=editor (row=0,col=0) refers to the top-left corner of the |
1031 | /// screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right |
1032 | /// corner. Fractional values are allowed, but the builtin implementation |
1033 | /// (used by non-multigrid UIs) will always round down to nearest integer. |
1034 | /// |
1035 | /// Out-of-bounds values, and configurations that make the float not fit inside |
1036 | /// the main editor, are allowed. The builtin implementation truncates values |
1037 | /// so floats are fully within the main screen grid. External GUIs |
1038 | /// could let floats hover outside of the main window like a tooltip, but |
1039 | /// this should not be used to specify arbitrary WM screen positions. |
1040 | /// |
1041 | /// Example (Lua): window-relative float |
1042 | /// <pre> |
1043 | /// vim.api.nvim_open_win(0, false, |
1044 | /// {relative='win', row=3, col=3, width=12, height=3}) |
1045 | /// </pre> |
1046 | /// |
1047 | /// Example (Lua): buffer-relative float (travels as buffer is scrolled) |
1048 | /// <pre> |
1049 | /// vim.api.nvim_open_win(0, false, |
1050 | /// {relative='win', width=12, height=3, bufpos={100,10}}) |
1051 | /// </pre> |
1052 | /// |
1053 | /// @param buffer Buffer to display, or 0 for current buffer |
1054 | /// @param enter Enter the window (make it the current window) |
1055 | /// @param config Map defining the window configuration. Keys: |
1056 | /// - `relative`: Sets the window layout to "floating", placed at (row,col) |
1057 | /// coordinates relative to one of: |
1058 | /// - "editor" The global editor grid |
1059 | /// - "win" Window given by the `win` field, or current window by |
1060 | /// default. |
1061 | /// - "cursor" Cursor position in current window. |
1062 | /// - `win`: |window-ID| for relative="win". |
1063 | /// - `anchor`: Decides which corner of the float to place at (row,col): |
1064 | /// - "NW" northwest (default) |
1065 | /// - "NE" northeast |
1066 | /// - "SW" southwest |
1067 | /// - "SE" southeast |
1068 | /// - `width`: Window width (in character cells). Minimum of 1. |
1069 | /// - `height`: Window height (in character cells). Minimum of 1. |
1070 | /// - `bufpos`: Places float relative to buffer text (only when |
1071 | /// relative="win"). Takes a tuple of zero-indexed [line, column]. |
1072 | /// `row` and `col` if given are applied relative to this |
1073 | /// position, else they default to `row=1` and `col=0` |
1074 | /// (thus like a tooltip near the buffer text). |
1075 | /// - `row`: Row position in units of "screen cell height", may be fractional. |
1076 | /// - `col`: Column position in units of "screen cell width", may be |
1077 | /// fractional. |
1078 | /// - `focusable`: Enable focus by user actions (wincmds, mouse events). |
1079 | /// Defaults to true. Non-focusable windows can be entered by |
1080 | /// |nvim_set_current_win()|. |
1081 | /// - `external`: GUI should display the window as an external |
1082 | /// top-level window. Currently accepts no other positioning |
1083 | /// configuration together with this. |
1084 | /// - `style`: Configure the appearance of the window. Currently only takes |
1085 | /// one non-empty value: |
1086 | /// - "minimal" Nvim will display the window with many UI options |
1087 | /// disabled. This is useful when displaying a temporary |
1088 | /// float where the text should not be edited. Disables |
1089 | /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', |
1090 | /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' |
1091 | /// is changed to `auto`. The end-of-buffer region is hidden |
1092 | /// by setting `eob` flag of 'fillchars' to a space char, |
1093 | /// and clearing the |EndOfBuffer| region in 'winhighlight'. |
1094 | /// @param[out] err Error details, if any |
1095 | /// |
1096 | /// @return Window handle, or 0 on error |
1097 | Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, |
1098 | Error *err) |
1099 | FUNC_API_SINCE(6) |
1100 | { |
1101 | FloatConfig fconfig = FLOAT_CONFIG_INIT; |
1102 | if (!parse_float_config(config, &fconfig, false, err)) { |
1103 | return 0; |
1104 | } |
1105 | win_T *wp = win_new_float(NULL, fconfig, err); |
1106 | if (!wp) { |
1107 | return 0; |
1108 | } |
1109 | if (enter) { |
1110 | win_enter(wp, false); |
1111 | } |
1112 | if (buffer > 0) { |
1113 | nvim_win_set_buf(wp->handle, buffer, err); |
1114 | } |
1115 | |
1116 | if (fconfig.style == kWinStyleMinimal) { |
1117 | win_set_minimal_style(wp); |
1118 | didset_window_options(wp); |
1119 | } |
1120 | return wp->handle; |
1121 | } |
1122 | |
1123 | /// Gets the current list of tabpage handles. |
1124 | /// |
1125 | /// @return List of tabpage handles |
1126 | ArrayOf(Tabpage) nvim_list_tabpages(void) |
1127 | FUNC_API_SINCE(1) |
1128 | { |
1129 | Array rv = ARRAY_DICT_INIT; |
1130 | |
1131 | FOR_ALL_TABS(tp) { |
1132 | rv.size++; |
1133 | } |
1134 | |
1135 | rv.items = xmalloc(sizeof(Object) * rv.size); |
1136 | size_t i = 0; |
1137 | |
1138 | FOR_ALL_TABS(tp) { |
1139 | rv.items[i++] = TABPAGE_OBJ(tp->handle); |
1140 | } |
1141 | |
1142 | return rv; |
1143 | } |
1144 | |
1145 | /// Gets the current tabpage. |
1146 | /// |
1147 | /// @return Tabpage handle |
1148 | Tabpage nvim_get_current_tabpage(void) |
1149 | FUNC_API_SINCE(1) |
1150 | { |
1151 | return curtab->handle; |
1152 | } |
1153 | |
1154 | /// Sets the current tabpage. |
1155 | /// |
1156 | /// @param tabpage Tabpage handle |
1157 | /// @param[out] err Error details, if any |
1158 | void nvim_set_current_tabpage(Tabpage tabpage, Error *err) |
1159 | FUNC_API_SINCE(1) |
1160 | { |
1161 | tabpage_T *tp = find_tab_by_handle(tabpage, err); |
1162 | |
1163 | if (!tp) { |
1164 | return; |
1165 | } |
1166 | |
1167 | try_start(); |
1168 | goto_tabpage_tp(tp, true, true); |
1169 | if (!try_end(err) && tp != curtab) { |
1170 | api_set_error(err, |
1171 | kErrorTypeException, |
1172 | "Failed to switch to tabpage %d" , |
1173 | tabpage); |
1174 | } |
1175 | } |
1176 | |
1177 | /// Creates a new namespace, or gets an existing one. |
1178 | /// |
1179 | /// Namespaces are used for buffer highlights and virtual text, see |
1180 | /// |nvim_buf_add_highlight()| and |nvim_buf_set_virtual_text()|. |
1181 | /// |
1182 | /// Namespaces can be named or anonymous. If `name` matches an existing |
1183 | /// namespace, the associated id is returned. If `name` is an empty string |
1184 | /// a new, anonymous namespace is created. |
1185 | /// |
1186 | /// @param name Namespace name or empty string |
1187 | /// @return Namespace id |
1188 | Integer nvim_create_namespace(String name) |
1189 | FUNC_API_SINCE(5) |
1190 | { |
1191 | handle_T id = map_get(String, handle_T)(namespace_ids, name); |
1192 | if (id > 0) { |
1193 | return id; |
1194 | } |
1195 | id = next_namespace_id++; |
1196 | if (name.size > 0) { |
1197 | String name_alloc = copy_string(name); |
1198 | map_put(String, handle_T)(namespace_ids, name_alloc, id); |
1199 | } |
1200 | return (Integer)id; |
1201 | } |
1202 | |
1203 | /// Gets existing, non-anonymous namespaces. |
1204 | /// |
1205 | /// @return dict that maps from names to namespace ids. |
1206 | Dictionary nvim_get_namespaces(void) |
1207 | FUNC_API_SINCE(5) |
1208 | { |
1209 | Dictionary retval = ARRAY_DICT_INIT; |
1210 | String name; |
1211 | handle_T id; |
1212 | |
1213 | map_foreach(namespace_ids, name, id, { |
1214 | PUT(retval, name.data, INTEGER_OBJ(id)); |
1215 | }) |
1216 | |
1217 | return retval; |
1218 | } |
1219 | |
1220 | /// Pastes at cursor, in any mode. |
1221 | /// |
1222 | /// Invokes the `vim.paste` handler, which handles each mode appropriately. |
1223 | /// Sets redo/undo. Faster than |nvim_input()|. Lines break at LF ("\n"). |
1224 | /// |
1225 | /// Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` |
1226 | /// but do not affect the return value (which is strictly decided by |
1227 | /// `vim.paste()`). On error, subsequent calls are ignored ("drained") until |
1228 | /// the next paste is initiated (phase 1 or -1). |
1229 | /// |
1230 | /// @param data Multiline input. May be binary (containing NUL bytes). |
1231 | /// @param crlf Also break lines at CR and CRLF. |
1232 | /// @param phase -1: paste in a single call (i.e. without streaming). |
1233 | /// To "stream" a paste, call `nvim_paste` sequentially with |
1234 | /// these `phase` values: |
1235 | /// - 1: starts the paste (exactly once) |
1236 | /// - 2: continues the paste (zero or more times) |
1237 | /// - 3: ends the paste (exactly once) |
1238 | /// @param[out] err Error details, if any |
1239 | /// @return |
1240 | /// - true: Client may continue pasting. |
1241 | /// - false: Client must cancel the paste. |
1242 | Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) |
1243 | FUNC_API_SINCE(6) |
1244 | { |
1245 | static bool draining = false; |
1246 | bool cancel = false; |
1247 | |
1248 | if (phase < -1 || phase > 3) { |
1249 | api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase); |
1250 | return false; |
1251 | } |
1252 | Array args = ARRAY_DICT_INIT; |
1253 | Object rv = OBJECT_INIT; |
1254 | if (phase == -1 || phase == 1) { // Start of paste-stream. |
1255 | draining = false; |
1256 | } else if (draining) { |
1257 | // Skip remaining chunks. Report error only once per "stream". |
1258 | goto theend; |
1259 | } |
1260 | Array lines = string_to_array(data, crlf); |
1261 | ADD(args, ARRAY_OBJ(lines)); |
1262 | ADD(args, INTEGER_OBJ(phase)); |
1263 | rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)" ), args, |
1264 | err); |
1265 | if (ERROR_SET(err)) { |
1266 | draining = true; |
1267 | goto theend; |
1268 | } |
1269 | if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) { |
1270 | ResetRedobuff(); |
1271 | AppendCharToRedobuff('a'); // Dot-repeat. |
1272 | } |
1273 | // vim.paste() decides if client should cancel. Errors do NOT cancel: we |
1274 | // want to drain remaining chunks (rather than divert them to main input). |
1275 | cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean); |
1276 | if (!cancel && !(State & CMDLINE)) { // Dot-repeat. |
1277 | for (size_t i = 0; i < lines.size; i++) { |
1278 | String s = lines.items[i].data.string; |
1279 | assert(data.size <= INT_MAX); |
1280 | AppendToRedobuffLit((char_u *)s.data, (int)s.size); |
1281 | // readfile()-style: "\n" is indicated by presence of N+1 item. |
1282 | if (i + 1 < lines.size) { |
1283 | AppendCharToRedobuff(NL); |
1284 | } |
1285 | } |
1286 | } |
1287 | if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) { |
1288 | AppendCharToRedobuff(ESC); // Dot-repeat. |
1289 | } |
1290 | theend: |
1291 | api_free_object(rv); |
1292 | api_free_array(args); |
1293 | if (cancel || phase == -1 || phase == 3) { // End of paste-stream. |
1294 | draining = false; |
1295 | } |
1296 | |
1297 | return !cancel; |
1298 | } |
1299 | |
1300 | /// Puts text at cursor, in any mode. |
1301 | /// |
1302 | /// Compare |:put| and |p| which are always linewise. |
1303 | /// |
1304 | /// @param lines |readfile()|-style list of lines. |channel-lines| |
1305 | /// @param type Edit behavior: any |getregtype()| result, or: |
1306 | /// - "b" |blockwise-visual| mode (may include width, e.g. "b3") |
1307 | /// - "c" |characterwise| mode |
1308 | /// - "l" |linewise| mode |
1309 | /// - "" guess by contents, see |setreg()| |
1310 | /// @param after Insert after cursor (like |p|), or before (like |P|). |
1311 | /// @param follow Place cursor at end of inserted text. |
1312 | /// @param[out] err Error details, if any |
1313 | void nvim_put(ArrayOf(String) lines, String type, Boolean after, |
1314 | Boolean follow, Error *err) |
1315 | FUNC_API_SINCE(6) |
1316 | { |
1317 | yankreg_T *reg = xcalloc(sizeof(yankreg_T), 1); |
1318 | if (!prepare_yankreg_from_object(reg, type, lines.size)) { |
1319 | api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'" , type.data); |
1320 | goto cleanup; |
1321 | } |
1322 | if (lines.size == 0) { |
1323 | goto cleanup; // Nothing to do. |
1324 | } |
1325 | |
1326 | for (size_t i = 0; i < lines.size; i++) { |
1327 | if (lines.items[i].type != kObjectTypeString) { |
1328 | api_set_error(err, kErrorTypeValidation, |
1329 | "Invalid lines (expected array of strings)" ); |
1330 | goto cleanup; |
1331 | } |
1332 | String line = lines.items[i].data.string; |
1333 | reg->y_array[i] = (char_u *)xmemdupz(line.data, line.size); |
1334 | memchrsub(reg->y_array[i], NUL, NL, line.size); |
1335 | } |
1336 | |
1337 | finish_yankreg_from_object(reg, false); |
1338 | |
1339 | TRY_WRAP({ |
1340 | try_start(); |
1341 | bool VIsual_was_active = VIsual_active; |
1342 | msg_silent++; // Avoid "N more lines" message. |
1343 | do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0); |
1344 | msg_silent--; |
1345 | VIsual_active = VIsual_was_active; |
1346 | try_end(err); |
1347 | }); |
1348 | |
1349 | cleanup: |
1350 | free_register(reg); |
1351 | xfree(reg); |
1352 | } |
1353 | |
1354 | /// Subscribes to event broadcasts. |
1355 | /// |
1356 | /// @param channel_id Channel id (passed automatically by the dispatcher) |
1357 | /// @param event Event type string |
1358 | void nvim_subscribe(uint64_t channel_id, String event) |
1359 | FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY |
1360 | { |
1361 | size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN); |
1362 | char e[METHOD_MAXLEN + 1]; |
1363 | memcpy(e, event.data, length); |
1364 | e[length] = NUL; |
1365 | rpc_subscribe(channel_id, e); |
1366 | } |
1367 | |
1368 | /// Unsubscribes to event broadcasts. |
1369 | /// |
1370 | /// @param channel_id Channel id (passed automatically by the dispatcher) |
1371 | /// @param event Event type string |
1372 | void nvim_unsubscribe(uint64_t channel_id, String event) |
1373 | FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY |
1374 | { |
1375 | size_t length = (event.size < METHOD_MAXLEN ? |
1376 | event.size : |
1377 | METHOD_MAXLEN); |
1378 | char e[METHOD_MAXLEN + 1]; |
1379 | memcpy(e, event.data, length); |
1380 | e[length] = NUL; |
1381 | rpc_unsubscribe(channel_id, e); |
1382 | } |
1383 | |
1384 | /// Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or |
1385 | /// "#rrggbb" hexadecimal string. |
1386 | /// |
1387 | /// Example: |
1388 | /// <pre> |
1389 | /// :echo nvim_get_color_by_name("Pink") |
1390 | /// :echo nvim_get_color_by_name("#cbcbcb") |
1391 | /// </pre> |
1392 | /// |
1393 | /// @param name Color name or "#rrggbb" string |
1394 | /// @return 24-bit RGB value, or -1 for invalid argument. |
1395 | Integer nvim_get_color_by_name(String name) |
1396 | FUNC_API_SINCE(1) |
1397 | { |
1398 | return name_to_color((char_u *)name.data); |
1399 | } |
1400 | |
1401 | /// Returns a map of color names and RGB values. |
1402 | /// |
1403 | /// Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values |
1404 | /// (e.g. 65535). |
1405 | /// |
1406 | /// @return Map of color names and RGB values. |
1407 | Dictionary nvim_get_color_map(void) |
1408 | FUNC_API_SINCE(1) |
1409 | { |
1410 | Dictionary colors = ARRAY_DICT_INIT; |
1411 | |
1412 | for (int i = 0; color_name_table[i].name != NULL; i++) { |
1413 | PUT(colors, color_name_table[i].name, |
1414 | INTEGER_OBJ(color_name_table[i].color)); |
1415 | } |
1416 | return colors; |
1417 | } |
1418 | |
1419 | /// Gets a map of the current editor state. |
1420 | /// |
1421 | /// @param types Context types ("regs", "jumps", "buflist", "gvars", ...) |
1422 | /// to gather, or NIL for all (see |context-types|). |
1423 | /// |
1424 | /// @return map of global |context|. |
1425 | Dictionary nvim_get_context(Array types) |
1426 | FUNC_API_SINCE(6) |
1427 | { |
1428 | int int_types = 0; |
1429 | if (types.size == 1 && types.items[0].type == kObjectTypeNil) { |
1430 | int_types = kCtxAll; |
1431 | } else { |
1432 | for (size_t i = 0; i < types.size; i++) { |
1433 | if (types.items[i].type == kObjectTypeString) { |
1434 | const char *const current = types.items[i].data.string.data; |
1435 | if (strequal(current, "regs" )) { |
1436 | int_types |= kCtxRegs; |
1437 | } else if (strequal(current, "jumps" )) { |
1438 | int_types |= kCtxJumps; |
1439 | } else if (strequal(current, "buflist" )) { |
1440 | int_types |= kCtxBuflist; |
1441 | } else if (strequal(current, "gvars" )) { |
1442 | int_types |= kCtxGVars; |
1443 | } else if (strequal(current, "sfuncs" )) { |
1444 | int_types |= kCtxSFuncs; |
1445 | } else if (strequal(current, "funcs" )) { |
1446 | int_types |= kCtxFuncs; |
1447 | } |
1448 | } |
1449 | } |
1450 | } |
1451 | |
1452 | Context ctx = CONTEXT_INIT; |
1453 | ctx_save(&ctx, int_types); |
1454 | Dictionary dict = ctx_to_dict(&ctx); |
1455 | ctx_free(&ctx); |
1456 | return dict; |
1457 | } |
1458 | |
1459 | /// Sets the current editor state from the given |context| map. |
1460 | /// |
1461 | /// @param dict |Context| map. |
1462 | Object nvim_load_context(Dictionary dict) |
1463 | FUNC_API_SINCE(6) |
1464 | { |
1465 | Context ctx = CONTEXT_INIT; |
1466 | |
1467 | int save_did_emsg = did_emsg; |
1468 | did_emsg = false; |
1469 | |
1470 | ctx_from_dict(dict, &ctx); |
1471 | if (!did_emsg) { |
1472 | ctx_restore(&ctx, kCtxAll); |
1473 | } |
1474 | |
1475 | ctx_free(&ctx); |
1476 | |
1477 | did_emsg = save_did_emsg; |
1478 | return (Object)OBJECT_INIT; |
1479 | } |
1480 | |
1481 | /// Gets the current mode. |mode()| |
1482 | /// "blocking" is true if Nvim is waiting for input. |
1483 | /// |
1484 | /// @returns Dictionary { "mode": String, "blocking": Boolean } |
1485 | Dictionary nvim_get_mode(void) |
1486 | FUNC_API_SINCE(2) FUNC_API_FAST |
1487 | { |
1488 | Dictionary rv = ARRAY_DICT_INIT; |
1489 | char *modestr = get_mode(); |
1490 | bool blocked = input_blocking(); |
1491 | |
1492 | PUT(rv, "mode" , STRING_OBJ(cstr_as_string(modestr))); |
1493 | PUT(rv, "blocking" , BOOLEAN_OBJ(blocked)); |
1494 | |
1495 | return rv; |
1496 | } |
1497 | |
1498 | /// Gets a list of global (non-buffer-local) |mapping| definitions. |
1499 | /// |
1500 | /// @param mode Mode short-name ("n", "i", "v", ...) |
1501 | /// @returns Array of maparg()-like dictionaries describing mappings. |
1502 | /// The "buffer" key is always zero. |
1503 | ArrayOf(Dictionary) nvim_get_keymap(String mode) |
1504 | FUNC_API_SINCE(3) |
1505 | { |
1506 | return keymap_array(mode, NULL); |
1507 | } |
1508 | |
1509 | /// Sets a global |mapping| for the given mode. |
1510 | /// |
1511 | /// To set a buffer-local mapping, use |nvim_buf_set_keymap()|. |
1512 | /// |
1513 | /// Unlike |:map|, leading/trailing whitespace is accepted as part of the {lhs} |
1514 | /// or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. |
1515 | /// |
1516 | /// Example: |
1517 | /// <pre> |
1518 | /// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) |
1519 | /// </pre> |
1520 | /// |
1521 | /// is equivalent to: |
1522 | /// <pre> |
1523 | /// nmap <nowait> <Space><NL> <Nop> |
1524 | /// </pre> |
1525 | /// |
1526 | /// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …) |
1527 | /// or "!" for |:map!|, or empty string for |:map|. |
1528 | /// @param lhs Left-hand-side |{lhs}| of the mapping. |
1529 | /// @param rhs Right-hand-side |{rhs}| of the mapping. |
1530 | /// @param opts Optional parameters map. Accepts all |:map-arguments| |
1531 | /// as keys excluding |<buffer>| but including |noremap|. |
1532 | /// Values are Booleans. Unknown key is an error. |
1533 | /// @param[out] err Error details, if any. |
1534 | void nvim_set_keymap(String mode, String lhs, String rhs, |
1535 | Dictionary opts, Error *err) |
1536 | FUNC_API_SINCE(6) |
1537 | { |
1538 | modify_keymap(-1, false, mode, lhs, rhs, opts, err); |
1539 | } |
1540 | |
1541 | /// Unmaps a global |mapping| for the given mode. |
1542 | /// |
1543 | /// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|. |
1544 | /// |
1545 | /// @see |nvim_set_keymap()| |
1546 | void nvim_del_keymap(String mode, String lhs, Error *err) |
1547 | FUNC_API_SINCE(6) |
1548 | { |
1549 | nvim_buf_del_keymap(-1, mode, lhs, err); |
1550 | } |
1551 | |
1552 | /// Gets a map of global (non-buffer-local) Ex commands. |
1553 | /// |
1554 | /// Currently only |user-commands| are supported, not builtin Ex commands. |
1555 | /// |
1556 | /// @param opts Optional parameters. Currently only supports |
1557 | /// {"builtin":false} |
1558 | /// @param[out] err Error details, if any. |
1559 | /// |
1560 | /// @returns Map of maps describing commands. |
1561 | Dictionary nvim_get_commands(Dictionary opts, Error *err) |
1562 | FUNC_API_SINCE(4) |
1563 | { |
1564 | return nvim_buf_get_commands(-1, opts, err); |
1565 | } |
1566 | |
1567 | /// Returns a 2-tuple (Array), where item 0 is the current channel id and item |
1568 | /// 1 is the |api-metadata| map (Dictionary). |
1569 | /// |
1570 | /// @returns 2-tuple [{channel-id}, {api-metadata}] |
1571 | Array nvim_get_api_info(uint64_t channel_id) |
1572 | FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY |
1573 | { |
1574 | Array rv = ARRAY_DICT_INIT; |
1575 | |
1576 | assert(channel_id <= INT64_MAX); |
1577 | ADD(rv, INTEGER_OBJ((int64_t)channel_id)); |
1578 | ADD(rv, DICTIONARY_OBJ(api_metadata())); |
1579 | |
1580 | return rv; |
1581 | } |
1582 | |
1583 | /// Self-identifies the client. |
1584 | /// |
1585 | /// The client/plugin/application should call this after connecting, to provide |
1586 | /// hints about its identity and purpose, for debugging and orchestration. |
1587 | /// |
1588 | /// Can be called more than once; the caller should merge old info if |
1589 | /// appropriate. Example: library first identifies the channel, then a plugin |
1590 | /// using that library later identifies itself. |
1591 | /// |
1592 | /// @note "Something is better than nothing". You don't need to include all the |
1593 | /// fields. |
1594 | /// |
1595 | /// @param channel_id |
1596 | /// @param name Short name for the connected client |
1597 | /// @param version Dictionary describing the version, with these |
1598 | /// (optional) keys: |
1599 | /// - "major" major version (defaults to 0 if not set, for no release yet) |
1600 | /// - "minor" minor version |
1601 | /// - "patch" patch number |
1602 | /// - "prerelease" string describing a prerelease, like "dev" or "beta1" |
1603 | /// - "commit" hash or similar identifier of commit |
1604 | /// @param type Must be one of the following values. Client libraries should |
1605 | /// default to "remote" unless overridden by the user. |
1606 | /// - "remote" remote client connected to Nvim. |
1607 | /// - "ui" gui frontend |
1608 | /// - "embedder" application using Nvim as a component (for example, |
1609 | /// IDE/editor implementing a vim mode). |
1610 | /// - "host" plugin host, typically started by nvim |
1611 | /// - "plugin" single plugin, started by nvim |
1612 | /// @param methods Builtin methods in the client. For a host, this does not |
1613 | /// include plugin methods which will be discovered later. |
1614 | /// The key should be the method name, the values are dicts with |
1615 | /// these (optional) keys (more keys may be added in future |
1616 | /// versions of Nvim, thus unknown keys are ignored. Clients |
1617 | /// must only use keys defined in this or later versions of |
1618 | /// Nvim): |
1619 | /// - "async" if true, send as a notification. If false or unspecified, |
1620 | /// use a blocking request |
1621 | /// - "nargs" Number of arguments. Could be a single integer or an array |
1622 | /// of two integers, minimum and maximum inclusive. |
1623 | /// |
1624 | /// @param attributes Arbitrary string:string map of informal client properties. |
1625 | /// Suggested keys: |
1626 | /// - "website": Client homepage URL (e.g. GitHub repository) |
1627 | /// - "license": License description ("Apache 2", "GPLv3", "MIT", …) |
1628 | /// - "logo": URI or path to image, preferably small logo or icon. |
1629 | /// .png or .svg format is preferred. |
1630 | /// |
1631 | /// @param[out] err Error details, if any |
1632 | void nvim_set_client_info(uint64_t channel_id, String name, |
1633 | Dictionary version, String type, |
1634 | Dictionary methods, Dictionary attributes, |
1635 | Error *err) |
1636 | FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY |
1637 | { |
1638 | Dictionary info = ARRAY_DICT_INIT; |
1639 | PUT(info, "name" , copy_object(STRING_OBJ(name))); |
1640 | |
1641 | version = copy_dictionary(version); |
1642 | bool has_major = false; |
1643 | for (size_t i = 0; i < version.size; i++) { |
1644 | if (strequal(version.items[i].key.data, "major" )) { |
1645 | has_major = true; |
1646 | break; |
1647 | } |
1648 | } |
1649 | if (!has_major) { |
1650 | PUT(version, "major" , INTEGER_OBJ(0)); |
1651 | } |
1652 | PUT(info, "version" , DICTIONARY_OBJ(version)); |
1653 | |
1654 | PUT(info, "type" , copy_object(STRING_OBJ(type))); |
1655 | PUT(info, "methods" , DICTIONARY_OBJ(copy_dictionary(methods))); |
1656 | PUT(info, "attributes" , DICTIONARY_OBJ(copy_dictionary(attributes))); |
1657 | |
1658 | rpc_set_client_info(channel_id, info); |
1659 | } |
1660 | |
1661 | /// Get information about a channel. |
1662 | /// |
1663 | /// @returns Dictionary describing a channel, with these keys: |
1664 | /// - "stream" the stream underlying the channel |
1665 | /// - "stdio" stdin and stdout of this Nvim instance |
1666 | /// - "stderr" stderr of this Nvim instance |
1667 | /// - "socket" TCP/IP socket or named pipe |
1668 | /// - "job" job with communication over its stdio |
1669 | /// - "mode" how data received on the channel is interpreted |
1670 | /// - "bytes" send and receive raw bytes |
1671 | /// - "terminal" a |terminal| instance interprets ASCII sequences |
1672 | /// - "rpc" |RPC| communication on the channel is active |
1673 | /// - "pty" Name of pseudoterminal, if one is used (optional). |
1674 | /// On a POSIX system, this will be a device path like |
1675 | /// /dev/pts/1. Even if the name is unknown, the key will |
1676 | /// still be present to indicate a pty is used. This is |
1677 | /// currently the case when using winpty on windows. |
1678 | /// - "buffer" buffer with connected |terminal| instance (optional) |
1679 | /// - "client" information about the client on the other end of the |
1680 | /// RPC channel, if it has added it using |
1681 | /// |nvim_set_client_info()|. (optional) |
1682 | /// |
1683 | Dictionary nvim_get_chan_info(Integer chan, Error *err) |
1684 | FUNC_API_SINCE(4) |
1685 | { |
1686 | if (chan < 0) { |
1687 | return (Dictionary)ARRAY_DICT_INIT; |
1688 | } |
1689 | return channel_info((uint64_t)chan); |
1690 | } |
1691 | |
1692 | /// Get information about all open channels. |
1693 | /// |
1694 | /// @returns Array of Dictionaries, each describing a channel with |
1695 | /// the format specified at |nvim_get_chan_info()|. |
1696 | Array nvim_list_chans(void) |
1697 | FUNC_API_SINCE(4) |
1698 | { |
1699 | return channel_all_info(); |
1700 | } |
1701 | |
1702 | /// Calls many API methods atomically. |
1703 | /// |
1704 | /// This has two main usages: |
1705 | /// 1. To perform several requests from an async context atomically, i.e. |
1706 | /// without interleaving redraws, RPC requests from other clients, or user |
1707 | /// interactions (however API methods may trigger autocommands or event |
1708 | /// processing which have such side-effects, e.g. |:sleep| may wake timers). |
1709 | /// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. |
1710 | /// |
1711 | /// @param channel_id |
1712 | /// @param calls an array of calls, where each call is described by an array |
1713 | /// with two elements: the request name, and an array of arguments. |
1714 | /// @param[out] err Validation error details (malformed `calls` parameter), |
1715 | /// if any. Errors from batched calls are given in the return value. |
1716 | /// |
1717 | /// @return Array of two elements. The first is an array of return |
1718 | /// values. The second is NIL if all calls succeeded. If a call resulted in |
1719 | /// an error, it is a three-element array with the zero-based index of the call |
1720 | /// which resulted in an error, the error type and the error message. If an |
1721 | /// error occurred, the values from all preceding calls will still be returned. |
1722 | Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) |
1723 | FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY |
1724 | { |
1725 | Array rv = ARRAY_DICT_INIT; |
1726 | Array results = ARRAY_DICT_INIT; |
1727 | Error nested_error = ERROR_INIT; |
1728 | |
1729 | size_t i; // also used for freeing the variables |
1730 | for (i = 0; i < calls.size; i++) { |
1731 | if (calls.items[i].type != kObjectTypeArray) { |
1732 | api_set_error(err, |
1733 | kErrorTypeValidation, |
1734 | "Items in calls array must be arrays" ); |
1735 | goto validation_error; |
1736 | } |
1737 | Array call = calls.items[i].data.array; |
1738 | if (call.size != 2) { |
1739 | api_set_error(err, |
1740 | kErrorTypeValidation, |
1741 | "Items in calls array must be arrays of size 2" ); |
1742 | goto validation_error; |
1743 | } |
1744 | |
1745 | if (call.items[0].type != kObjectTypeString) { |
1746 | api_set_error(err, |
1747 | kErrorTypeValidation, |
1748 | "Name must be String" ); |
1749 | goto validation_error; |
1750 | } |
1751 | String name = call.items[0].data.string; |
1752 | |
1753 | if (call.items[1].type != kObjectTypeArray) { |
1754 | api_set_error(err, |
1755 | kErrorTypeValidation, |
1756 | "Args must be Array" ); |
1757 | goto validation_error; |
1758 | } |
1759 | Array args = call.items[1].data.array; |
1760 | |
1761 | MsgpackRpcRequestHandler handler = |
1762 | msgpack_rpc_get_handler_for(name.data, |
1763 | name.size, |
1764 | &nested_error); |
1765 | |
1766 | if (ERROR_SET(&nested_error)) { |
1767 | break; |
1768 | } |
1769 | Object result = handler.fn(channel_id, args, &nested_error); |
1770 | if (ERROR_SET(&nested_error)) { |
1771 | // error handled after loop |
1772 | break; |
1773 | } |
1774 | |
1775 | ADD(results, result); |
1776 | } |
1777 | |
1778 | ADD(rv, ARRAY_OBJ(results)); |
1779 | if (ERROR_SET(&nested_error)) { |
1780 | Array errval = ARRAY_DICT_INIT; |
1781 | ADD(errval, INTEGER_OBJ((Integer)i)); |
1782 | ADD(errval, INTEGER_OBJ(nested_error.type)); |
1783 | ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); |
1784 | ADD(rv, ARRAY_OBJ(errval)); |
1785 | } else { |
1786 | ADD(rv, NIL); |
1787 | } |
1788 | goto theend; |
1789 | |
1790 | validation_error: |
1791 | api_free_array(results); |
1792 | theend: |
1793 | api_clear_error(&nested_error); |
1794 | return rv; |
1795 | } |
1796 | |
1797 | typedef struct { |
1798 | ExprASTNode **node_p; |
1799 | Object *ret_node_p; |
1800 | } ExprASTConvStackItem; |
1801 | |
1802 | /// @cond DOXYGEN_NOT_A_FUNCTION |
1803 | typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; |
1804 | /// @endcond |
1805 | |
1806 | /// Parse a VimL expression. |
1807 | /// |
1808 | /// @param[in] expr Expression to parse. Always treated as a single line. |
1809 | /// @param[in] flags Flags: |
1810 | /// - "m" if multiple expressions in a row are allowed (only |
1811 | /// the first one will be parsed), |
1812 | /// - "E" if EOC tokens are not allowed (determines whether |
1813 | /// they will stop parsing process or be recognized as an |
1814 | /// operator/space, though also yielding an error). |
1815 | /// - "l" when needing to start parsing with lvalues for |
1816 | /// ":let" or ":for". |
1817 | /// Common flag sets: |
1818 | /// - "m" to parse like for ":echo". |
1819 | /// - "E" to parse like for "<C-r>=". |
1820 | /// - empty string for ":call". |
1821 | /// - "lm" to parse for ":let". |
1822 | /// @param[in] highlight If true, return value will also include "highlight" |
1823 | /// key containing array of 4-tuples (arrays) (Integer, |
1824 | /// Integer, Integer, String), where first three numbers |
1825 | /// define the highlighted region and represent line, |
1826 | /// starting column and ending column (latter exclusive: |
1827 | /// one should highlight region [start_col, end_col)). |
1828 | /// |
1829 | /// @return |
1830 | /// - AST: top-level dictionary with these keys: |
1831 | /// - "error": Dictionary with error, present only if parser saw some |
1832 | /// error. Contains the following keys: |
1833 | /// - "message": String, error message in printf format, translated. |
1834 | /// Must contain exactly one "%.*s". |
1835 | /// - "arg": String, error message argument. |
1836 | /// - "len": Amount of bytes successfully parsed. With flags equal to "" |
1837 | /// that should be equal to the length of expr string. |
1838 | /// (“Sucessfully parsed” here means “participated in AST |
1839 | /// creation”, not “till the first error”.) |
1840 | /// - "ast": AST, either nil or a dictionary with these keys: |
1841 | /// - "type": node type, one of the value names from ExprASTNodeType |
1842 | /// stringified without "kExprNode" prefix. |
1843 | /// - "start": a pair [line, column] describing where node is "started" |
1844 | /// where "line" is always 0 (will not be 0 if you will be |
1845 | /// using nvim_parse_viml() on e.g. ":let", but that is not |
1846 | /// present yet). Both elements are Integers. |
1847 | /// - "len": “length” of the node. This and "start" are there for |
1848 | /// debugging purposes primary (debugging parser and providing |
1849 | /// debug information). |
1850 | /// - "children": a list of nodes described in top/"ast". There always |
1851 | /// is zero, one or two children, key will not be present |
1852 | /// if node has no children. Maximum number of children |
1853 | /// may be found in node_maxchildren array. |
1854 | /// - Local values (present only for certain nodes): |
1855 | /// - "scope": a single Integer, specifies scope for "Option" and |
1856 | /// "PlainIdentifier" nodes. For "Option" it is one of |
1857 | /// ExprOptScope values, for "PlainIdentifier" it is one of |
1858 | /// ExprVarScope values. |
1859 | /// - "ident": identifier (without scope, if any), present for "Option", |
1860 | /// "PlainIdentifier", "PlainKey" and "Environment" nodes. |
1861 | /// - "name": Integer, register name (one character) or -1. Only present |
1862 | /// for "Register" nodes. |
1863 | /// - "cmp_type": String, comparison type, one of the value names from |
1864 | /// ExprComparisonType, stringified without "kExprCmp" |
1865 | /// prefix. Only present for "Comparison" nodes. |
1866 | /// - "ccs_strategy": String, case comparison strategy, one of the |
1867 | /// value names from ExprCaseCompareStrategy, |
1868 | /// stringified without "kCCStrategy" prefix. Only |
1869 | /// present for "Comparison" nodes. |
1870 | /// - "augmentation": String, augmentation type for "Assignment" nodes. |
1871 | /// Is either an empty string, "Add", "Subtract" or |
1872 | /// "Concat" for "=", "+=", "-=" or ".=" respectively. |
1873 | /// - "invert": Boolean, true if result of comparison needs to be |
1874 | /// inverted. Only present for "Comparison" nodes. |
1875 | /// - "ivalue": Integer, integer value for "Integer" nodes. |
1876 | /// - "fvalue": Float, floating-point value for "Float" nodes. |
1877 | /// - "svalue": String, value for "SingleQuotedString" and |
1878 | /// "DoubleQuotedString" nodes. |
1879 | /// @param[out] err Error details, if any |
1880 | Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, |
1881 | Error *err) |
1882 | FUNC_API_SINCE(4) FUNC_API_FAST |
1883 | { |
1884 | int pflags = 0; |
1885 | for (size_t i = 0 ; i < flags.size ; i++) { |
1886 | switch (flags.data[i]) { |
1887 | case 'm': { pflags |= kExprFlagsMulti; break; } |
1888 | case 'E': { pflags |= kExprFlagsDisallowEOC; break; } |
1889 | case 'l': { pflags |= kExprFlagsParseLet; break; } |
1890 | case NUL: { |
1891 | api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)" , |
1892 | (unsigned)flags.data[i]); |
1893 | return (Dictionary)ARRAY_DICT_INIT; |
1894 | } |
1895 | default: { |
1896 | api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)" , |
1897 | flags.data[i], (unsigned)flags.data[i]); |
1898 | return (Dictionary)ARRAY_DICT_INIT; |
1899 | } |
1900 | } |
1901 | } |
1902 | ParserLine plines[] = { |
1903 | { |
1904 | .data = expr.data, |
1905 | .size = expr.size, |
1906 | .allocated = false, |
1907 | }, |
1908 | { NULL, 0, false }, |
1909 | }; |
1910 | ParserLine *plines_p = plines; |
1911 | ParserHighlight colors; |
1912 | kvi_init(colors); |
1913 | ParserHighlight *const colors_p = (highlight ? &colors : NULL); |
1914 | ParserState pstate; |
1915 | viml_parser_init( |
1916 | &pstate, parser_simple_get_line, &plines_p, colors_p); |
1917 | ExprAST east = viml_pexpr_parse(&pstate, pflags); |
1918 | |
1919 | const size_t ret_size = ( |
1920 | 2 // "ast", "len" |
1921 | + (size_t)(east.err.msg != NULL) // "error" |
1922 | + (size_t)highlight // "highlight" |
1923 | + 0); |
1924 | Dictionary ret = { |
1925 | .items = xmalloc(ret_size * sizeof(ret.items[0])), |
1926 | .size = 0, |
1927 | .capacity = ret_size, |
1928 | }; |
1929 | ret.items[ret.size++] = (KeyValuePair) { |
1930 | .key = STATIC_CSTR_TO_STRING("ast" ), |
1931 | .value = NIL, |
1932 | }; |
1933 | ret.items[ret.size++] = (KeyValuePair) { |
1934 | .key = STATIC_CSTR_TO_STRING("len" ), |
1935 | .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 |
1936 | ? plines[0].size |
1937 | : pstate.pos.col)), |
1938 | }; |
1939 | if (east.err.msg != NULL) { |
1940 | Dictionary err_dict = { |
1941 | .items = xmalloc(2 * sizeof(err_dict.items[0])), |
1942 | .size = 2, |
1943 | .capacity = 2, |
1944 | }; |
1945 | err_dict.items[0] = (KeyValuePair) { |
1946 | .key = STATIC_CSTR_TO_STRING("message" ), |
1947 | .value = STRING_OBJ(cstr_to_string(east.err.msg)), |
1948 | }; |
1949 | if (east.err.arg == NULL) { |
1950 | err_dict.items[1] = (KeyValuePair) { |
1951 | .key = STATIC_CSTR_TO_STRING("arg" ), |
1952 | .value = STRING_OBJ(STRING_INIT), |
1953 | }; |
1954 | } else { |
1955 | err_dict.items[1] = (KeyValuePair) { |
1956 | .key = STATIC_CSTR_TO_STRING("arg" ), |
1957 | .value = STRING_OBJ(((String) { |
1958 | .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), |
1959 | .size = (size_t)east.err.arg_len, |
1960 | })), |
1961 | }; |
1962 | } |
1963 | ret.items[ret.size++] = (KeyValuePair) { |
1964 | .key = STATIC_CSTR_TO_STRING("error" ), |
1965 | .value = DICTIONARY_OBJ(err_dict), |
1966 | }; |
1967 | } |
1968 | if (highlight) { |
1969 | Array hl = (Array) { |
1970 | .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), |
1971 | .capacity = kv_size(colors), |
1972 | .size = kv_size(colors), |
1973 | }; |
1974 | for (size_t i = 0 ; i < kv_size(colors) ; i++) { |
1975 | const ParserHighlightChunk chunk = kv_A(colors, i); |
1976 | Array chunk_arr = (Array) { |
1977 | .items = xmalloc(4 * sizeof(chunk_arr.items[0])), |
1978 | .capacity = 4, |
1979 | .size = 4, |
1980 | }; |
1981 | chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); |
1982 | chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); |
1983 | chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); |
1984 | chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); |
1985 | hl.items[i] = ARRAY_OBJ(chunk_arr); |
1986 | } |
1987 | ret.items[ret.size++] = (KeyValuePair) { |
1988 | .key = STATIC_CSTR_TO_STRING("highlight" ), |
1989 | .value = ARRAY_OBJ(hl), |
1990 | }; |
1991 | } |
1992 | kvi_destroy(colors); |
1993 | |
1994 | // Walk over the AST, freeing nodes in process. |
1995 | ExprASTConvStack ast_conv_stack; |
1996 | kvi_init(ast_conv_stack); |
1997 | kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { |
1998 | .node_p = &east.root, |
1999 | .ret_node_p = &ret.items[0].value, |
2000 | })); |
2001 | while (kv_size(ast_conv_stack)) { |
2002 | ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); |
2003 | ExprASTNode *const node = *cur_item.node_p; |
2004 | if (node == NULL) { |
2005 | assert(kv_size(ast_conv_stack) == 1); |
2006 | kv_drop(ast_conv_stack, 1); |
2007 | } else { |
2008 | if (cur_item.ret_node_p->type == kObjectTypeNil) { |
2009 | const size_t ret_node_items_size = (size_t)( |
2010 | 3 // "type", "start" and "len" |
2011 | + (node->children != NULL) // "children" |
2012 | + (node->type == kExprNodeOption |
2013 | || node->type == kExprNodePlainIdentifier) // "scope" |
2014 | + (node->type == kExprNodeOption |
2015 | || node->type == kExprNodePlainIdentifier |
2016 | || node->type == kExprNodePlainKey |
2017 | || node->type == kExprNodeEnvironment) // "ident" |
2018 | + (node->type == kExprNodeRegister) // "name" |
2019 | + (3 // "cmp_type", "ccs_strategy", "invert" |
2020 | * (node->type == kExprNodeComparison)) |
2021 | + (node->type == kExprNodeInteger) // "ivalue" |
2022 | + (node->type == kExprNodeFloat) // "fvalue" |
2023 | + (node->type == kExprNodeDoubleQuotedString |
2024 | || node->type == kExprNodeSingleQuotedString) // "svalue" |
2025 | + (node->type == kExprNodeAssignment) // "augmentation" |
2026 | + 0); |
2027 | Dictionary ret_node = { |
2028 | .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), |
2029 | .capacity = ret_node_items_size, |
2030 | .size = 0, |
2031 | }; |
2032 | *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); |
2033 | } |
2034 | Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; |
2035 | if (node->children != NULL) { |
2036 | const size_t num_children = 1 + (node->children->next != NULL); |
2037 | Array children_array = { |
2038 | .items = xmalloc(num_children * sizeof(children_array.items[0])), |
2039 | .capacity = num_children, |
2040 | .size = num_children, |
2041 | }; |
2042 | for (size_t i = 0; i < num_children; i++) { |
2043 | children_array.items[i] = NIL; |
2044 | } |
2045 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2046 | .key = STATIC_CSTR_TO_STRING("children" ), |
2047 | .value = ARRAY_OBJ(children_array), |
2048 | }; |
2049 | kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { |
2050 | .node_p = &node->children, |
2051 | .ret_node_p = &children_array.items[0], |
2052 | })); |
2053 | } else if (node->next != NULL) { |
2054 | kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { |
2055 | .node_p = &node->next, |
2056 | .ret_node_p = cur_item.ret_node_p + 1, |
2057 | })); |
2058 | } else { |
2059 | kv_drop(ast_conv_stack, 1); |
2060 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2061 | .key = STATIC_CSTR_TO_STRING("type" ), |
2062 | .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), |
2063 | }; |
2064 | Array start_array = { |
2065 | .items = xmalloc(2 * sizeof(start_array.items[0])), |
2066 | .capacity = 2, |
2067 | .size = 2, |
2068 | }; |
2069 | start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); |
2070 | start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); |
2071 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2072 | .key = STATIC_CSTR_TO_STRING("start" ), |
2073 | .value = ARRAY_OBJ(start_array), |
2074 | }; |
2075 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2076 | .key = STATIC_CSTR_TO_STRING("len" ), |
2077 | .value = INTEGER_OBJ((Integer)node->len), |
2078 | }; |
2079 | switch (node->type) { |
2080 | case kExprNodeDoubleQuotedString: |
2081 | case kExprNodeSingleQuotedString: { |
2082 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2083 | .key = STATIC_CSTR_TO_STRING("svalue" ), |
2084 | .value = STRING_OBJ(((String) { |
2085 | .data = node->data.str.value, |
2086 | .size = node->data.str.size, |
2087 | })), |
2088 | }; |
2089 | break; |
2090 | } |
2091 | case kExprNodeOption: { |
2092 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2093 | .key = STATIC_CSTR_TO_STRING("scope" ), |
2094 | .value = INTEGER_OBJ(node->data.opt.scope), |
2095 | }; |
2096 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2097 | .key = STATIC_CSTR_TO_STRING("ident" ), |
2098 | .value = STRING_OBJ(((String) { |
2099 | .data = xmemdupz(node->data.opt.ident, |
2100 | node->data.opt.ident_len), |
2101 | .size = node->data.opt.ident_len, |
2102 | })), |
2103 | }; |
2104 | break; |
2105 | } |
2106 | case kExprNodePlainIdentifier: { |
2107 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2108 | .key = STATIC_CSTR_TO_STRING("scope" ), |
2109 | .value = INTEGER_OBJ(node->data.var.scope), |
2110 | }; |
2111 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2112 | .key = STATIC_CSTR_TO_STRING("ident" ), |
2113 | .value = STRING_OBJ(((String) { |
2114 | .data = xmemdupz(node->data.var.ident, |
2115 | node->data.var.ident_len), |
2116 | .size = node->data.var.ident_len, |
2117 | })), |
2118 | }; |
2119 | break; |
2120 | } |
2121 | case kExprNodePlainKey: { |
2122 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2123 | .key = STATIC_CSTR_TO_STRING("ident" ), |
2124 | .value = STRING_OBJ(((String) { |
2125 | .data = xmemdupz(node->data.var.ident, |
2126 | node->data.var.ident_len), |
2127 | .size = node->data.var.ident_len, |
2128 | })), |
2129 | }; |
2130 | break; |
2131 | } |
2132 | case kExprNodeEnvironment: { |
2133 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2134 | .key = STATIC_CSTR_TO_STRING("ident" ), |
2135 | .value = STRING_OBJ(((String) { |
2136 | .data = xmemdupz(node->data.env.ident, |
2137 | node->data.env.ident_len), |
2138 | .size = node->data.env.ident_len, |
2139 | })), |
2140 | }; |
2141 | break; |
2142 | } |
2143 | case kExprNodeRegister: { |
2144 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2145 | .key = STATIC_CSTR_TO_STRING("name" ), |
2146 | .value = INTEGER_OBJ(node->data.reg.name), |
2147 | }; |
2148 | break; |
2149 | } |
2150 | case kExprNodeComparison: { |
2151 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2152 | .key = STATIC_CSTR_TO_STRING("cmp_type" ), |
2153 | .value = STRING_OBJ(cstr_to_string( |
2154 | eltkn_cmp_type_tab[node->data.cmp.type])), |
2155 | }; |
2156 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2157 | .key = STATIC_CSTR_TO_STRING("ccs_strategy" ), |
2158 | .value = STRING_OBJ(cstr_to_string( |
2159 | ccs_tab[node->data.cmp.ccs])), |
2160 | }; |
2161 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2162 | .key = STATIC_CSTR_TO_STRING("invert" ), |
2163 | .value = BOOLEAN_OBJ(node->data.cmp.inv), |
2164 | }; |
2165 | break; |
2166 | } |
2167 | case kExprNodeFloat: { |
2168 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2169 | .key = STATIC_CSTR_TO_STRING("fvalue" ), |
2170 | .value = FLOAT_OBJ(node->data.flt.value), |
2171 | }; |
2172 | break; |
2173 | } |
2174 | case kExprNodeInteger: { |
2175 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2176 | .key = STATIC_CSTR_TO_STRING("ivalue" ), |
2177 | .value = INTEGER_OBJ((Integer)( |
2178 | node->data.num.value > API_INTEGER_MAX |
2179 | ? API_INTEGER_MAX |
2180 | : (Integer)node->data.num.value)), |
2181 | }; |
2182 | break; |
2183 | } |
2184 | case kExprNodeAssignment: { |
2185 | const ExprAssignmentType asgn_type = node->data.ass.type; |
2186 | ret_node->items[ret_node->size++] = (KeyValuePair) { |
2187 | .key = STATIC_CSTR_TO_STRING("augmentation" ), |
2188 | .value = STRING_OBJ( |
2189 | asgn_type == kExprAsgnPlain |
2190 | ? (String)STRING_INIT |
2191 | : cstr_to_string(expr_asgn_type_tab[asgn_type])), |
2192 | }; |
2193 | break; |
2194 | } |
2195 | case kExprNodeMissing: |
2196 | case kExprNodeOpMissing: |
2197 | case kExprNodeTernary: |
2198 | case kExprNodeTernaryValue: |
2199 | case kExprNodeSubscript: |
2200 | case kExprNodeListLiteral: |
2201 | case kExprNodeUnaryPlus: |
2202 | case kExprNodeBinaryPlus: |
2203 | case kExprNodeNested: |
2204 | case kExprNodeCall: |
2205 | case kExprNodeComplexIdentifier: |
2206 | case kExprNodeUnknownFigure: |
2207 | case kExprNodeLambda: |
2208 | case kExprNodeDictLiteral: |
2209 | case kExprNodeCurlyBracesIdentifier: |
2210 | case kExprNodeComma: |
2211 | case kExprNodeColon: |
2212 | case kExprNodeArrow: |
2213 | case kExprNodeConcat: |
2214 | case kExprNodeConcatOrSubscript: |
2215 | case kExprNodeOr: |
2216 | case kExprNodeAnd: |
2217 | case kExprNodeUnaryMinus: |
2218 | case kExprNodeBinaryMinus: |
2219 | case kExprNodeNot: |
2220 | case kExprNodeMultiplication: |
2221 | case kExprNodeDivision: |
2222 | case kExprNodeMod: { |
2223 | break; |
2224 | } |
2225 | } |
2226 | assert(cur_item.ret_node_p->data.dictionary.size |
2227 | == cur_item.ret_node_p->data.dictionary.capacity); |
2228 | xfree(*cur_item.node_p); |
2229 | *cur_item.node_p = NULL; |
2230 | } |
2231 | } |
2232 | } |
2233 | kvi_destroy(ast_conv_stack); |
2234 | |
2235 | assert(ret.size == ret.capacity); |
2236 | // Should be a no-op actually, leaving it in case non-nodes will need to be |
2237 | // freed later. |
2238 | viml_pexpr_free_ast(east); |
2239 | viml_parser_destroy(&pstate); |
2240 | return ret; |
2241 | } |
2242 | |
2243 | |
2244 | /// Writes a message to vim output or error buffer. The string is split |
2245 | /// and flushed after each newline. Incomplete lines are kept for writing |
2246 | /// later. |
2247 | /// |
2248 | /// @param message Message to write |
2249 | /// @param to_err true: message is an error (uses `emsg` instead of `msg`) |
2250 | static void write_msg(String message, bool to_err) |
2251 | { |
2252 | static size_t out_pos = 0, err_pos = 0; |
2253 | static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE]; |
2254 | |
2255 | #define PUSH_CHAR(i, pos, line_buf, msg) \ |
2256 | if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \ |
2257 | line_buf[pos] = NUL; \ |
2258 | msg((char_u *)line_buf); \ |
2259 | pos = 0; \ |
2260 | continue; \ |
2261 | } \ |
2262 | \ |
2263 | line_buf[pos++] = message.data[i]; |
2264 | |
2265 | ++no_wait_return; |
2266 | for (uint32_t i = 0; i < message.size; i++) { |
2267 | if (to_err) { |
2268 | PUSH_CHAR(i, err_pos, err_line_buf, emsg); |
2269 | } else { |
2270 | PUSH_CHAR(i, out_pos, out_line_buf, msg); |
2271 | } |
2272 | } |
2273 | --no_wait_return; |
2274 | msg_end(); |
2275 | } |
2276 | |
2277 | // Functions used for testing purposes |
2278 | |
2279 | /// Returns object given as argument. |
2280 | /// |
2281 | /// This API function is used for testing. One should not rely on its presence |
2282 | /// in plugins. |
2283 | /// |
2284 | /// @param[in] obj Object to return. |
2285 | /// |
2286 | /// @return its argument. |
2287 | Object nvim__id(Object obj) |
2288 | { |
2289 | return copy_object(obj); |
2290 | } |
2291 | |
2292 | /// Returns array given as argument. |
2293 | /// |
2294 | /// This API function is used for testing. One should not rely on its presence |
2295 | /// in plugins. |
2296 | /// |
2297 | /// @param[in] arr Array to return. |
2298 | /// |
2299 | /// @return its argument. |
2300 | Array nvim__id_array(Array arr) |
2301 | { |
2302 | return copy_object(ARRAY_OBJ(arr)).data.array; |
2303 | } |
2304 | |
2305 | /// Returns dictionary given as argument. |
2306 | /// |
2307 | /// This API function is used for testing. One should not rely on its presence |
2308 | /// in plugins. |
2309 | /// |
2310 | /// @param[in] dct Dictionary to return. |
2311 | /// |
2312 | /// @return its argument. |
2313 | Dictionary nvim__id_dictionary(Dictionary dct) |
2314 | { |
2315 | return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; |
2316 | } |
2317 | |
2318 | /// Returns floating-point value given as argument. |
2319 | /// |
2320 | /// This API function is used for testing. One should not rely on its presence |
2321 | /// in plugins. |
2322 | /// |
2323 | /// @param[in] flt Value to return. |
2324 | /// |
2325 | /// @return its argument. |
2326 | Float nvim__id_float(Float flt) |
2327 | { |
2328 | return flt; |
2329 | } |
2330 | |
2331 | /// Gets internal stats. |
2332 | /// |
2333 | /// @return Map of various internal stats. |
2334 | Dictionary nvim__stats(void) |
2335 | { |
2336 | Dictionary rv = ARRAY_DICT_INIT; |
2337 | PUT(rv, "fsync" , INTEGER_OBJ(g_stats.fsync)); |
2338 | PUT(rv, "redraw" , INTEGER_OBJ(g_stats.redraw)); |
2339 | return rv; |
2340 | } |
2341 | |
2342 | /// Gets a list of dictionaries representing attached UIs. |
2343 | /// |
2344 | /// @return Array of UI dictionaries, each with these keys: |
2345 | /// - "height" Requested height of the UI |
2346 | /// - "width" Requested width of the UI |
2347 | /// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|) |
2348 | /// - "ext_..." Requested UI extensions, see |ui-option| |
2349 | /// - "chan" Channel id of remote UI (not present for TUI) |
2350 | Array nvim_list_uis(void) |
2351 | FUNC_API_SINCE(4) |
2352 | { |
2353 | return ui_array(); |
2354 | } |
2355 | |
2356 | /// Gets the immediate children of process `pid`. |
2357 | /// |
2358 | /// @return Array of child process ids, empty if process not found. |
2359 | Array nvim_get_proc_children(Integer pid, Error *err) |
2360 | FUNC_API_SINCE(4) |
2361 | { |
2362 | Array rvobj = ARRAY_DICT_INIT; |
2363 | int *proc_list = NULL; |
2364 | |
2365 | if (pid <= 0 || pid > INT_MAX) { |
2366 | api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); |
2367 | goto end; |
2368 | } |
2369 | |
2370 | size_t proc_count; |
2371 | int rv = os_proc_children((int)pid, &proc_list, &proc_count); |
2372 | if (rv != 0) { |
2373 | // syscall failed (possibly because of kernel options), try shelling out. |
2374 | DLOG("fallback to vim._os_proc_children()" ); |
2375 | Array a = ARRAY_DICT_INIT; |
2376 | ADD(a, INTEGER_OBJ(pid)); |
2377 | String s = cstr_to_string("return vim._os_proc_children(select(1, ...))" ); |
2378 | Object o = nvim_execute_lua(s, a, err); |
2379 | api_free_string(s); |
2380 | api_free_array(a); |
2381 | if (o.type == kObjectTypeArray) { |
2382 | rvobj = o.data.array; |
2383 | } else if (!ERROR_SET(err)) { |
2384 | api_set_error(err, kErrorTypeException, |
2385 | "Failed to get process children. pid=%" PRId64 " error=%d" , |
2386 | pid, rv); |
2387 | } |
2388 | goto end; |
2389 | } |
2390 | |
2391 | for (size_t i = 0; i < proc_count; i++) { |
2392 | ADD(rvobj, INTEGER_OBJ(proc_list[i])); |
2393 | } |
2394 | |
2395 | end: |
2396 | xfree(proc_list); |
2397 | return rvobj; |
2398 | } |
2399 | |
2400 | /// Gets info describing process `pid`. |
2401 | /// |
2402 | /// @return Map of process properties, or NIL if process not found. |
2403 | Object nvim_get_proc(Integer pid, Error *err) |
2404 | FUNC_API_SINCE(4) |
2405 | { |
2406 | Object rvobj = OBJECT_INIT; |
2407 | rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; |
2408 | rvobj.type = kObjectTypeDictionary; |
2409 | |
2410 | if (pid <= 0 || pid > INT_MAX) { |
2411 | api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); |
2412 | return NIL; |
2413 | } |
2414 | #ifdef WIN32 |
2415 | rvobj.data.dictionary = os_proc_info((int)pid); |
2416 | if (rvobj.data.dictionary.size == 0) { // Process not found. |
2417 | return NIL; |
2418 | } |
2419 | #else |
2420 | // Cross-platform process info APIs are miserable, so use `ps` instead. |
2421 | Array a = ARRAY_DICT_INIT; |
2422 | ADD(a, INTEGER_OBJ(pid)); |
2423 | String s = cstr_to_string("return vim._os_proc_info(select(1, ...))" ); |
2424 | Object o = nvim_execute_lua(s, a, err); |
2425 | api_free_string(s); |
2426 | api_free_array(a); |
2427 | if (o.type == kObjectTypeArray && o.data.array.size == 0) { |
2428 | return NIL; // Process not found. |
2429 | } else if (o.type == kObjectTypeDictionary) { |
2430 | rvobj.data.dictionary = o.data.dictionary; |
2431 | } else if (!ERROR_SET(err)) { |
2432 | api_set_error(err, kErrorTypeException, |
2433 | "Failed to get process info. pid=%" PRId64, pid); |
2434 | } |
2435 | #endif |
2436 | return rvobj; |
2437 | } |
2438 | |
2439 | /// Selects an item in the completion popupmenu. |
2440 | /// |
2441 | /// If |ins-completion| is not active this API call is silently ignored. |
2442 | /// Useful for an external UI using |ui-popupmenu| to control the popupmenu |
2443 | /// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to |
2444 | /// ensure the mapping doesn't end completion mode. |
2445 | /// |
2446 | /// @param item Index (zero-based) of the item to select. Value of -1 selects |
2447 | /// nothing and restores the original text. |
2448 | /// @param insert Whether the selection should be inserted in the buffer. |
2449 | /// @param finish Finish the completion and dismiss the popupmenu. Implies |
2450 | /// `insert`. |
2451 | /// @param opts Optional parameters. Reserved for future use. |
2452 | /// @param[out] err Error details, if any |
2453 | void (Integer item, Boolean insert, Boolean finish, |
2454 | Dictionary opts, Error *err) |
2455 | FUNC_API_SINCE(6) |
2456 | { |
2457 | if (opts.size > 0) { |
2458 | api_set_error(err, kErrorTypeValidation, "opts dict isn't empty" ); |
2459 | return; |
2460 | } |
2461 | |
2462 | if (finish) { |
2463 | insert = true; |
2464 | } |
2465 | |
2466 | pum_ext_select_item((int)item, insert, finish); |
2467 | } |
2468 | |
2469 | /// NB: if your UI doesn't use hlstate, this will not return hlstate first time |
2470 | Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) |
2471 | { |
2472 | Array ret = ARRAY_DICT_INIT; |
2473 | |
2474 | // TODO(bfredl): if grid == 0 we should read from the compositor's buffer. |
2475 | // The only problem is that it does not yet exist. |
2476 | ScreenGrid *g = &default_grid; |
2477 | if (grid == pum_grid.handle) { |
2478 | g = &pum_grid; |
2479 | } else if (grid > 1) { |
2480 | win_T *wp = get_win_by_grid_handle((handle_T)grid); |
2481 | if (wp != NULL && wp->w_grid.chars != NULL) { |
2482 | g = &wp->w_grid; |
2483 | } else { |
2484 | api_set_error(err, kErrorTypeValidation, |
2485 | "No grid with the given handle" ); |
2486 | return ret; |
2487 | } |
2488 | } |
2489 | |
2490 | if (row < 0 || row >= g->Rows |
2491 | || col < 0 || col >= g->Columns) { |
2492 | return ret; |
2493 | } |
2494 | size_t off = g->line_offset[(size_t)row] + (size_t)col; |
2495 | ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off]))); |
2496 | int attr = g->attrs[off]; |
2497 | ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err))); |
2498 | // will not work first time |
2499 | if (!highlight_use_hlstate()) { |
2500 | ADD(ret, ARRAY_OBJ(hl_inspect(attr))); |
2501 | } |
2502 | return ret; |
2503 | } |
2504 | |