| 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 | |