diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/api | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2 rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip | |
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/.
- src/nvim/ becomes the new root dir for nvim executable sources.
- src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/api')
| -rw-r--r-- | src/nvim/api/buffer.c | 413 | ||||
| -rw-r--r-- | src/nvim/api/buffer.h | 145 | ||||
| -rw-r--r-- | src/nvim/api/defs.h | 74 | ||||
| -rw-r--r-- | src/nvim/api/helpers.c | 578 | ||||
| -rw-r--r-- | src/nvim/api/helpers.h | 90 | ||||
| -rw-r--r-- | src/nvim/api/tabpage.c | 88 | ||||
| -rw-r--r-- | src/nvim/api/tabpage.h | 47 | ||||
| -rw-r--r-- | src/nvim/api/vim.c | 321 | ||||
| -rw-r--r-- | src/nvim/api/vim.h | 158 | ||||
| -rw-r--r-- | src/nvim/api/window.c | 184 | ||||
| -rw-r--r-- | src/nvim/api/window.h | 115 |
11 files changed, 2213 insertions, 0 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c new file mode 100644 index 0000000000..328b26061d --- /dev/null +++ b/src/nvim/api/buffer.c @@ -0,0 +1,413 @@ +// Much of this code was adapted from 'if_py_both.h' from the original +// vim source +#include <stdint.h> +#include <stdlib.h> + +#include "api/buffer.h" +#include "api/helpers.h" +#include "api/defs.h" +#include "../vim.h" +#include "../buffer.h" +#include "memline.h" +#include "memory.h" +#include "misc1.h" +#include "misc2.h" +#include "ex_cmds.h" +#include "mark.h" +#include "fileio.h" +#include "move.h" +#include "../window.h" +#include "undo.h" + +// Find a window that contains "buf" and switch to it. +// If there is no such window, use the current window and change "curbuf". +// Caller must initialize save_curbuf to NULL. +// restore_win_for_buf() MUST be called later! +static void switch_to_win_for_buf(buf_T *buf, + win_T **save_curwinp, + tabpage_T **save_curtabp, + buf_T **save_curbufp); + +static void restore_win_for_buf(win_T *save_curwin, + tabpage_T *save_curtab, + buf_T *save_curbuf); + +// Check if deleting lines made the cursor position invalid. +// Changed the lines from "lo" to "hi" and added "extra" lines (negative if +// deleted). +static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra); + +// Normalizes 0-based indexes to buffer line numbers +static int64_t normalize_index(buf_T *buf, int64_t index); + +int64_t buffer_get_length(Buffer buffer, Error *err) +{ + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return 0; + } + + return buf->b_ml.ml_line_count; +} + +String buffer_get_line(Buffer buffer, int64_t index, Error *err) +{ + String rv = {.size = 0}; + StringArray slice = buffer_get_slice(buffer, index, index, true, true, err); + + if (slice.size) { + rv = slice.items[0]; + } + + return rv; +} + +void buffer_set_line(Buffer buffer, int64_t index, String line, Error *err) +{ + StringArray array = {.items = &line, .size = 1}; + buffer_set_slice(buffer, index, index, true, true, array, err); +} + +void buffer_del_line(Buffer buffer, int64_t index, Error *err) +{ + StringArray array = {.size = 0}; + buffer_set_slice(buffer, index, index, true, true, array, err); +} + +StringArray buffer_get_slice(Buffer buffer, + int64_t start, + int64_t end, + bool include_start, + bool include_end, + Error *err) +{ + StringArray rv = {.size = 0}; + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return rv; + } + + start = normalize_index(buf, start) + (include_start ? 0 : 1); + end = normalize_index(buf, end) + (include_end ? 1 : 0); + + if (start >= end) { + // Return 0-length array + return rv; + } + + rv.size = end - start; + rv.items = xmalloc(sizeof(String) * rv.size); + + for (uint32_t i = 0; i < rv.size; i++) { + rv.items[i].data = xstrdup((char *)ml_get_buf(buf, start + i, false)); + rv.items[i].size = strlen(rv.items[i].data); + } + + return rv; +} + +void buffer_set_slice(Buffer buffer, + int64_t start, + int64_t end, + bool include_start, + bool include_end, + StringArray replacement, + Error *err) +{ + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return; + } + + start = normalize_index(buf, start) + (include_start ? 0 : 1); + end = normalize_index(buf, end) + (include_end ? 1 : 0); + + if (start > end) { + set_api_error("start > end", err); + return; + } + + buf_T *save_curbuf = NULL; + win_T *save_curwin = NULL; + tabpage_T *save_curtab = NULL; + uint32_t new_len = replacement.size; + uint32_t old_len = end - start; + uint32_t i; + int32_t extra = 0; // lines added to text, can be negative + char **lines; + + if (new_len == 0) { + // avoid allocating zero bytes + lines = NULL; + } else { + lines = xcalloc(sizeof(char *), new_len); + } + + for (i = 0; i < new_len; i++) { + String l = replacement.items[i]; + lines[i] = xstrndup(l.data, l.size); + } + + try_start(); + switch_to_win_for_buf(buf, &save_curwin, &save_curtab, &save_curbuf); + + if (u_save(start - 1, end) == FAIL) { + set_api_error("Cannot save undo information", err); + goto cleanup; + } + + // If the size of the range is reducing (ie, new_len < old_len) we + // need to delete some old_len. We do this at the start, by + // repeatedly deleting line "start". + for (i = 0; new_len < old_len && i < old_len - new_len; i++) { + if (ml_delete(start, false) == FAIL) { + set_api_error("Cannot delete line", err); + goto cleanup; + } + } + + extra -= i; + + // For as long as possible, replace the existing old_len with the + // new old_len. This is a more efficient operation, as it requires + // less memory allocation and freeing. + for (i = 0; i < old_len && i < new_len; i++) { + if (ml_replace(start + i, (char_u *)lines[i], false) == FAIL) { + set_api_error("Cannot replace line", err); + goto cleanup; + } + // Mark lines that haven't been passed to the buffer as they need + // to be freed later + lines[i] = NULL; + } + + // Now we may need to insert the remaining new old_len + while (i < new_len) { + if (ml_append(start + i - 1, (char_u *)lines[i], 0, false) == FAIL) { + set_api_error("Cannot insert line", err); + goto cleanup; + } + // Same as with replacing + lines[i] = NULL; + i++; + extra++; + } + + // Adjust marks. Invalidate any which lie in the + // changed range, and move any in the remainder of the buffer. + // Only adjust marks if we managed to switch to a window that holds + // the buffer, otherwise line numbers will be invalid. + if (save_curbuf == NULL) { + mark_adjust(start, end - 1, MAXLNUM, extra); + } + + changed_lines(start, 0, end, extra); + + if (buf == curbuf) { + fix_cursor(start, end, extra); + } + +cleanup: + for (uint32_t i = 0; i < new_len; i++) { + if (lines[i] != NULL) { + free(lines[i]); + } + } + + free(lines); + restore_win_for_buf(save_curwin, save_curtab, save_curbuf); + try_end(err); +} + +Object buffer_get_var(Buffer buffer, String name, Error *err) +{ + Object rv; + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return rv; + } + + return dict_get_value(buf->b_vars, name, err); +} + +Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) +{ + Object rv; + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return rv; + } + + return dict_set_value(buf->b_vars, name, value, err); +} + +Object buffer_get_option(Buffer buffer, String name, Error *err) +{ + Object rv; + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return rv; + } + + return get_option_from(buf, SREQ_BUF, name, err); +} + +void buffer_set_option(Buffer buffer, String name, Object value, Error *err) +{ + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return; + } + + set_option_to(buf, SREQ_BUF, name, value, err); +} + +String buffer_get_name(Buffer buffer, Error *err) +{ + String rv = {.size = 0, .data = ""}; + buf_T *buf = find_buffer(buffer, err); + + if (!buf || buf->b_ffname == NULL) { + return rv; + } + + rv.data = xstrdup((char *)buf->b_ffname); + rv.size = strlen(rv.data); + return rv; +} + +void buffer_set_name(Buffer buffer, String name, Error *err) +{ + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return; + } + + aco_save_T aco; + int ren_ret; + char *val = xstrndup(name.data, name.size); + + try_start(); + // Using aucmd_*: autocommands will be executed by rename_buffer + aucmd_prepbuf(&aco, buf); + ren_ret = rename_buffer((char_u *)val); + aucmd_restbuf(&aco); + + if (try_end(err)) { + return; + } + + if (ren_ret == FAIL) { + set_api_error("failed to rename buffer", err); + } +} + +bool buffer_is_valid(Buffer buffer) +{ + Error stub = {.set = false}; + return find_buffer(buffer, &stub) != NULL; +} + +void buffer_insert(Buffer buffer, int64_t index, StringArray lines, Error *err) +{ + buffer_set_slice(buffer, index, index, false, true, lines, err); +} + +Position buffer_get_mark(Buffer buffer, String name, Error *err) +{ + Position rv; + buf_T *buf = find_buffer(buffer, err); + + if (!buf) { + return rv; + } + + if (name.size != 0) { + set_api_error("mark name must be a single character", err); + return rv; + } + + pos_T *posp; + buf_T *savebuf; + char mark = *name.data; + + try_start(); + switch_buffer(&savebuf, buf); + posp = getmark(mark, false); + restore_buffer(savebuf); + + if (try_end(err)) { + return rv; + } + + if (posp == NULL) { + set_api_error("invalid mark name", err); + return rv; + } + + rv.row = posp->lnum; + rv.col = posp->col; + return rv; +} + +static void switch_to_win_for_buf(buf_T *buf, + win_T **save_curwinp, + tabpage_T **save_curtabp, + buf_T **save_curbufp) +{ + win_T *wp; + tabpage_T *tp; + + if (find_win_for_buf(buf, &wp, &tp) == FAIL + || switch_win(save_curwinp, save_curtabp, wp, tp, true) == FAIL) + switch_buffer(save_curbufp, buf); +} + +static void restore_win_for_buf(win_T *save_curwin, + tabpage_T *save_curtab, + buf_T *save_curbuf) +{ + if (save_curbuf == NULL) { + restore_win(save_curwin, save_curtab, true); + } else { + restore_buffer(save_curbuf); + } +} + +static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) +{ + if (curwin->w_cursor.lnum >= lo) { + // Adjust the cursor position if it's in/after the changed + // lines. + if (curwin->w_cursor.lnum >= hi) { + curwin->w_cursor.lnum += extra; + check_cursor_col(); + } else if (extra < 0) { + curwin->w_cursor.lnum = lo; + check_cursor(); + } else { + check_cursor_col(); + } + changed_cline_bef_curs(); + } + invalidate_botline(); +} + +static int64_t normalize_index(buf_T *buf, int64_t index) +{ + // Fix if < 0 + index = index < 0 ? buf->b_ml.ml_line_count + index : index; + // Convert the index to a vim line number + index++; + // Fix if > line_count + index = index > buf->b_ml.ml_line_count ? buf->b_ml.ml_line_count : index; + return index; +} diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h new file mode 100644 index 0000000000..04f9422c19 --- /dev/null +++ b/src/nvim/api/buffer.h @@ -0,0 +1,145 @@ +#ifndef NEOVIM_API_BUFFER_H +#define NEOVIM_API_BUFFER_H + +#include <stdint.h> +#include <stdbool.h> + +#include "api/defs.h" + +/// Gets the buffer line count +/// +/// @param buffer The buffer handle +/// @param[out] err Details of an error that may have occurred +/// @return The line count +int64_t buffer_get_length(Buffer buffer, Error *err); + +/// Gets a buffer line +/// +/// @param buffer The buffer handle +/// @param index The line index +/// @param[out] err Details of an error that may have occurred +/// @return The line string +String buffer_get_line(Buffer buffer, int64_t index, Error *err); + +/// Sets a buffer line +/// +/// @param buffer The buffer handle +/// @param index The line index +/// @param line The new line. +/// @param[out] err Details of an error that may have occurred +void buffer_set_line(Buffer buffer, int64_t index, String line, Error *err); + +/// Deletes a buffer line +/// +/// @param buffer The buffer handle +/// @param index The line index +/// @param[out] err Details of an error that may have occurred +void buffer_del_line(Buffer buffer, int64_t index, Error *err); + +/// Retrieves a line range from the buffer +/// +/// @param buffer The buffer handle +/// @param start The first line index +/// @param end The last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param[out] err Details of an error that may have occurred +/// @return An array of lines +StringArray buffer_get_slice(Buffer buffer, + int64_t start, + int64_t end, + bool include_start, + bool include_end, + Error *err); + +/// Replaces a line range on the buffer +/// +/// @param buffer The buffer handle +/// @param start The first line index +/// @param end The last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param lines An array of lines to use as replacement(A 0-length array +/// will simply delete the line range) +/// @param[out] err Details of an error that may have occurred +void buffer_set_slice(Buffer buffer, + int64_t start, + int64_t end, + bool include_start, + bool include_end, + StringArray replacement, + Error *err); + +/// Gets a buffer variable +/// +/// @param buffer The buffer handle +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The variable value +Object buffer_get_var(Buffer buffer, String name, Error *err); + +/// Sets a buffer variable. Passing 'nil' as value deletes the variable. +/// +/// @param buffer The buffer handle +/// @param name The variable name +/// @param value The variable value +/// @param[out] err Details of an error that may have occurred +/// @return The old value +Object buffer_set_var(Buffer buffer, String name, Object value, Error *err); + +/// Gets a buffer option value +/// +/// @param buffer The buffer handle +/// @param name The option name +/// @param[out] err Details of an error that may have occurred +/// @return The option value +Object buffer_get_option(Buffer buffer, String name, Error *err); + +/// Sets a buffer option value. Passing 'nil' as value deletes the option(only +/// works if there's a global fallback) +/// +/// @param buffer The buffer handle +/// @param name The option name +/// @param value The option value +/// @param[out] err Details of an error that may have occurred +void buffer_set_option(Buffer buffer, String name, Object value, Error *err); + +/// Gets the full file name for the buffer +/// +/// @param buffer The buffer handle +/// @param[out] err Details of an error that may have occurred +/// @return The buffer name +String buffer_get_name(Buffer buffer, Error *err); + +/// Sets the full file name for a buffer +/// +/// @param buffer The buffer handle +/// @param name The buffer name +/// @param[out] err Details of an error that may have occurred +void buffer_set_name(Buffer buffer, String name, Error *err); + +/// Checks if a buffer is valid +/// +/// @param buffer The buffer handle +/// @return true if the buffer is valid, false otherwise +bool buffer_is_valid(Buffer buffer); + +/// Inserts a sequence of lines to a buffer at a certain index +/// +/// @param buffer The buffer handle +/// @param lnum Insert the lines before `lnum`. If negative, it will append +/// to the end of the buffer. +/// @param lines An array of lines +/// @param[out] err Details of an error that may have occurred +void buffer_insert(Buffer buffer, int64_t index, StringArray lines, Error *err); + +/// Return a tuple (row,col) representing the position of the named mark +/// +/// @param buffer The buffer handle +/// @param name The mark's name +/// @param[out] err Details of an error that may have occurred +/// @return The (row, col) tuple +Position buffer_get_mark(Buffer buffer, String name, Error *err); + +#endif // NEOVIM_API_BUFFER_H + diff --git a/src/nvim/api/defs.h b/src/nvim/api/defs.h new file mode 100644 index 0000000000..0ac2e790d2 --- /dev/null +++ b/src/nvim/api/defs.h @@ -0,0 +1,74 @@ +#ifndef NEOVIM_API_DEFS_H +#define NEOVIM_API_DEFS_H + +#include <stdint.h> +#include <string.h> + +// Basic types +typedef struct { + char msg[256]; + bool set; +} Error; + +typedef struct { + char *data; + size_t size; +} String; + +typedef uint16_t Buffer; +typedef uint16_t Window; +typedef uint16_t Tabpage; + +typedef struct object Object; + +typedef struct { + String *items; + size_t size; +} StringArray; + +typedef struct { + uint16_t row, col; +} Position; + +typedef struct { + Object *items; + size_t size; +} Array; + +typedef struct key_value_pair KeyValuePair; + +typedef struct { + KeyValuePair *items; + size_t size; +} Dictionary; + +typedef enum { + kObjectTypeNil, + kObjectTypeBool, + kObjectTypeInt, + kObjectTypeFloat, + kObjectTypeString, + kObjectTypeArray, + kObjectTypeDictionary +} ObjectType; + +struct object { + ObjectType type; + union { + bool boolean; + int64_t integer; + double floating_point; + String string; + Array array; + Dictionary dictionary; + } data; +}; + +struct key_value_pair { + String key; + Object value; +}; + + +#endif // NEOVIM_API_DEFS_H + diff --git a/src/nvim/api/helpers.c b/src/nvim/api/helpers.c new file mode 100644 index 0000000000..51b1dcb754 --- /dev/null +++ b/src/nvim/api/helpers.c @@ -0,0 +1,578 @@ +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "api/helpers.h" +#include "api/defs.h" +#include "../vim.h" +#include "../buffer.h" +#include "../window.h" +#include "memory.h" +#include "eval.h" +#include "option.h" +#include "option_defs.h" + +#include "lib/khash.h" + +#if defined(ARCH_64) +typedef uint64_t ptr_int_t; +KHASH_SET_INIT_INT64(Lookup) +#elif defined(ARCH_32) +typedef uint32_t ptr_int_t; +KHASH_SET_INIT_INT(Lookup) +#endif + +/// Recursion helper for the `vim_to_object`. This uses a pointer table +/// to avoid infinite recursion due to cyclic references +/// +/// @param obj The source object +/// @param lookup Lookup table containing pointers to all processed objects +/// @return The converted value +static Object vim_to_object_rec(typval_T *obj, khash_t(Lookup) *lookup); + +static bool object_to_vim(Object obj, typval_T *tv, Error *err); + +static void set_option_value_for(char *key, + int numval, + char *stringval, + int opt_flags, + int opt_type, + void *from, + Error *err); + +static void set_option_value_err(char *key, + int numval, + char *stringval, + int opt_flags, + Error *err); + +void try_start() +{ + ++trylevel; +} + +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; +} + +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; +} + +Object dict_set_value(dict_T *dict, String key, Object value, Error *err) +{ + Object rv = {.type = kObjectTypeNil}; + + 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; + } + + dictitem_T *di = dict_find(dict, (uint8_t *)key.data, 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 + char *k = xstrndup(key.data, key.size); + di = dictitem_alloc((uint8_t *)k); + free(k); + 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; +} + +Object get_option_from(void *from, int type, String name, Error *err) +{ + Object rv = {.type = kObjectTypeNil}; + + if (name.size == 0) { + set_api_error("Empty option name", err); + return rv; + } + + // Return values + int64_t numval; + char *stringval = NULL; + // copy the option name into 0-delimited string + char *key = xstrndup(name.data, name.size); + int flags = get_option_value_strict(key, &numval, &stringval, type, from); + free(key); + + if (!flags) { + set_api_error("invalid option name", err); + return rv; + } + + if (flags & SOPT_BOOL) { + rv.type = kObjectTypeBool; + rv.data.boolean = numval ? true : false; + } else if (flags & SOPT_NUM) { + rv.type = kObjectTypeInt; + 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; +} + +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; + } + + char *key = xstrndup(name.data, name.size); + int flags = get_option_value_strict(key, NULL, NULL, type, to); + + if (flags == 0) { + set_api_error("invalid option name", err); + goto cleanup; + } + + if (value.type == kObjectTypeNil) { + if (type == SREQ_GLOBAL) { + set_api_error("unable to unset option", err); + goto cleanup; + } else if (!(flags & SOPT_GLOBAL)) { + set_api_error("cannot unset option that doesn't have a global value", + err); + goto cleanup; + } else { + unset_global_local_option(key, to); + goto cleanup; + } + } + + int opt_flags = (type ? OPT_LOCAL : OPT_GLOBAL); + + if (flags & SOPT_BOOL) { + if (value.type != kObjectTypeBool) { + set_api_error("option requires a boolean value", err); + goto cleanup; + } + bool val = value.data.boolean; + set_option_value_for(key, val, NULL, opt_flags, type, to, err); + + } else if (flags & SOPT_NUM) { + if (value.type != kObjectTypeInt) { + set_api_error("option requires an integer value", err); + goto cleanup; + } + + int val = value.data.integer; + set_option_value_for(key, val, NULL, opt_flags, type, to, err); + } else { + if (value.type != kObjectTypeString) { + set_api_error("option requires a string value", err); + goto cleanup; + } + + char *val = xstrndup(value.data.string.data, value.data.string.size); + set_option_value_for(key, 0, val, opt_flags, type, to, err); + } + +cleanup: + free(key); +} + +Object vim_to_object(typval_T *obj) +{ + Object rv; + // We use a lookup table to break out of cyclic references + khash_t(Lookup) *lookup = kh_init(Lookup); + rv = vim_to_object_rec(obj, lookup); + // Free the table + kh_destroy(Lookup, lookup); + return rv; +} + +buf_T *find_buffer(Buffer buffer, Error *err) +{ + buf_T *buf = buflist_findnr(buffer); + + if (buf == NULL) { + set_api_error("Invalid buffer id", err); + } + + return buf; +} + +win_T * find_window(Window window, Error *err) +{ + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (!--window) { + return wp; + } + } + + set_api_error("Invalid window id", err); + return NULL; +} + +tabpage_T * find_tab(Tabpage tabpage, Error *err) +{ + tabpage_T *rv = find_tabpage(tabpage); + + if (!rv) { + set_api_error("Invalid tabpage id", err); + } + + return rv; +} + +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 kObjectTypeBool: + tv->v_type = VAR_NUMBER; + tv->vval.v_number = obj.data.boolean; + break; + + case kObjectTypeInt: + tv->v_type = VAR_NUMBER; + tv->vval.v_number = obj.data.integer; + break; + + case kObjectTypeFloat: + tv->v_type = VAR_FLOAT; + tv->vval.v_float = obj.data.floating_point; + break; + + case kObjectTypeString: + tv->v_type = VAR_STRING; + tv->vval.v_string = (uint8_t *)xstrndup(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; + } + + char *k = xstrndup(key.data, key.size); + dictitem_T *di = dictitem_alloc((uint8_t *)k); + free(k); + + 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; +} + +static Object vim_to_object_rec(typval_T *obj, khash_t(Lookup) *lookup) +{ + Object rv = {.type = kObjectTypeNil}; + + if (obj->v_type == VAR_LIST || obj->v_type == VAR_DICT) { + int ret; + // Container object, add it to the lookup table + kh_put(Lookup, lookup, (ptr_int_t)obj, &ret); + if (!ret) { + // It's already present, meaning we alredy processed it so just return + // nil instead. + return rv; + } + } + + switch (obj->v_type) { + case VAR_STRING: + if (obj->vval.v_string != NULL) { + rv.type = kObjectTypeString; + rv.data.string.data = xstrdup((char *)obj->vval.v_string); + rv.data.string.size = strlen(rv.data.string.data); + } + break; |