diff options
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; + + case VAR_NUMBER: + rv.type = kObjectTypeInt; + rv.data.integer = obj->vval.v_number; + break; + + case VAR_FLOAT: + rv.type = kObjectTypeFloat; + rv.data.floating_point = obj->vval.v_float; + break; + + case VAR_LIST: + { + list_T *list = obj->vval.v_list; + listitem_T *item; + + if (list != NULL) { + rv.type = kObjectTypeArray; + rv.data.array.size = list->lv_len; + rv.data.array.items = xmalloc(list->lv_len * sizeof(Object)); + + uint32_t i = 0; + for (item = list->lv_first; item != NULL; item = item->li_next) { + rv.data.array.items[i] = vim_to_object_rec(&item->li_tv, lookup); + i++; + } + } + } + break; + + case VAR_DICT: + { + dict_T *dict = obj->vval.v_dict; + hashtab_T *ht; + uint64_t todo; + hashitem_T *hi; + dictitem_T *di; + + if (dict != NULL) { + ht = &obj->vval.v_dict->dv_hashtab; + todo = ht->ht_used; + rv.type = kObjectTypeDictionary; + + // Count items + rv.data.dictionary.size = 0; + for (hi = ht->ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + todo--; + rv.data.dictionary.size++; + } + } + + rv.data.dictionary.items = + xmalloc(rv.data.dictionary.size * sizeof(KeyValuePair)); + todo = ht->ht_used; + uint32_t i = 0; + + // Convert all + for (hi = ht->ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + di = dict_lookup(hi); + // Convert key + rv.data.dictionary.items[i].key.data = + xstrdup((char *)hi->hi_key); + rv.data.dictionary.items[i].key.size = + strlen((char *)hi->hi_key); + // Convert value + rv.data.dictionary.items[i].value = + vim_to_object_rec(&di->di_tv, lookup); + todo--; + i++; + } + } + } + } + break; + } + + return rv; +} + + +static void set_option_value_for(char *key, + int numval, + char *stringval, + int opt_flags, + int opt_type, + void *from, + Error *err) +{ + win_T *save_curwin = NULL; + tabpage_T *save_curtab = NULL; + buf_T *save_curbuf = NULL; + + try_start(); + switch (opt_type) + { + case SREQ_WIN: + if (switch_win(&save_curwin, &save_curtab, (win_T *)from, + win_find_tabpage((win_T *)from), false) == FAIL) + { + if (try_end(err)) { + return; + } + set_api_error("problem while switching windows", err); + return; + } + set_option_value_err(key, numval, stringval, opt_flags, err); + restore_win(save_curwin, save_curtab, true); + break; + case SREQ_BUF: + switch_buffer(&save_curbuf, (buf_T *)from); + set_option_value_err(key, numval, stringval, opt_flags, err); + restore_buffer(save_curbuf); + break; + case SREQ_GLOBAL: + set_option_value_err(key, numval, stringval, opt_flags, err); + break; + } + + if (err->set) { + return; + } + + try_end(err); +} + + +static void set_option_value_err(char *key, + int numval, + char *stringval, + int opt_flags, + Error *err) +{ + char *errmsg; + + if ((errmsg = (char *)set_option_value((uint8_t *)key, + numval, + (uint8_t *)stringval, + opt_flags))) + { + if (try_end(err)) { + return; + } + + set_api_error(errmsg, err); + } +} diff --git a/src/nvim/api/helpers.h b/src/nvim/api/helpers.h new file mode 100644 index 0000000000..afa16e54f4 --- /dev/null +++ b/src/nvim/api/helpers.h @@ -0,0 +1,90 @@ +#ifndef NEOVIM_API_HELPERS_H +#define NEOVIM_API_HELPERS_H + +#include <stdbool.h> + +#include "api/defs.h" +#include "../vim.h" + +#define set_api_error(message, err) \ + do { \ + strncpy(err->msg, message, sizeof(err->msg)); \ + err->set = true; \ + } while (0) + +/// Start block that may cause vimscript exceptions +void try_start(void); + +/// End try block, set the error message if any and return true if an error +/// occurred. +/// +/// @param err Pointer to the stack-allocated error object +/// @return true if an error occurred +bool try_end(Error *err); + +/// 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); + +/// 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); + +/// 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); + +/// 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); + +/// Convert a vim object to an `Object` instance, recursively expanding +/// Arrays/Dictionaries. +/// +/// @param obj The source object +/// @return The converted value +Object vim_to_object(typval_T *obj); + +/// 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); + +/// 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); + +/// 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); + +#endif // NEOVIM_API_HELPERS_H + diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c new file mode 100644 index 0000000000..27f0aba99c --- /dev/null +++ b/src/nvim/api/tabpage.c @@ -0,0 +1,88 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "api/tabpage.h" +#include "api/vim.h" +#include "api/defs.h" +#include "api/helpers.h" + +int64_t tabpage_get_window_count(Tabpage tabpage, Error *err) +{ + uint64_t rv = 0; + tabpage_T *tab = find_tab(tabpage, err); + + if (!tab) { + return rv; + } + + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (tp != tab) { + break; + } + rv++; + } + + return rv; +} + +Object tabpage_get_var(Tabpage tabpage, String name, Error *err) +{ + Object rv; + tabpage_T *tab = find_tab(tabpage, err); + + if (!tab) { + return rv; + } + + return dict_get_value(tab->tp_vars, name, err); +} + +Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) +{ + Object rv; + tabpage_T *tab = find_tab(tabpage, err); + + if (!tab) { + return rv; + } + + return dict_set_value(tab->tp_vars, name, value, err); +} + +Window tabpage_get_window(Tabpage tabpage, Error *err) +{ + Window rv = 0; + tabpage_T *tab = find_tab(tabpage, err); + + if (!tab) { + return rv; + } + + if (tab == curtab) { + return vim_get_current_window(); + } else { + tabpage_T *tp; + win_T *wp; + rv = 1; + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (tp == tab && wp == tab->tp_curwin) { + return rv; + } + rv++; + } + // There should always be a current window for a tabpage + abort(); + } +} + +bool tabpage_is_valid(Tabpage tabpage) +{ + Error stub = {.set = false}; + return find_tab(tabpage, &stub) != NULL; +} + diff --git a/src/nvim/api/tabpage.h b/src/nvim/api/tabpage.h new file mode 100644 index 0000000000..ccd6c7ab9e --- /dev/null +++ b/src/nvim/api/tabpage.h @@ -0,0 +1,47 @@ +#ifndef NEOVIM_API_TABPAGE_H +#define NEOVIM_API_TABPAGE_H + +#include <stdint.h> +#include <stdbool.h> + +#include "api/defs.h" + +/// Gets the number of windows in a tabpage +/// +/// @param tabpage The tabpage +/// @param[out] err Details of an error that may have occurred +/// @return The number of windows in `tabpage` +int64_t tabpage_get_window_count(Tabpage tabpage, Error *err); + +/// Gets a tabpage variable +/// +/// @param tabpage The tab page handle +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The variable value +Object tabpage_get_var(Tabpage tabpage, String name, Error *err); + +/// Sets a tabpage variable. Passing 'nil' as value deletes the variable. +/// +/// @param tabpage handle +/// @param name The variable name +/// @param value The variable value +/// @param[out] err Details of an error that may have occurred +/// @return The tab page handle +Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err); + +/// Gets the current window in a tab page +/// +/// @param tabpage The tab page handle +/// @param[out] err Details of an error that may have occurred +/// @return The Window handle +Window tabpage_get_window(Tabpage tabpage, Error *err); + +/// Checks if a tab page is valid +/// +/// @param tabpage The tab page handle +/// @return true if the tab page is valid, false otherwise +bool tabpage_is_valid(Tabpage tabpage); + +#endif // NEOVIM_API_TABPAGE_H + diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c new file mode 100644 index 0000000000..f9fa21550e --- /dev/null +++ b/src/nvim/api/vim.c @@ -0,0 +1,321 @@ +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "api/vim.h" +#include "api/helpers.h" +#include "api/defs.h" +#include "api/buffer.h" +#include "../vim.h" +#include "../buffer.h" +#include "../window.h" +#include "types.h" +#include "ascii.h" +#include "ex_docmd.h" +#include "screen.h" +#include "memory.h" +#include "message.h" +#include "eval.h" +#include "misc2.h" + +#define LINE_BUFFER_SIZE 4096 + +/// Writes a message to vim output or error buffer. The string is split +/// and flushed after each newline. Incomplete lines are kept for writing +/// later. +/// +/// @param message The message to write +/// @param to_err True if it should be treated as an error message(use +/// `emsg` instead of `msg` to print each line) +static void write_msg(String message, bool to_err); + +void vim_push_keys(String str) +{ + abort(); +} + +void vim_command(String str, Error *err) +{ + // We still use 0-terminated strings, so we must convert. + char *cmd_str = xstrndup(str.data, str.size); + // Run the command + try_start(); + do_cmdline_cmd((char_u *)cmd_str); + free(cmd_str); + update_screen(VALID); + try_end(err); +} + +Object vim_eval(String str, Error *err) +{ + Object rv; + char *expr_str = xstrndup(str.data, str.size); + // Evaluate the expression + try_start(); + typval_T *expr_result = eval_expr((char_u *)expr_str, NULL); + free(expr_str); + + 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; +} + +int64_t vim_strwidth(String str) +{ + return mb_string2cells((char_u *)str.data, str.size); +} + +StringArray vim_list_runtime_paths(void) +{ + StringArray rv = {.size = 0}; + uint8_t *rtp = p_rtp; + + if (*rtp == NUL) { + // No paths + return rv; + } + + // Count the number of paths in rtp + while (*rtp != NUL) { + if (*rtp == ',') { + rv.size++; + } + rtp++; + } + + // index + uint32_t i = 0; + // Allocate memory for the copies + rv.items = xmalloc(sizeof(String) * rv.size); + // reset the position + rtp = p_rtp; + // Start copying + while (*rtp != NUL) { + rv.items[i].data = xmalloc(MAXPATHL); + // Copy the path from 'runtimepath' to rv.items[i] + rv.items[i].size = copy_option_part(&rtp, + (char_u *)rv.items[i].data, + MAXPATHL, + ","); + i++; + } + + return rv; +} + +void vim_change_directory(String dir, Error *err) +{ + char string[MAXPATHL]; + strncpy(string, dir.data, dir.size); + + 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); +} + +String vim_get_current_line(Error *err) +{ + return buffer_get_line(curbuf->b_fnum, curwin->w_cursor.lnum - 1, err); +} + +void vim_set_current_line(String line, Error *err) +{ + buffer_set_line(curbuf->b_fnum, curwin->w_cursor.lnum - 1, line, err); +} + +void vim_del_current_line(Error *err) +{ + buffer_del_line(curbuf->b_fnum, curwin->w_cursor.lnum - 1, err); +} + +Object vim_get_var(String name, Error *err) +{ + return dict_get_value(&globvardict, name, err); +} + +Object vim_set_var(String name, Object value, Error *err) +{ + return dict_set_value(&globvardict, name, value, err); +} + +Object vim_get_vvar(String name, Error *err) +{ + return dict_get_value(&vimvardict, name, err); +} + +Object vim_get_option(String name, Error *err) +{ + return get_option_from(NULL, SREQ_GLOBAL, name, err); +} + +void vim_set_option(String name, Object value, Error *err) +{ + set_option_to(NULL, SREQ_GLOBAL, name, value, err); +} + +void vim_out_write(String str) +{ + write_msg(str, false); +} + +void vim_err_write(String str) +{ + write_msg(str, true); +} + +int64_t vim_get_buffer_count(void) +{ + buf_T *b = firstbuf; + uint64_t n = 0; + + while (b) { + n++; + b = b->b_next; + } + + return n; +} + +Buffer vim_get_current_buffer(void) +{ + return curbuf->b_fnum; +} + +void vim_set_current_buffer(Buffer buffer, Error *err) +{ + try_start(); + if (do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buffer, 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); +} + +int64_t vim_get_window_count(void) +{ + tabpage_T *tp; + win_T *wp; + uint64_t rv = 0; + + FOR_ALL_TAB_WINDOWS(tp, wp) { + rv++; + } + + return rv; +} + +Window vim_get_current_window(void) +{ + tabpage_T *tp; + win_T *wp; + Window rv = 1; + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp == curwin) { + return rv; + } + + rv++; + } + + // There should always be a current window + abort(); +} + +void vim_set_current_window(Window window, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return; + } + + try_start(); + win_goto(win); + + if (win != curwin) { + if (try_end(err)) { + return; + } + set_api_error("did not switch to the specified window", err); + return; + } + + try_end(err); +} + +int64_t vim_get_tabpage_count(void) +{ + tabpage_T *tp = first_tabpage; + uint64_t rv = 0; + + while (tp != NULL) { + tp = tp->tp_next; + rv++; + } + + return rv; +} + +Tabpage vim_get_current_tabpage(void) +{ + Tabpage rv = 1; + tabpage_T *t; + + for (t = first_tabpage; t != NULL && t != curtab; t = t->tp_next) { + rv++; + } + + return rv; +} + +void vim_set_current_tabpage(Tabpage tabpage, Error *err) +{ + try_start(); + goto_tabpage(tabpage); + try_end(err); +} + +static void write_msg(String message, bool to_err) +{ + static int pos = 0; + static char line_buf[LINE_BUFFER_SIZE]; + + for (uint32_t i = 0; i < message.size; i++) { + if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { + // Flush line + line_buf[pos] = NUL; + if (to_err) { + emsg((uint8_t *)line_buf); + } else { + msg((uint8_t *)line_buf); + } + + pos = 0; + continue; + } + + line_buf[pos++] = message.data[i]; + } +} diff --git a/src/nvim/api/vim.h b/src/nvim/api/vim.h new file mode 100644 index 0000000000..ceb696ff76 --- /dev/null +++ b/src/nvim/api/vim.h @@ -0,0 +1,158 @@ +#ifndef NEOVIM_API_VIM_H +#define NEOVIM_API_VIM_H + +#include <stdint.h> +#include <stdbool.h> + +#include "api/defs.h" + +/// Send keys to vim input buffer, simulating user input. +/// +/// @param str The keys to send +void vim_push_keys(String str); + +/// Executes an ex-mode command str +/// +/// @param str The command str +/// @param[out] err Details of an error that may have occurred +void vim_command(String str, Error *err); + +/// 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); + +/// Calculates the number of display cells `str` occupies, tab is counted as +/// one cell. +/// +/// @param str Some text +/// @return The number of cells +int64_t vim_strwidth(String str); + +/// Returns a list of paths contained in 'runtimepath' +/// +/// @return The list of paths +StringArray vim_list_runtime_paths(void); + +/// 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); + +/// Return the current line +/// +/// @param[out] err Details of an error that may have occurred +/// @return The current line string +String vim_get_current_line(Error *err); + +/// Delete the current line +/// +/// @param[out] err Details of an error that may have occurred +void vim_del_current_line(Error *err); + +/// Sets the current line +/// +/// @param line The line contents +/// @param[out] err Details of an error that may have occurred +void vim_set_current_line(String line, Error *err); + +/// Gets a global variable +/// +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The variable value +Object vim_get_var(String name, Error *err); + +/// Sets a global variable. Passing 'nil' as value deletes the variable. +/// +/// @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 if any +Object vim_set_var(String name, Object value, Error *err); + +/// Gets a vim variable +/// +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The variable value +Object vim_get_vvar(String name, Error *err); + +/// Get an option value string +/// +/// @param name The option name +/// @param[out] err Details of an error that may have occurred +/// @return The option value +Object vim_get_option(String name, Error *err); + +/// Sets an option value +/// +/// @param name The option name +/// @param value The new option value +/// @param[out] err Details of an error that may have occurred +void vim_set_option(String name, Object value, Error *err); + +/// Write a message to vim output buffer +/// +/// @param str The message +void vim_out_write(String str); + +/// Write a message to vim error buffer +/// +/// @param str The message +void vim_err_write(String str); + +/// Gets the number of buffers +/// +/// @return The number of buffers +int64_t vim_get_buffer_count(void); + +/// Return the current buffer +/// +/// @reqturn The buffer handle +Buffer vim_get_current_buffer(void); + +/// 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); + +/// Gets the number of windows +/// +/// @return The number of windows +int64_t vim_get_window_count(void); + +/// Return the current window +/// +/// @return The window handle +Window vim_get_current_window(void); + +/// Sets the current window +/// +/// @param handle The window handle +void vim_set_current_window(Window window, Error *err); + +/// Gets the number of tab pages +/// +/// @return The number of tab pages +int64_t vim_get_tabpage_count(void); + +/// Return the current tab page +/// +/// @return The tab page handle +Tabpage vim_get_current_tabpage(void); + +/// Sets the current tab page +/// +/// @param handle The tab page handle +/// @param[out] err Details of an error that may have occurred +void vim_set_current_tabpage(Tabpage tabpage, Error *err); + +#endif // NEOVIM_API_VIM_H + diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c new file mode 100644 index 0000000000..2c67fc2a74 --- /dev/null +++ b/src/nvim/api/window.c @@ -0,0 +1,184 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "api/window.h" +#include "api/defs.h" +#include "api/helpers.h" +#include "../vim.h" +#include "../window.h" +#include "screen.h" +#include "misc2.h" + + +Buffer window_get_buffer(Window window, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return 0; + } + + return win->w_buffer->b_fnum; +} + +Position window_get_cursor(Window window, Error *err) +{ + Position rv = {.row = 0, .col = 0}; + win_T *win = find_window(window, err); + + if (win) { + rv.row = win->w_cursor.lnum; + rv.col = win->w_cursor.col; + } + + return rv; +} + +void window_set_cursor(Window window, Position pos, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return; + } + + if (pos.row <= 0 || pos.row > win->w_buffer->b_ml.ml_line_count) { + set_api_error("cursor position outside buffer", err); + return; + } + + win->w_cursor.lnum = pos.row; + win->w_cursor.col = pos.col; + win->w_cursor.coladd = 0; + // When column is out of range silently correct it. + check_cursor_col_win(win); + update_screen(VALID); +} + +int64_t window_get_height(Window window, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return 0; + } + + return win->w_height; +} + +void window_set_height(Window window, int64_t height, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return; + } + + win_T *savewin = curwin; + curwin = win; + try_start(); + win_setheight(height); + curwin = savewin; + try_end(err); +} + +int64_t window_get_width(Window window, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return 0; + } + + return win->w_width; +} + +void window_set_width(Window window, int64_t width, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return; + } + + win_T *savewin = curwin; + curwin = win; + try_start(); + win_setwidth(width); + curwin = savewin; + try_end(err); +} + +Object window_get_var(Window window, String name, Error *err) +{ + Object rv; + win_T *win = find_window(window, err); + + if (!win) { + return rv; + } + + return dict_get_value(win->w_vars, name, err); +} + +Object window_set_var(Window window, String name, Object value, Error *err) +{ + Object rv; + win_T *win = find_window(window, err); + + if (!win) { + return rv; + } + + return dict_set_value(win->w_vars, name, value, err); +} + +Object window_get_option(Window window, String name, Error *err) +{ + Object rv; + win_T *win = find_window(window, err); + + if (!win) { + return rv; + } + + return get_option_from(win, SREQ_WIN, name, err); +} + +void window_set_option(Window window, String name, Object value, Error *err) +{ + win_T *win = find_window(window, err); + + if (!win) { + return; + } + + set_option_to(win, SREQ_WIN, name, value, err); +} + +Position window_get_position(Window window, Error *err) +{ + Position rv; + win_T *win = find_window(window, err); + + if (win) { + rv.col = win->w_wincol; + rv.row = win->w_winrow; + } + + return rv; +} + +Tabpage window_get_tabpage(Window window, Error *err) +{ + set_api_error("Not implemented", err); + return 0; +} + +bool window_is_valid(Window window) +{ + Error stub = {.set = false}; + return find_window(window, &stub) != NULL; +} + diff --git a/src/nvim/api/window.h b/src/nvim/api/window.h new file mode 100644 index 0000000000..e9baa3f170 --- /dev/null +++ b/src/nvim/api/window.h @@ -0,0 +1,115 @@ +#ifndef NEOVIM_API_WINDOW_H +#define NEOVIM_API_WINDOW_H + +#include <stdint.h> +#include <stdbool.h> + +#include "api/defs.h" + +/// Gets the current buffer in a window +/// +/// @param window The window handle +/// @param[out] err Details of an error that may have occurred +/// @return The buffer handle +Buffer window_get_buffer(Window window, Error *err); + +/// Gets the cursor position in the window +/// +/// @param window The window handle +/// @param[out] err Details of an error that may have occurred +/// @return the (row, col) tuple +Position window_get_cursor(Window window, Error *err); + +/// Sets the cursor position in the window +/// +/// @param window The window handle +/// @param pos the (row, col) tuple representing the new position +/// @param[out] err Details of an error that may have occurred +void window_set_cursor(Window window, Position pos, Error *err); + +/// Gets the window height +/// +/// @param window The window handle +/// @param[out] err Details of an error that may have occurred +/// @return the height in rows +int64_t window_get_height(Window window, Error *err); + +/// Sets the window height. This will only succeed if the screen is split +/// horizontally. +/// +/// @param window The window handle +/// @param height the new height in rows +/// @param[out] err Details of an error that may have occurred +void window_set_height(Window window, int64_t height, Error *err); + +/// Gets the window width +/// +/// @param window The window handle +/// @param[out] err Details of an error that may have occurred +/// @return the width in columns +int64_t window_get_width(Window window, Error *err); + +/// Sets the window width. This will only succeed if the screen is split +/// vertically. +/// +/// @param window The window handle +/// @param width the new width in columns +/// @param[out] err Details of an error that may have occurred +void window_set_width(Window window, int64_t width, Error *err); + +/// Gets a window variable +/// +/// @param window The window handle +/// @param name The variable name +/// @param[out] err Details of an error that may have occurred +/// @return The variable value +Object window_get_var(Window window, String name, Error *err); + +/// Sets a window variable. Passing 'nil' as value deletes the variable. +/// +/// @param window The window 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 window_set_var(Window window, String name, Object value, Error *err); + +/// Gets a window option value +/// +/// @param window The window handle +/// @param name The option name +/// @param[out] err Details of an error that may have occurred +/// @return The option value +Object window_get_option(Window window, String name, Error *err); + +/// Sets a window option value. Passing 'nil' as value deletes the option(only +/// works if there's a global fallback) +/// +/// @param window The window handle +/// @param name The option name +/// @param value The option value +/// @param[out] err Details of an error that may have occurred +void window_set_option(Window window, String name, Object value, Error *err); + +/// Gets the window position in display cells. First position is zero. +/// +/// @param window The window handle +/// @param[out] err Details of an error that may have occurred +/// @return The (row, col) tuple with the window position +Position window_get_position(Window window, Error *err); + +/// Gets the window tab page +/// +/// @param window The window handle +/// @param[out] err Details of an error that may have occurred +/// @return The tab page that contains the window +Tabpage window_get_tabpage(Window window, Error *err); + +/// Checks if a window is valid +/// +/// @param window The window handle +/// @return true if the window is valid, false otherwise +bool window_is_valid(Window window); + +#endif // NEOVIM_API_WINDOW_H + |