helpers.c (30865B)
1 #include <assert.h> 2 #include <limits.h> 3 #include <stdarg.h> 4 #include <stdbool.h> 5 #include <stddef.h> 6 #include <stdint.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 11 #include "klib/kvec.h" 12 #include "nvim/api/private/converter.h" 13 #include "nvim/api/private/defs.h" 14 #include "nvim/api/private/helpers.h" 15 #include "nvim/api/private/validate.h" 16 #include "nvim/ascii_defs.h" 17 #include "nvim/buffer_defs.h" 18 #include "nvim/eval/typval.h" 19 #include "nvim/eval/vars.h" 20 #include "nvim/ex_eval.h" 21 #include "nvim/garray_defs.h" 22 #include "nvim/globals.h" 23 #include "nvim/highlight_group.h" 24 #include "nvim/lua/executor.h" 25 #include "nvim/map_defs.h" 26 #include "nvim/mark.h" 27 #include "nvim/memline.h" 28 #include "nvim/memory.h" 29 #include "nvim/memory_defs.h" 30 #include "nvim/message.h" 31 #include "nvim/msgpack_rpc/unpacker.h" 32 #include "nvim/pos_defs.h" 33 #include "nvim/runtime.h" 34 #include "nvim/types_defs.h" 35 36 #include "api/private/api_metadata.generated.h" 37 #include "api/private/helpers.c.generated.h" // IWYU pragma: keep 38 39 /// Start block that may cause Vimscript exceptions while evaluating another code 40 /// 41 /// Used just in case caller is supposed to be operating when other Vimscript code 42 /// is being processed and that “other Vimscript code” must not be affected. 43 /// 44 /// @warning Avoid calling directly; use TRY_WRAP instead. 45 /// 46 /// @param[out] tstate Location where try state should be saved. 47 void try_enter(TryState *const tstate) 48 { 49 // TODO(ZyX-I): Check whether try_enter()/try_leave() may use 50 // enter_cleanup()/leave_cleanup(). Or 51 // save_dbg_stuff()/restore_dbg_stuff(). 52 *tstate = (TryState) { 53 .current_exception = current_exception, 54 .msg_list = (const msglist_T *const *)msg_list, 55 .private_msg_list = NULL, 56 .got_int = got_int, 57 .did_throw = did_throw, 58 .need_rethrow = need_rethrow, 59 .did_emsg = did_emsg, 60 }; 61 // `msg_list` controls the collection of abort-causing non-exception errors, 62 // which would otherwise be ignored. This pattern is from do_cmdline(). 63 msg_list = &tstate->private_msg_list; 64 current_exception = NULL; 65 got_int = false; 66 did_throw = false; 67 need_rethrow = false; 68 did_emsg = false; 69 trylevel++; 70 } 71 72 /// Ends a `try_enter` block; sets error message if any. 73 /// 74 /// @warning Avoid calling directly; use TRY_WRAP instead. 75 /// 76 /// @param[out] err Pointer to the stack-allocated error object 77 void try_leave(const TryState *const tstate, Error *const err) 78 FUNC_ATTR_NONNULL_ALL 79 { 80 // Note: all globals manipulated here should be saved/restored in 81 // try_enter/try_leave. 82 assert(trylevel > 0); 83 trylevel--; 84 85 // Set by emsg(), affects aborting(). See also enter_cleanup(). 86 did_emsg = false; 87 force_abort = false; 88 89 if (got_int) { 90 if (did_throw) { 91 // If we got an interrupt, discard the current exception 92 discard_current_exception(); 93 } 94 95 api_set_error(err, kErrorTypeException, "Keyboard interrupt"); 96 got_int = false; 97 } else if (msg_list != NULL && *msg_list != NULL) { 98 bool should_free; 99 char *msg = get_exception_string(*msg_list, 100 ET_ERROR, 101 NULL, 102 &should_free); 103 api_set_error(err, kErrorTypeException, "%s", msg); 104 free_global_msglist(); 105 106 if (should_free) { 107 xfree(msg); 108 } 109 } else if (did_throw || need_rethrow) { 110 if (*current_exception->throw_name != NUL) { 111 if (current_exception->throw_lnum != 0) { 112 api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s", 113 current_exception->throw_name, current_exception->throw_lnum, 114 current_exception->value); 115 } else { 116 api_set_error(err, kErrorTypeException, "%s: %s", 117 current_exception->throw_name, current_exception->value); 118 } 119 } else { 120 api_set_error(err, kErrorTypeException, "%s", current_exception->value); 121 } 122 discard_current_exception(); 123 } 124 125 // Restore the exception context. 126 msg_list = (msglist_T **)tstate->msg_list; 127 current_exception = tstate->current_exception; 128 got_int = tstate->got_int; 129 did_throw = tstate->did_throw; 130 need_rethrow = tstate->need_rethrow; 131 did_emsg = tstate->did_emsg; 132 } 133 134 /// Recursively expands a vimscript value in a dict 135 /// 136 /// @param dict The vimscript dict 137 /// @param key The key 138 /// @param[out] err Details of an error that may have occurred 139 Object dict_get_value(dict_T *dict, String key, Arena *arena, Error *err) 140 { 141 dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); 142 143 if (di == NULL) { 144 api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); 145 return (Object)OBJECT_INIT; 146 } 147 148 return vim_to_object(&di->di_tv, arena, true); 149 } 150 151 dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err) 152 { 153 dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); 154 155 if (di != NULL) { 156 if (di->di_flags & DI_FLAGS_RO) { 157 api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data); 158 } else if (di->di_flags & DI_FLAGS_LOCK) { 159 api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data); 160 } else if (del && (di->di_flags & DI_FLAGS_FIX)) { 161 api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data); 162 } 163 } else if (dict->dv_lock) { 164 api_set_error(err, kErrorTypeException, "Dict is locked"); 165 } else if (key.size == 0) { 166 api_set_error(err, kErrorTypeValidation, "Key name is empty"); 167 } else if (key.size > INT_MAX) { 168 api_set_error(err, kErrorTypeValidation, "Key name is too long"); 169 } 170 171 return di; 172 } 173 174 /// Set a value in a scope dict. Objects are recursively expanded into their 175 /// vimscript equivalents. 176 /// 177 /// @param dict The vimscript dict 178 /// @param key The key 179 /// @param value The new value 180 /// @param del Delete key in place of setting it. Argument `value` is ignored in 181 /// this case. 182 /// @param retval If true the old value will be converted and returned. 183 /// @param[out] err Details of an error that may have occurred 184 /// @return The old value if `retval` is true and the key was present, else NIL 185 Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retval, Arena *arena, 186 Error *err) 187 { 188 Object rv = OBJECT_INIT; 189 dictitem_T *di = dict_check_writable(dict, key, del, err); 190 191 if (ERROR_SET(err)) { 192 return rv; 193 } 194 195 bool watched = tv_dict_is_watched(dict); 196 197 if (del) { 198 // Delete the key 199 if (di == NULL) { 200 // Doesn't exist, fail 201 api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); 202 } else { 203 // Notify watchers 204 if (watched) { 205 tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv); 206 } 207 // Return the old value 208 if (retval) { 209 rv = vim_to_object(&di->di_tv, arena, false); 210 } 211 // Delete the entry 212 tv_dict_item_remove(dict, di); 213 } 214 } else { 215 // Update the key 216 typval_T tv; 217 218 // Convert the object to a vimscript type in the temporary variable 219 object_to_vim(value, &tv, err); 220 221 typval_T oldtv = TV_INITIAL_VALUE; 222 223 if (di == NULL) { 224 // Need to create an entry 225 di = tv_dict_item_alloc_len(key.data, key.size); 226 tv_dict_add(dict, di); 227 } else { 228 // Return the old value 229 if (retval) { 230 rv = vim_to_object(&di->di_tv, arena, false); 231 } 232 bool type_error = false; 233 if (dict == get_vimvar_dict() 234 && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) { 235 tv_clear(&tv); 236 if (type_error) { 237 api_set_error(err, kErrorTypeValidation, 238 "Setting v:%s to value with wrong type", key.data); 239 } 240 return rv; 241 } 242 if (watched) { 243 tv_copy(&di->di_tv, &oldtv); 244 } 245 tv_clear(&di->di_tv); 246 } 247 248 // Update the value 249 tv_copy(&tv, &di->di_tv); 250 251 // Notify watchers 252 if (watched) { 253 tv_dict_watcher_notify(dict, key.data, &tv, &oldtv); 254 tv_clear(&oldtv); 255 } 256 257 // Clear the temporary variable 258 tv_clear(&tv); 259 } 260 261 return rv; 262 } 263 264 buf_T *find_buffer_by_handle(Buffer buffer, Error *err) 265 { 266 if (buffer == 0) { 267 return curbuf; 268 } 269 270 buf_T *rv = handle_get_buffer(buffer); 271 272 if (!rv) { 273 api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer); 274 } 275 276 return rv; 277 } 278 279 win_T *find_window_by_handle(Window window, Error *err) 280 { 281 if (window == 0) { 282 return curwin; 283 } 284 285 win_T *rv = handle_get_window(window); 286 287 if (!rv) { 288 api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window); 289 } 290 291 return rv; 292 } 293 294 tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err) 295 { 296 if (tabpage == 0) { 297 return curtab; 298 } 299 300 tabpage_T *rv = handle_get_tabpage(tabpage); 301 302 if (!rv) { 303 api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage); 304 } 305 306 return rv; 307 } 308 309 /// Allocates a String consisting of a single char. Does not support multibyte 310 /// characters. The resulting string is also NUL-terminated, to facilitate 311 /// interoperating with code using C strings. 312 /// 313 /// @param char the char to convert 314 /// @return the resulting String, if the input char was NUL, an 315 /// empty String is returned 316 String cchar_to_string(char c) 317 { 318 char buf[] = { c, NUL }; 319 return (String){ 320 .data = xmemdupz(buf, 1), 321 .size = (c != NUL) ? 1 : 0 322 }; 323 } 324 325 /// Copies a C string into a String (binary safe string, characters + length). 326 /// The resulting string is also NUL-terminated, to facilitate interoperating 327 /// with code using C strings. 328 /// 329 /// @param str the C string to copy 330 /// @return the resulting String, if the input string was NULL, an 331 /// empty String is returned 332 String cstr_to_string(const char *str) 333 { 334 if (str == NULL) { 335 return (String)STRING_INIT; 336 } 337 338 size_t len = strlen(str); 339 return (String){ 340 .data = xmemdupz(str, len), 341 .size = len, 342 }; 343 } 344 345 /// Copies a String to an allocated, NUL-terminated C string. 346 /// 347 /// @param str the String to copy 348 /// @return the resulting C string 349 char *string_to_cstr(String str) 350 FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT 351 { 352 return xstrndup(str.data, str.size); 353 } 354 355 /// Copies buffer to an allocated String. 356 /// The resulting string is also NUL-terminated, to facilitate interoperating 357 /// with code using C strings. 358 /// 359 /// @param buf the buffer to copy 360 /// @param size length of the buffer 361 /// @return the resulting String, if the input string was NULL, an 362 /// empty String is returned 363 String cbuf_to_string(const char *buf, size_t size) 364 FUNC_ATTR_NONNULL_ALL 365 { 366 return (String){ 367 .data = xmemdupz(buf, size), 368 .size = size 369 }; 370 } 371 372 String cstrn_to_string(const char *str, size_t maxsize) 373 FUNC_ATTR_NONNULL_ALL 374 { 375 return cbuf_to_string(str, strnlen(str, maxsize)); 376 } 377 378 String cstrn_as_string(char *str, size_t maxsize) 379 FUNC_ATTR_NONNULL_ALL 380 { 381 return cbuf_as_string(str, strnlen(str, maxsize)); 382 } 383 384 /// Creates a String using the given C string. Unlike 385 /// cstr_to_string this function DOES NOT copy the C string. 386 /// 387 /// @param str the C string to use 388 /// @return The resulting String, or an empty String if 389 /// str was NULL 390 String cstr_as_string(const char *str) FUNC_ATTR_PURE 391 { 392 if (str == NULL) { 393 return (String)STRING_INIT; 394 } 395 return (String){ .data = (char *)str, .size = strlen(str) }; 396 } 397 398 /// Return the owned memory of a ga as a String 399 /// 400 /// Reinitializes the ga to a valid empty state. 401 String ga_take_string(garray_T *ga) 402 { 403 String str = { .data = (char *)ga->ga_data, .size = (size_t)ga->ga_len }; 404 ga->ga_data = NULL; 405 ga->ga_len = 0; 406 ga->ga_maxlen = 0; 407 return str; 408 } 409 410 /// Creates "readfile()-style" ArrayOf(String) from a binary string. 411 /// 412 /// - Lines break at \n (NL/LF/line-feed). 413 /// - NUL bytes are replaced with NL. 414 /// - If the last byte is a linebreak an extra empty list item is added. 415 /// 416 /// @param input Binary string 417 /// @param crlf Also break lines at CR and CRLF. 418 /// @return [allocated] String array 419 Array string_to_array(const String input, bool crlf, Arena *arena) 420 { 421 ArrayBuilder ret = ARRAY_DICT_INIT; 422 kvi_init(ret); 423 for (size_t i = 0; i < input.size; i++) { 424 const char *start = input.data + i; 425 const char *end = start; 426 size_t line_len = 0; 427 for (; line_len < input.size - i; line_len++) { 428 end = start + line_len; 429 if (*end == NL || (crlf && *end == CAR)) { 430 break; 431 } 432 } 433 i += line_len; 434 if (crlf && *end == CAR && i + 1 < input.size && *(end + 1) == NL) { 435 i += 1; // Advance past CRLF. 436 } 437 String s = CBUF_TO_ARENA_STR(arena, start, line_len); 438 memchrsub(s.data, NUL, NL, line_len); 439 kvi_push(ret, STRING_OBJ(s)); 440 // If line ends at end-of-buffer, add empty final item. 441 // This is "readfile()-style", see also ":help channel-lines". 442 if (i + 1 == input.size && (*end == NL || (crlf && *end == CAR))) { 443 kvi_push(ret, STRING_OBJ(STRING_INIT)); 444 } 445 } 446 447 return arena_take_arraybuilder(arena, &ret); 448 } 449 450 /// Normalizes 0-based indexes to buffer line numbers. 451 int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) 452 { 453 assert(buf->b_ml.ml_line_count > 0); 454 int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1; 455 // A negative index counts from the bottom. 456 index = index < 0 ? max_index + index + 1 : index; 457 458 // Check for oob and clamp. 459 if (index > max_index) { 460 *oob = true; 461 index = max_index; 462 } else if (index < 0) { 463 *oob = true; 464 index = 0; 465 } 466 // Convert the index to a 1-based line number. 467 index++; 468 return index; 469 } 470 471 /// Returns a substring of a buffer line 472 /// 473 /// @param buf Buffer id 474 /// @param lnum Line number (1-based) 475 /// @param start_col Starting byte offset into line (0-based) 476 /// @param end_col Ending byte offset into line (0-based, exclusive) 477 /// @param err Error object 478 /// @return The text between start_col and end_col on line lnum of buffer buf 479 String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, Error *err) 480 { 481 String rv = STRING_INIT; 482 483 if (lnum >= MAXLNUM) { 484 api_set_error(err, kErrorTypeValidation, "Line index is too high"); 485 return rv; 486 } 487 488 char *bufstr = ml_get_buf(buf, (linenr_T)lnum); 489 colnr_T line_length = ml_get_buf_len(buf, (linenr_T)lnum); 490 491 start_col = start_col < 0 ? line_length + start_col + 1 : start_col; 492 end_col = end_col < 0 ? line_length + end_col + 1 : end_col; 493 494 start_col = MIN(MAX(0, start_col), line_length); 495 end_col = MIN(MAX(0, end_col), line_length); 496 497 if (start_col > end_col) { 498 api_set_error(err, kErrorTypeValidation, "start_col must be less than or equal to end_col"); 499 return rv; 500 } 501 502 return cbuf_as_string(bufstr + start_col, (size_t)(end_col - start_col)); 503 } 504 505 void api_free_string(String value) 506 { 507 xfree(value.data); 508 } 509 510 Array arena_array(Arena *arena, size_t max_size) 511 { 512 Array arr = ARRAY_DICT_INIT; 513 kv_fixsize_arena(arena, arr, max_size); 514 return arr; 515 } 516 517 Dict arena_dict(Arena *arena, size_t max_size) 518 { 519 Dict dict = ARRAY_DICT_INIT; 520 kv_fixsize_arena(arena, dict, max_size); 521 return dict; 522 } 523 524 String arena_string(Arena *arena, String str) 525 { 526 if (str.size) { 527 return cbuf_as_string(arena_memdupz(arena, str.data, str.size), str.size); 528 } else { 529 return (String){ .data = arena ? "" : xstrdup(""), .size = 0 }; 530 } 531 } 532 533 Array arena_take_arraybuilder(Arena *arena, ArrayBuilder *arr) 534 { 535 Array ret = arena_array(arena, kv_size(*arr)); 536 ret.size = kv_size(*arr); 537 memcpy(ret.items, arr->items, sizeof(ret.items[0]) * ret.size); 538 kvi_destroy(*arr); 539 return ret; 540 } 541 542 void api_free_object(Object value) 543 { 544 switch (value.type) { 545 case kObjectTypeNil: 546 case kObjectTypeBoolean: 547 case kObjectTypeInteger: 548 case kObjectTypeFloat: 549 case kObjectTypeBuffer: 550 case kObjectTypeWindow: 551 case kObjectTypeTabpage: 552 break; 553 554 case kObjectTypeString: 555 api_free_string(value.data.string); 556 break; 557 558 case kObjectTypeArray: 559 api_free_array(value.data.array); 560 break; 561 562 case kObjectTypeDict: 563 api_free_dict(value.data.dict); 564 break; 565 566 case kObjectTypeLuaRef: 567 api_free_luaref(value.data.luaref); 568 break; 569 } 570 } 571 572 void api_free_array(Array value) 573 { 574 for (size_t i = 0; i < value.size; i++) { 575 api_free_object(value.items[i]); 576 } 577 578 xfree(value.items); 579 } 580 581 void api_free_dict(Dict value) 582 { 583 for (size_t i = 0; i < value.size; i++) { 584 api_free_string(value.items[i].key); 585 api_free_object(value.items[i].value); 586 } 587 588 xfree(value.items); 589 } 590 591 void api_clear_error(Error *value) 592 FUNC_ATTR_NONNULL_ALL 593 { 594 if (!ERROR_SET(value)) { 595 return; 596 } 597 xfree(value->msg); 598 value->msg = NULL; 599 value->type = kErrorTypeNone; 600 } 601 602 // initialized once, never freed 603 static ArenaMem mem_for_metadata = NULL; 604 605 /// @returns a shared value. caller must not modify it! 606 Object api_metadata(void) 607 { 608 static Object metadata = OBJECT_INIT; 609 610 if (metadata.type == kObjectTypeNil) { 611 Arena arena = ARENA_EMPTY; 612 Error err = ERROR_INIT; 613 metadata = unpack((char *)packed_api_metadata, sizeof(packed_api_metadata), &arena, &err); 614 if (ERROR_SET(&err) || metadata.type != kObjectTypeDict) { 615 abort(); 616 } 617 mem_for_metadata = arena_finish(&arena); 618 } 619 620 return metadata; 621 } 622 623 String api_metadata_raw(void) 624 { 625 return cbuf_as_string((char *)packed_api_metadata, sizeof(packed_api_metadata)); 626 } 627 628 // all the copy_[object] functions allow arena=NULL, 629 // then global allocations are used, and the resulting object 630 // should be freed with an api_free_[object] function 631 632 String copy_string(String str, Arena *arena) 633 { 634 if (str.data != NULL) { 635 return (String){ .data = arena_memdupz(arena, str.data, str.size), .size = str.size }; 636 } else { 637 return (String)STRING_INIT; 638 } 639 } 640 641 Array copy_array(Array array, Arena *arena) 642 { 643 Array rv = arena_array(arena, array.size); 644 for (size_t i = 0; i < array.size; i++) { 645 ADD(rv, copy_object(array.items[i], arena)); 646 } 647 return rv; 648 } 649 650 Dict copy_dict(Dict dict, Arena *arena) 651 { 652 Dict rv = arena_dict(arena, dict.size); 653 for (size_t i = 0; i < dict.size; i++) { 654 KeyValuePair item = dict.items[i]; 655 PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena)); 656 } 657 return rv; 658 } 659 660 /// Creates a deep clone of an object 661 Object copy_object(Object obj, Arena *arena) 662 { 663 switch (obj.type) { 664 case kObjectTypeBuffer: 665 case kObjectTypeTabpage: 666 case kObjectTypeWindow: 667 case kObjectTypeNil: 668 case kObjectTypeBoolean: 669 case kObjectTypeInteger: 670 case kObjectTypeFloat: 671 return obj; 672 673 case kObjectTypeString: 674 return STRING_OBJ(copy_string(obj.data.string, arena)); 675 676 case kObjectTypeArray: 677 return ARRAY_OBJ(copy_array(obj.data.array, arena)); 678 679 case kObjectTypeDict: 680 return DICT_OBJ(copy_dict(obj.data.dict, arena)); 681 682 case kObjectTypeLuaRef: 683 return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); 684 } 685 UNREACHABLE; 686 } 687 688 void api_set_error(Error *err, ErrorType errType, const char *format, ...) 689 FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PRINTF(3, 4) 690 { 691 assert(kErrorTypeNone != errType); 692 va_list args1; 693 va_list args2; 694 va_start(args1, format); 695 va_copy(args2, args1); 696 int len = vsnprintf(NULL, 0, format, args1); 697 va_end(args1); 698 assert(len >= 0); 699 // Limit error message to 1 MB. 700 size_t bufsize = MIN((size_t)len + 1, 1024 * 1024); 701 err->msg = xmalloc(bufsize); 702 vsnprintf(err->msg, bufsize, format, args2); 703 va_end(args2); 704 705 err->type = errType; 706 } 707 708 /// Force obj to bool. 709 /// If it fails, returns false and sets err 710 /// @param obj The object to coerce to a boolean 711 /// @param what The name of the object, used for error message 712 /// @param nil_value What to return if the type is nil. 713 /// @param err Set if there was an error in converting to a bool 714 bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err) 715 { 716 if (obj.type == kObjectTypeBoolean) { 717 return obj.data.boolean; 718 } else if (obj.type == kObjectTypeInteger) { 719 return obj.data.integer; // C semantics: non-zero int is true 720 } else if (obj.type == kObjectTypeNil) { 721 return nil_value; // caller decides what NIL (missing retval in Lua) means 722 } else { 723 api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what); 724 return false; 725 } 726 } 727 728 int object_to_hl_id(Object obj, const char *what, Error *err) 729 { 730 if (obj.type == kObjectTypeString) { 731 String str = obj.data.string; 732 return str.size ? syn_check_group(str.data, str.size) : 0; 733 } else if (obj.type == kObjectTypeInteger) { 734 int id = (int)obj.data.integer; 735 return (1 <= id && id <= highlight_num_groups()) ? id : 0; 736 } else { 737 api_set_error(err, kErrorTypeValidation, "Invalid hl_group: %s", what); 738 return 0; 739 } 740 } 741 742 char *api_typename(ObjectType t) 743 { 744 switch (t) { 745 case kObjectTypeNil: 746 return "nil"; 747 case kObjectTypeBoolean: 748 return "Boolean"; 749 case kObjectTypeInteger: 750 return "Integer"; 751 case kObjectTypeFloat: 752 return "Float"; 753 case kObjectTypeString: 754 return "String"; 755 case kObjectTypeArray: 756 return "Array"; 757 case kObjectTypeDict: 758 return "Dict"; 759 case kObjectTypeLuaRef: 760 return "Function"; 761 case kObjectTypeBuffer: 762 return "Buffer"; 763 case kObjectTypeWindow: 764 return "Window"; 765 case kObjectTypeTabpage: 766 return "Tabpage"; 767 } 768 UNREACHABLE; 769 } 770 771 HlMessage parse_hl_msg(ArrayOf(Tuple(String, *HLGroupID)) chunks, bool is_err, Error *err) 772 { 773 HlMessage hl_msg = KV_INITIAL_VALUE; 774 for (size_t i = 0; i < chunks.size; i++) { 775 VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, { 776 goto free_exit; 777 }); 778 Tuple(String, *HLGroupID) chunk = chunks.items[i].data.array; 779 VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString), 780 "%s", "Invalid chunk: expected Array with 1 or 2 Strings", { 781 goto free_exit; 782 }); 783 784 String str = copy_string(chunk.items[0].data.string, NULL); 785 786 int hl_id = 787 chunk.size == 2 ? object_to_hl_id(chunk.items[1], "text highlight", err) 788 : is_err ? HLF_E 789 : 0; 790 kv_push(hl_msg, ((HlMessageChunk){ .text = str, .hl_id = hl_id })); 791 } 792 793 return hl_msg; 794 795 free_exit: 796 hl_msg_free(hl_msg); 797 return (HlMessage)KV_INITIAL_VALUE; 798 } 799 800 // see also nlua_pop_keydict for the lua specific implementation 801 bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dict dict, Error *err) 802 { 803 for (size_t i = 0; i < dict.size; i++) { 804 String k = dict.items[i].key; 805 KeySetLink *field = hashy(k.data, k.size); 806 if (!field) { 807 api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data); 808 return false; 809 } 810 811 if (field->opt_index >= 0) { 812 OptKeySet *ks = (OptKeySet *)retval; 813 ks->is_set_ |= (1ULL << field->opt_index); 814 } 815 816 char *mem = ((char *)retval + field->ptr_off); 817 Object *value = &dict.items[i].value; 818 819 if (field->type == kObjectTypeNil) { 820 *(Object *)mem = *value; 821 } else if (field->type == kObjectTypeInteger) { 822 if (field->is_hlgroup) { 823 int hl_id = 0; 824 if (value->type != kObjectTypeNil) { 825 hl_id = object_to_hl_id(*value, k.data, err); 826 if (ERROR_SET(err)) { 827 return false; 828 } 829 } 830 *(Integer *)mem = hl_id; 831 } else { 832 VALIDATE_T(field->str, kObjectTypeInteger, value->type, { 833 return false; 834 }); 835 836 *(Integer *)mem = value->data.integer; 837 } 838 } else if (field->type == kObjectTypeFloat) { 839 Float *val = (Float *)mem; 840 if (value->type == kObjectTypeInteger) { 841 *val = (Float)value->data.integer; 842 } else { 843 VALIDATE_T(field->str, kObjectTypeFloat, value->type, { 844 return false; 845 }); 846 *val = value->data.floating; 847 } 848 } else if (field->type == kObjectTypeBoolean) { 849 // caller should check HAS_KEY to override the nil behavior, or GET_BOOL_OR_TRUE 850 // to directly use true when nil 851 *(Boolean *)mem = api_object_to_bool(*value, field->str, false, err); 852 if (ERROR_SET(err)) { 853 return false; 854 } 855 } else if (field->type == kObjectTypeString) { 856 VALIDATE_T(field->str, kObjectTypeString, value->type, { 857 return false; 858 }); 859 *(String *)mem = value->data.string; 860 } else if (field->type == kObjectTypeArray) { 861 VALIDATE_T(field->str, kObjectTypeArray, value->type, { 862 return false; 863 }); 864 *(Array *)mem = value->data.array; 865 } else if (field->type == kObjectTypeDict) { 866 Dict *val = (Dict *)mem; 867 // allow empty array as empty dict for lua (directly or via lua-client RPC) 868 if (value->type == kObjectTypeArray && value->data.array.size == 0) { 869 *val = (Dict)ARRAY_DICT_INIT; 870 } else if (value->type == kObjectTypeDict) { 871 *val = value->data.dict; 872 } else { 873 api_err_exp(err, field->str, api_typename((ObjectType)field->type), 874 api_typename(value->type)); 875 return false; 876 } 877 } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow 878 || field->type == kObjectTypeTabpage) { 879 if (value->type == kObjectTypeInteger || value->type == (ObjectType)field->type) { 880 *(handle_T *)mem = (handle_T)value->data.integer; 881 } else { 882 api_err_exp(err, field->str, api_typename((ObjectType)field->type), 883 api_typename(value->type)); 884 return false; 885 } 886 } else if (field->type == kObjectTypeLuaRef) { 887 api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s' is only allowed from Lua", 888 (int)k.size, k.data); 889 return false; 890 } else { 891 abort(); 892 } 893 } 894 895 return true; 896 } 897 898 Dict api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size, Arena *arena) 899 { 900 Dict rv = arena_dict(arena, max_size); 901 for (size_t i = 0; table[i].str; i++) { 902 KeySetLink *field = &table[i]; 903 bool is_set = true; 904 if (field->opt_index >= 0) { 905 OptKeySet *ks = (OptKeySet *)value; 906 is_set = ks->is_set_ & (1ULL << field->opt_index); 907 } 908 909 if (!is_set) { 910 continue; 911 } 912 913 char *mem = ((char *)value + field->ptr_off); 914 Object val = NIL; 915 916 if (field->type == kObjectTypeNil) { 917 val = *(Object *)mem; 918 } else if (field->type == kObjectTypeInteger) { 919 val = INTEGER_OBJ(*(Integer *)mem); 920 } else if (field->type == kObjectTypeFloat) { 921 val = FLOAT_OBJ(*(Float *)mem); 922 } else if (field->type == kObjectTypeBoolean) { 923 val = BOOLEAN_OBJ(*(Boolean *)mem); 924 } else if (field->type == kObjectTypeString) { 925 val = STRING_OBJ(*(String *)mem); 926 } else if (field->type == kObjectTypeArray) { 927 val = ARRAY_OBJ(*(Array *)mem); 928 } else if (field->type == kObjectTypeDict) { 929 val = DICT_OBJ(*(Dict *)mem); 930 } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow 931 || field->type == kObjectTypeTabpage) { 932 val.data.integer = *(handle_T *)mem; 933 val.type = (ObjectType)field->type; 934 } else if (field->type == kObjectTypeLuaRef) { 935 // do nothing 936 } else { 937 abort(); 938 } 939 940 PUT_C(rv, field->str, val); 941 } 942 943 return rv; 944 } 945 946 void api_luarefs_free_object(Object value) 947 { 948 // TODO(bfredl): this is more complicated than it needs to be. 949 // we should be able to lock down more specifically where luarefs can be 950 switch (value.type) { 951 case kObjectTypeLuaRef: 952 api_free_luaref(value.data.luaref); 953 break; 954 955 case kObjectTypeArray: 956 api_luarefs_free_array(value.data.array); 957 break; 958 959 case kObjectTypeDict: 960 api_luarefs_free_dict(value.data.dict); 961 break; 962 963 default: 964 break; 965 } 966 } 967 968 void api_luarefs_free_keydict(void *dict, KeySetLink *table) 969 { 970 for (size_t i = 0; table[i].str; i++) { 971 char *mem = ((char *)dict + table[i].ptr_off); 972 if (table[i].type == kObjectTypeNil) { 973 api_luarefs_free_object(*(Object *)mem); 974 } else if (table[i].type == kObjectTypeLuaRef) { 975 api_free_luaref(*(LuaRef *)mem); 976 } else if (table[i].type == kObjectTypeDict) { 977 api_luarefs_free_dict(*(Dict *)mem); 978 } 979 } 980 } 981 982 void api_luarefs_free_array(Array value) 983 { 984 for (size_t i = 0; i < value.size; i++) { 985 api_luarefs_free_object(value.items[i]); 986 } 987 } 988 989 void api_luarefs_free_dict(Dict value) 990 { 991 for (size_t i = 0; i < value.size; i++) { 992 api_luarefs_free_object(value.items[i].value); 993 } 994 } 995 996 /// Set a named mark 997 /// buffer and mark name must be validated already 998 /// @param buffer Buffer to set the mark on 999 /// @param name Mark name 1000 /// @param line Line number 1001 /// @param col Column/row number 1002 /// @return true if the mark was set, else false 1003 bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err) 1004 { 1005 buf = buf == NULL ? curbuf : buf; 1006 // If line == 0 the marks is being deleted 1007 bool res = false; 1008 bool deleting = false; 1009 if (line == 0) { 1010 col = 0; 1011 deleting = true; 1012 } else { 1013 if (col > MAXCOL) { 1014 api_set_error(err, kErrorTypeValidation, "Column value outside range"); 1015 return res; 1016 } 1017 if (line < 1 || line > buf->b_ml.ml_line_count) { 1018 api_set_error(err, kErrorTypeValidation, "Line value outside range"); 1019 return res; 1020 } 1021 } 1022 assert(INT32_MIN <= line && line <= INT32_MAX); 1023 pos_T pos = { (linenr_T)line, (int)col, 0 }; 1024 res = setmark_pos(*name.data, &pos, buf->handle, NULL); 1025 if (!res) { 1026 if (deleting) { 1027 api_set_error(err, kErrorTypeException, 1028 "Failed to delete named mark: %c", *name.data); 1029 } else { 1030 api_set_error(err, kErrorTypeException, 1031 "Failed to set named mark: %c", *name.data); 1032 } 1033 } 1034 return res; 1035 } 1036 1037 /// Get default statusline highlight for window 1038 const char *get_default_stl_hl(win_T *wp, bool use_winbar, int stc_hl_id) 1039 { 1040 if (wp == NULL) { 1041 return "TabLineFill"; 1042 } else if (use_winbar) { 1043 return (wp == curwin) ? "WinBar" : "WinBarNC"; 1044 } else if (stc_hl_id > 0) { 1045 return syn_id2name(stc_hl_id); 1046 } else { 1047 return (wp == curwin) ? "StatusLine" : "StatusLineNC"; 1048 } 1049 } 1050 1051 /// Sets sctx for API calls. 1052 /// 1053 /// @param channel_id api client id to determine if it's a internal or RPC call. 1054 /// 1055 /// @return previous value of current_sctx. To be used later for restoring sctx. 1056 sctx_T api_set_sctx(uint64_t channel_id) 1057 { 1058 sctx_T old_current_sctx = current_sctx; 1059 // The script context is already properly set when calling an API from Vimscript. 1060 if (channel_id != VIML_INTERNAL_CALL) { 1061 current_sctx.sc_lnum = 0; 1062 if (channel_id == LUA_INTERNAL_CALL) { 1063 // When the current script is a Lua script, don't override sc_sid. 1064 if (!script_is_lua(current_sctx.sc_sid)) { 1065 current_sctx.sc_sid = SID_LUA; 1066 } 1067 } else { 1068 current_sctx.sc_sid = SID_API_CLIENT; 1069 current_sctx.sc_chan = channel_id; 1070 } 1071 } 1072 return old_current_sctx; 1073 }