/// Set a value in a dict. Objects are recursively expanded into their /// vimscript equivalents. Passing 'nil' as value deletes the key. /// /// @param dict The vimscript dict /// @param key The key /// @param value The new value /// @param[out] err Details of an error that may have occurred /// @return the old value, if any Object dict_set_value(dict_T *dict, String key, Object value, Error *err) { Object rv = OBJECT_INIT; if (dict->dv_lock) { set_api_error("Dictionary is locked", err); return rv; } if (key.size == 0) { set_api_error("Empty dictionary keys aren't allowed", err); return rv; } if (key.size > INT_MAX) { set_api_error("Key length is too high", err); return rv; } dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size); if (value.type == kObjectTypeNil) { // Delete the key if (di == NULL) { // Doesn't exist, fail set_api_error("Key doesn't exist", err); } else { // Return the old value rv = vim_to_object(&di->di_tv); // Delete the entry hashitem_T *hi = hash_find(&dict->dv_hashtab, di->di_key); hash_remove(&dict->dv_hashtab, hi); dictitem_free(di); } } else { // Update the key typval_T tv; // Convert the object to a vimscript type in the temporary variable if (!object_to_vim(value, &tv, err)) { return rv; } if (di == NULL) { // Need to create an entry di = dictitem_alloc((uint8_t *) key.data); dict_add(dict, di); } else { // Return the old value clear_tv(&di->di_tv); } // Update the value copy_tv(&tv, &di->di_tv); // Clear the temporary variable clear_tv(&tv); } return rv; }
/// Calculates the number of display cells `str` occupies, tab is counted as /// one cell. /// /// @param str Some text /// @param[out] err Details of an error that may have occurred /// @return The number of cells Integer vim_strwidth(String str, Error *err) { if (str.size > INT_MAX) { set_api_error("String length is too high", err); return 0; } return (Integer) mb_string2cells((char_u *) str.data); }
/// Finds the pointer for a window number /// /// @param window the window number /// @param[out] err Details of an error that may have occurred /// @return the window pointer win_T * find_window(Window window, Error *err) { win_T *rv = handle_get_window(window); if (!rv) { set_api_error("Invalid window id", err); } return rv; }
/// Finds the pointer for a tabpage number /// /// @param tabpage the tabpage number /// @param[out] err Details of an error that may have occurred /// @return the tabpage pointer tabpage_T * find_tab(Tabpage tabpage, Error *err) { tabpage_T *rv = handle_get_tabpage(tabpage); if (!rv) { set_api_error("Invalid tabpage id", err); } return rv; }
/// Finds the pointer for a window number /// /// @param window the window number /// @param[out] err Details of an error that may have occurred /// @return the window pointer buf_T *find_buffer(Buffer buffer, Error *err) { buf_T *rv = handle_get_buffer(buffer); if (!rv) { set_api_error("Invalid buffer id", err); } return rv; }
Integer vim_strwidth(String str, Error *err) { if (str.size > INT_MAX) { set_api_error("String length is too high", err); return 0; } char *buf = xstrndup(str.data, str.size); Integer rv = mb_string2cells((char_u *)buf, -1); free(buf); return rv; }
/// Recursively expands a vimscript value in a dict /// /// @param dict The vimscript dict /// @param key The key /// @param[out] err Details of an error that may have occurred Object dict_get_value(dict_T *dict, String key, Error *err) { hashitem_T *hi = hash_find(&dict->dv_hashtab, (uint8_t *) key.data); if (HASHITEM_EMPTY(hi)) { set_api_error("Key not found", err); return (Object) OBJECT_INIT; } dictitem_T *di = dict_lookup(hi); return vim_to_object(&di->di_tv); }
/// Gets the value of a global or local(buffer, window) option. /// /// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer /// to the window or buffer. /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred /// @return the option value Object get_option_from(void *from, int type, String name, Error *err) { Object rv = OBJECT_INIT; if (name.size == 0) { set_api_error("Empty option name", err); return rv; } // Return values int64_t numval; char *stringval = NULL; int flags = get_option_value_strict(name.data, &numval, &stringval, type, from); if (!flags) { set_api_error("invalid option name", err); return rv; } if (flags & SOPT_BOOL) { rv.type = kObjectTypeBoolean; rv.data.boolean = numval ? true : false; } else if (flags & SOPT_NUM) { rv.type = kObjectTypeInteger; rv.data.integer = numval; } else if (flags & SOPT_STRING) { if (stringval) { rv.type = kObjectTypeString; rv.data.string.data = stringval; rv.data.string.size = strlen(stringval); } else { set_api_error(N_("Unable to get option value"), err); } } else { set_api_error(N_("internal error: unknown option type"), err); } return rv; }
/// Changes vim working directory /// /// @param dir The new working directory /// @param[out] err Details of an error that may have occurred void vim_change_directory(String dir, Error *err) { if (dir.size >= MAXPATHL) { set_api_error("directory string is too long", err); return; } char string[MAXPATHL]; strncpy(string, dir.data, dir.size); string[dir.size] = NUL; try_start(); if (vim_chdir((char_u *)string)) { if (!try_end(err)) { set_api_error("failed to change directory", err); } return; } post_chdir(false); try_end(err); }
bool try_end(Error *err) { --trylevel; // Without this it stops processing all subsequent VimL commands and // generates strange error messages if I e.g. try calling Test() in a // cycle did_emsg = false; if (got_int) { if (did_throw) { // If we got an interrupt, discard the current exception discard_current_exception(); } set_api_error("Keyboard interrupt", err); got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; char *msg = (char *)get_exception_string(*msg_list, ET_ERROR, NULL, &should_free); strncpy(err->msg, msg, sizeof(err->msg)); err->set = true; free_global_msglist(); if (should_free) { free(msg); } } else if (did_throw) { set_api_error((char *)current_exception->value, err); } return err->set; }
/// Pass input keys to Neovim /// /// @param keys to be typed /// @param replace_tcodes If true replace special keys such as <CR> or <Leader> /// for compatibility with Vim --remote-send expressions /// @param remap If True remap keys /// @param typed Handle keys as if typed; otherwise they are handled as /// if coming from a mapping. This matters for undo, /// opening folds, etc. void vim_feedkeys(String keys, Boolean replace_tcodes, Boolean remap, Boolean typed, Error *err) { char *ptr = NULL; char *cpo_save = (char *)p_cpo; if (replace_tcodes) { // Set 'cpoptions' the way we want it. // B set - backslashes are *not* treated specially // k set - keycodes are *not* reverse-engineered // < unset - <Key> sequences *are* interpreted // The last but one parameter of replace_termcodes() is TRUE so that the // <lt> sequence is recognised - needed for a real backslash. p_cpo = (char_u *)"Bk"; replace_termcodes((char_u *)keys.data, (char_u **)&ptr, false, true, true); p_cpo = (char_u *)cpo_save; } else { ptr = keys.data; } if (ptr == NULL) { set_api_error("Failed to eval expression", err); } else { // Add the string to the input stream. // Can't use add_to_input_buf() here, we now have K_SPECIAL bytes. // // First clear typed characters from the typeahead buffer, there could // be half a mapping there. Then append to the existing string, so // that multiple commands from a client are concatenated. if (typebuf.tb_maplen < typebuf.tb_len) { del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen); } (void)ins_typebuf((char_u *)ptr, (remap ? REMAP_YES : REMAP_NONE), typebuf.tb_len, !typed, false); // Let input_available() know we inserted text in the typeahead // buffer. */ typebuf_was_filled = true; if (replace_tcodes) { free(ptr); } } }
Object dict_get_value(dict_T *dict, String key, Error *err) { Object rv; hashitem_T *hi; dictitem_T *di; char *k = xstrndup(key.data, key.size); hi = hash_find(&dict->dv_hashtab, (uint8_t *)k); free(k); if (HASHITEM_EMPTY(hi)) { set_api_error("Key not found", err); return rv; } di = dict_lookup(hi); rv = vim_to_object(&di->di_tv); return rv; }
/// Evaluates the expression str using the vim internal expression /// evaluator (see |expression|). /// Dictionaries and lists are recursively expanded. /// /// @param str The expression str /// @param[out] err Details of an error that may have occurred /// @return The expanded object Object vim_eval(String str, Error *err) { Object rv; // Evaluate the expression try_start(); typval_T *expr_result = eval_expr((char_u *) str.data, NULL); if (!expr_result) { set_api_error("Failed to eval expression", err); } if (!try_end(err)) { // No errors, convert the result rv = vim_to_object(expr_result); } // Free the vim object free_tv(expr_result); return rv; }
/// Sets the current window /// /// @param handle The window handle void vim_set_current_window(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); if (!win) { return; } try_start(); goto_tabpage_win(win_find_tabpage(win), win); if (win != curwin) { if (try_end(err)) { return; } set_api_error("did not switch to the specified window", err); return; } try_end(err); }
/// Sets the current buffer /// /// @param id The buffer handle /// @param[out] err Details of an error that may have occurred void vim_set_current_buffer(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { return; } try_start(); if (do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0) == FAIL) { if (try_end(err)) { return; } char msg[256]; snprintf(msg, sizeof(msg), "failed to switch to buffer %d", (int)buffer); set_api_error(msg, err); return; } try_end(err); }
/// Sets the value of a global or local(buffer, window) option. /// /// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer /// to the window or buffer. /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred void set_option_to(void *to, int type, String name, Object value, Error *err) { if (name.size == 0) { set_api_error("Empty option name", err); return; } int flags = get_option_value_strict(name.data, NULL, NULL, type, to); if (flags == 0) { set_api_error("invalid option name", err); return; } if (value.type == kObjectTypeNil) { if (type == SREQ_GLOBAL) { set_api_error("unable to unset option", err); return; } else if (!(flags & SOPT_GLOBAL)) { set_api_error("cannot unset option that doesn't have a global value", err); return; } else { unset_global_local_option(name.data, to); return; } } int opt_flags = (type ? OPT_LOCAL : OPT_GLOBAL); if (flags & SOPT_BOOL) { if (value.type != kObjectTypeBoolean) { set_api_error("option requires a boolean value", err); return; } bool val = value.data.boolean; set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); } else if (flags & SOPT_NUM) { if (value.type != kObjectTypeInteger) { set_api_error("option requires an integer value", err); return; } if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) { set_api_error("Option value outside range", err); return; } int val = (int) value.data.integer; set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); } else { if (value.type != kObjectTypeString) { set_api_error("option requires a string value", err); return; } set_option_value_for(name.data, 0, value.data.string.data, opt_flags, type, to, err); } }
/// Copies a C string into a String (binary safe string, characters + length). /// The resulting string is also NUL-terminated, to facilitate interoperating /// with code using C strings. /// /// @param str the C string to copy /// @return the resulting String, if the input string was NULL, an /// empty String is returned String cstr_to_string(const char *str) { if (str == NULL) { return (String) STRING_INIT; } size_t len = strlen(str); return (String) { .data = xmemdupz(str, len), .size = len }; } static bool object_to_vim(Object obj, typval_T *tv, Error *err) { tv->v_type = VAR_UNKNOWN; tv->v_lock = 0; switch (obj.type) { case kObjectTypeNil: tv->v_type = VAR_NUMBER; tv->vval.v_number = 0; break; case kObjectTypeBoolean: tv->v_type = VAR_NUMBER; tv->vval.v_number = obj.data.boolean; break; case kObjectTypeInteger: if (obj.data.integer > INT_MAX || obj.data.integer < INT_MIN) { set_api_error("Integer value outside range", err); return false; } tv->v_type = VAR_NUMBER; tv->vval.v_number = (int)obj.data.integer; break; case kObjectTypeFloat: tv->v_type = VAR_FLOAT; tv->vval.v_float = obj.data.floating; break; case kObjectTypeString: tv->v_type = VAR_STRING; tv->vval.v_string = xmemdupz(obj.data.string.data, obj.data.string.size); break; case kObjectTypeArray: tv->v_type = VAR_LIST; tv->vval.v_list = list_alloc(); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; listitem_T *li = listitem_alloc(); if (!object_to_vim(item, &li->li_tv, err)) { // cleanup listitem_free(li); list_free(tv->vval.v_list, true); return false; } list_append(tv->vval.v_list, li); } tv->vval.v_list->lv_refcount++; break; case kObjectTypeDictionary: tv->v_type = VAR_DICT; tv->vval.v_dict = dict_alloc(); for (uint32_t i = 0; i < obj.data.dictionary.size; i++) { KeyValuePair item = obj.data.dictionary.items[i]; String key = item.key; if (key.size == 0) { set_api_error("Empty dictionary keys aren't allowed", err); // cleanup dict_free(tv->vval.v_dict, true); return false; } dictitem_T *di = dictitem_alloc((uint8_t *) key.data); if (!object_to_vim(item.value, &di->di_tv, err)) { // cleanup dictitem_free(di); dict_free(tv->vval.v_dict, true); return false; } dict_add(tv->vval.v_dict, di); } tv->vval.v_dict->dv_refcount++; break; } return true; }