aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/buffer.c413
-rw-r--r--src/nvim/api/buffer.h145
-rw-r--r--src/nvim/api/defs.h74
-rw-r--r--src/nvim/api/helpers.c578
-rw-r--r--src/nvim/api/helpers.h90
-rw-r--r--src/nvim/api/tabpage.c88
-rw-r--r--src/nvim/api/tabpage.h47
-rw-r--r--src/nvim/api/vim.c321
-rw-r--r--src/nvim/api/vim.h158
-rw-r--r--src/nvim/api/window.c184
-rw-r--r--src/nvim/api/window.h115
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
+